mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-26 06:20:10 +01:00
Compare commits
157 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3738c3fc38 | ||
|
|
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 | ||
|
|
98b4693525 | ||
|
|
94c05e9e2d | ||
|
|
172a2af982 | ||
|
|
1efa2bda30 | ||
|
|
6ced56e11d | ||
|
|
1408dd5245 | ||
|
|
3b6609f13a | ||
|
|
2c6aa8e0d0 | ||
|
|
fc26e6321b | ||
|
|
ed2889cecf | ||
|
|
33c7d9cba5 | ||
|
|
7d5940101b | ||
|
|
4760fd7f52 | ||
|
|
3f292e3ce3 | ||
|
|
941fc5f51c | ||
|
|
8d59ad2713 | ||
|
|
2010a525d3 | ||
|
|
ff6db3e08e | ||
|
|
914e8c6c7a | ||
|
|
bf801f4f7f | ||
|
|
f9f08f7f5c | ||
|
|
b940d9f3a1 | ||
|
|
3372d8f102 | ||
|
|
55e4c7e4cb | ||
|
|
57af462ecf | ||
|
|
8ea56477d9 | ||
|
|
f2093a3f76 | ||
|
|
03428f3380 | ||
|
|
5d39e1357e | ||
|
|
99dbd109ce | ||
|
|
a7735677ae | ||
|
|
ae7ac460b9 | ||
|
|
ead1c144b2 | ||
|
|
207421cb7b | ||
|
|
f4efb37b03 | ||
|
|
df075e6628 | ||
|
|
faf4641625 | ||
|
|
e8268969ea | ||
|
|
bfd26c98e3 | ||
|
|
18ff08243b | ||
|
|
b9a895f825 | ||
|
|
dabd2af828 | ||
|
|
954f76d107 | ||
|
|
fb20b96024 | ||
|
|
f322a8b159 | ||
|
|
60c47e96a8 | ||
|
|
8df58db415 | ||
|
|
878dd7a0c9 | ||
|
|
567d5181ca | ||
|
|
3413ca9617 | ||
|
|
78b6df769b | ||
|
|
9a0053a501 | ||
|
|
963d10f1ac | ||
|
|
3337af64ca | ||
|
|
2374d034d7 | ||
|
|
7a8ecbf41d |
133 changed files with 7556 additions and 1544 deletions
121
NEWS
121
NEWS
|
|
@ -1,3 +1,121 @@
|
|||
# PipeWire 1.5.84 (2025-11-27)
|
||||
|
||||
This is the fourth 1.6 release candidate that is API and ABI
|
||||
compatible with previous 1.4.x, 1.2.x and 1.0.x releases.
|
||||
|
||||
Changes since the last pre-release:
|
||||
|
||||
## Highlights
|
||||
- Capabilities wer added to improve negotiation over links.
|
||||
- The audio resampler now has a configurable window function to better
|
||||
tune the resampler quality. A kaiser and blackman window was added
|
||||
and the default parameters were tuned.
|
||||
- Various small fixes and improvements.
|
||||
|
||||
## PipeWire
|
||||
- Capabilities and PeerCapabilities were added to exchange key/value
|
||||
pairs between consumer and producer right after a link is made. This
|
||||
can be used to detect how the negotiation of formats and buffers
|
||||
should be done.
|
||||
|
||||
## Modules
|
||||
- Avoid segfaults in RTP source. (#4970)
|
||||
- The AVB module has seen some improvements.
|
||||
|
||||
## Pulse-server
|
||||
- @NONE@ can now be used to clear the default sink/source.
|
||||
|
||||
## SPA
|
||||
- Support longer convolver filenames and also support inline
|
||||
IRs.
|
||||
- The audio resampler window function is now selectable and
|
||||
configurable. A kaiser window and blackman window was added
|
||||
and the default qualities were tweaked to improve quality.
|
||||
- The filter-graph convolver latency is now set by default to
|
||||
something more sensible. (0 by default and N/2 for hilbert).
|
||||
(#4980)
|
||||
|
||||
## Bluetooth
|
||||
- Better xrun and error handling for iso streams.
|
||||
- The +CNUM reply was fixed.
|
||||
- The CIEC call status was fixed. (#1744)
|
||||
- Add BAP context metadata to improve compatibility.
|
||||
- Improve compatiblity with Creative Zen Hybrid Pro by releasing
|
||||
transports simultaneously.
|
||||
|
||||
|
||||
Older versions:
|
||||
|
||||
# PipeWire 1.5.83 (2025-11-06)
|
||||
|
||||
This is the third 1.6 release candidate that is API and ABI
|
||||
compatible with previous 1.4.x, 1.2.x and 1.0.x releases.
|
||||
|
||||
Changes since the last pre-release:
|
||||
|
||||
## Highlights
|
||||
- Include the NEWS and updated version number.
|
||||
|
||||
# PipeWire 1.5.82 (2025-11-06)
|
||||
|
||||
This is the second 1.6 release candidate that is API and ABI
|
||||
compatible with previous 1.4.x, 1.2.x and 1.0.x releases.
|
||||
|
||||
Changes since the last pre-release:
|
||||
|
||||
## Highlights
|
||||
- The max channel limit is now a compile time option.
|
||||
- The SAP and RTP module have seen some robustness improvements.
|
||||
- Add audio.layout propperty.
|
||||
- Cleanups to the code here and there.
|
||||
|
||||
## PipeWire
|
||||
- Handle Tags more like Latency with a NULL param when no ports are linked
|
||||
and some sort of (empty) Tag when the ports are linked.
|
||||
|
||||
## Modules
|
||||
- Improve the echo-cancel module to keep the streams more aligned
|
||||
and cause less latency.
|
||||
- Improve format parsing errors in most modules.
|
||||
- The RTP module now has extra code for better network robustness, including
|
||||
cases when network interfaces are not yet up and running, and multicast
|
||||
sockets are silently kicked out of IGMP groups.
|
||||
- The direct timestamp mode in the RTP module was effectively broken and is
|
||||
now fixed.
|
||||
- Add support for audio.layout.
|
||||
- Add multichannel support to ROC.
|
||||
|
||||
## SPA
|
||||
- Rework the maximum number of channel handling. Because this is a
|
||||
potential ABI break, it is now a compile time option with new
|
||||
functions to handle more than the previous 64 channels.
|
||||
- The 64 channel limit was removed from the noise shaper.
|
||||
- spa_strbuf is used in more places instead of custom snprintf code.
|
||||
- The volume ramp code was simplified.
|
||||
- The driver node now has properties to configure the clock.
|
||||
- The adapter will try to renegotiate when the driver changes.
|
||||
- Fix relaxed array parsing with od number of elements. (#4944)
|
||||
- audio.layout was added to set the channel positions to some
|
||||
predefined layouts.
|
||||
- Added more POD choice checks to ensure the right amount of values
|
||||
are present in the choice.
|
||||
- Fix __has_attribute usage. (#4962)
|
||||
- Thread RESET_ON_FORK is now disabled for JACK application so that
|
||||
forking will preserve any real-time thread priorities, like JACK.
|
||||
(#4966)
|
||||
- Fix some compilation issues. (#4960 and #4961).
|
||||
|
||||
## Pulse-server
|
||||
- Fix missing subscription events on device port changes.
|
||||
- Increase min.quantum to 256/48000. (#4875)
|
||||
|
||||
## GStreamer
|
||||
- Avoid overflow in clock time calculations.
|
||||
- Fix renegotiation.
|
||||
|
||||
## Docs
|
||||
- Swap the name and id of device.product
|
||||
|
||||
# PipeWire 1.5.81 (2025-10-16)
|
||||
|
||||
This is the first 1.6 release candidate that is API and ABI
|
||||
|
|
@ -196,9 +314,6 @@ also contains some new features:
|
|||
## Docs
|
||||
- Document the client-node flow a bit more.
|
||||
|
||||
|
||||
Older versions:
|
||||
|
||||
# PipeWire 1.4.0 (2025-03-06)
|
||||
|
||||
This is the 1.4 release that is API and ABI compatible with previous
|
||||
|
|
|
|||
|
|
@ -59,6 +59,14 @@ stream.properties = {
|
|||
#node.autoconnect = true
|
||||
#resample.disable = false
|
||||
#resample.quality = 4
|
||||
#resample.window = exp # blackman kaiser
|
||||
#resample.cutoff = 0.0
|
||||
#resample.n-taps = 0
|
||||
#resample.param.exp.A = 0.0
|
||||
#resample.param.blackman.alpha = 0.0
|
||||
#resample.param.kaiser.alpha = 0.0
|
||||
#resample.param.kaiser.stopband-attenuation = 0.0
|
||||
#resample.param.kaiser.transition-bandwidth = 0.0
|
||||
#monitor.channel-volumes = false
|
||||
#channelmix.disable = false
|
||||
#channelmix.min-volume = 0.0
|
||||
|
|
|
|||
|
|
@ -547,17 +547,137 @@ Below is an explanation of the options that can be tuned in the sample converter
|
|||
\parblock
|
||||
The quality of the resampler. from 0 to 14, the default is 4.
|
||||
|
||||
The quality of a resampler depends on multiple factors:
|
||||
|
||||
1. Anti-Aliasing, how well are unwanted frequencies filtered out. Poor anti-aliasing
|
||||
will make the original inaudible frequencies audible as distortion and noise.
|
||||
2. Cutoff frequence. At what frequency the transition band will start. This is the
|
||||
frequency where the signal will start to fade out. A too low cutoff might remove too
|
||||
much of the high frequencies and make the sound dull. A too high cutoff might cause
|
||||
aliasing. The cutoff frequency is usually expressed as a ratio of the Nyquist
|
||||
frequency, 1.0 being the Nyquist frequency (frequency/2).
|
||||
3. Transition band length. How quickly the unwanted frequencies are filtered out. A
|
||||
shorter transition band requires longer filters with more CPU and latency but
|
||||
causes less aliasing. The transition band length is expressed as a ratio of the
|
||||
Nyquist frequency.
|
||||
4. Stopband attenuation. How well the unwanted frequencies are filtered out. This is
|
||||
usually measured in dB. 96dB is below audidle on CD quality audio, 150dB is below
|
||||
the precision of floating point values.
|
||||
5. CPU usage. Better anti-aliasing needs longer filters and is therefore more CPU
|
||||
intensive.
|
||||
6. Latency. Longer filters have a higher Latency. In real-time application the latency
|
||||
should be kept as low as possible.
|
||||
7. Ringing. A too short transition band length might cause ringing because of how the
|
||||
sinc filters work. This can sound like flutter on sharp attacks in the audio signal.
|
||||
|
||||
Increasing the quality will result in better cutoff and less aliasing at the expense of
|
||||
(much) more CPU consumption. The default quality of 4 has been selected as a good compromise
|
||||
between quality and performance with no artifacts that are well below the audible range.
|
||||
(much) more CPU consumption and more ringing. The default quality of 4 has been selected
|
||||
as a good compromise between quality and performance with no artifacts that are well
|
||||
below the audible range.
|
||||
|
||||
The default resampler quality for the exp window results in a cutoff of 0.87 and a
|
||||
filter size of about 48 taps. It has a Stopband attenuation of about 150 dB.
|
||||
|
||||
See [Infinite Wave](https://src.infinitewave.ca/) for a comparison of the performance.
|
||||
|
||||
You can tune the resampler in a variaty of ways:
|
||||
|
||||
* Tune the cutoff frequency. Increase the cutoff to preserve more high frequencies at the
|
||||
expense of more aliasing.
|
||||
* Tune the transition band. Reduce the transition band to better filter out the frequencies
|
||||
around the cutoff frequency and get less aliasing at the expense of more ringing, CPU and
|
||||
Latency. This can be done by increasing the number of taps.
|
||||
* Tune the stopband attenuation. Increase the attenuation to reduce aliasing at the expense
|
||||
of a wider transition band. This can only be done on the kaiser window, the exp and
|
||||
blackman window have 150dB and 96dB attenuation respecively.
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.disable = false
|
||||
Disable the resampler entirely. The node will only be able to negotiate with the graph
|
||||
when the samplerates are compatible.
|
||||
|
||||
@PAR@ node-prop resample.window = exp
|
||||
\parblock
|
||||
The resampler window function to use. By default an exponential window function is used
|
||||
that gives a good balance between complexitiy and quality.
|
||||
|
||||
You can also specify a blackman or kaiser window, both with different tradeoffs. The
|
||||
kaiser window has some extra tunable parameters for the specific use cases.
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.cutoff = 0.0
|
||||
\parblock
|
||||
The resampler cutoff frequency. This is a value between 0.0 and 1.0. A value of 0.0 will
|
||||
use a predefined value based on the resampler quality.
|
||||
|
||||
A higher cutoff value will preserve more high frequency content but depending on the
|
||||
size of the transition band will cause more aliasing.
|
||||
|
||||
The default quality 4 setting for all windows is 0.87.
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.n-taps = 0
|
||||
\parblock
|
||||
The resampler number of taps. A value of 0 will use a predefined value based on
|
||||
the resampler quality or other window function parameters.
|
||||
|
||||
A higher number of taps will use more CPU, Latency and cause more ringing but will
|
||||
reduce aliasing.
|
||||
|
||||
The default quality setting for the exp window is 48.
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.param.exp.A = 0.0
|
||||
\parblock
|
||||
The A parameter for the exponential window function. A value of 0.0 will use a predefined
|
||||
value based on the quality when the exp window is in use.
|
||||
|
||||
The default setting for the exp window is 16.97789.
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.param.blackman.alpha = 0.0
|
||||
\parblock
|
||||
The alpha value of the blackman function. A value of 0.0 will use a predefined
|
||||
value based on the quality when the blackman window is in use.
|
||||
|
||||
The default quality setting for the blackman window is 0.16.
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.param.kaiser.stopband-attenuation = 0.0
|
||||
\parblock
|
||||
The kaiser window stopband attenuation parameter in dB. A default value of 0.0 will use a
|
||||
predefined value based on the quality.
|
||||
|
||||
A higher value will filter out more of the unwanted frequencies and reduce aliasing at the
|
||||
expense of a larger transition band. A value of 96dB is below the dynamic range of CD quality
|
||||
audio. 150dB is the limit of the precision of the resampler.
|
||||
|
||||
The default quality setting for the kaiser window is 130.000000.
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.param.kaiser.transition-bandwidth = 0.0
|
||||
\parblock
|
||||
The kaiser window transition bandwidth parameter. A default value of 0.0 will use a
|
||||
predefined value based on the quality.
|
||||
|
||||
A smaller transition band will cause a steeper cutoff with less unwanted frequencies
|
||||
in the final signal at the expense of more a larger filter and more CPU usage and
|
||||
latency. A smaller transition band can also cause more ringing.
|
||||
|
||||
The default quality setting for the kaiser window is 0.177032
|
||||
\endparblock
|
||||
|
||||
@PAR@ node-prop resample.param.kaiser.alpha = 0.0
|
||||
\parblock
|
||||
The kaiser window alpha parameter. A default value of 0.0 will calculate an alpha value
|
||||
based on the stopband-attenuation and transition-bandwidth parameters.
|
||||
|
||||
This value is usually calculated from the other parameters but can be set explicitly
|
||||
with this property.
|
||||
|
||||
The default quality setting for the kaiser window is 4.254931.
|
||||
\endparblock
|
||||
|
||||
## Channel Mixer Parameters
|
||||
|
||||
Source, sinks, capture and playback streams can apply channel mixing on the incoming signal.
|
||||
|
|
@ -1018,6 +1138,15 @@ HFP/HSP backend (default: native). Available values: any, none, hsphfpd, ofono,
|
|||
|
||||
@PAR@ monitor-prop bluez5.hfphsp-backend-native-modem # string
|
||||
|
||||
@PAR@ monitor-prop bluez5.hfphsp-backend-native-pts # boolean
|
||||
Enable specific workarounds for Bluetooth qualification.
|
||||
|
||||
@PAR@ monitor-prop bluez5.disable-dummy-call # boolean
|
||||
By default a call status event is sent on audio stream connection/disconnection to
|
||||
workaround some headset timeout disconnection when the HFP HF is used by another
|
||||
application than telephony one, e.g. a conference application/website.
|
||||
This prevent to send this event.
|
||||
|
||||
@PAR@ monitor-prop bluez5.dummy-avrcp player # boolean
|
||||
Register dummy AVRCP player. Some devices have wrongly functioning
|
||||
volume or playback controls if this is not enabled. Default: false
|
||||
|
|
@ -1137,6 +1266,18 @@ Available source contexts PACS bitmask of the the server.
|
|||
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer
|
||||
Supported source contexts PACS bitmask of the the server.
|
||||
|
||||
@PAR@ monitor-prop bluez5.bap-server-tmap-features = null # array of string
|
||||
Override advertised TMAP service features. See TMAP specification for their meaning.
|
||||
Possible values: "cg", "ct", "ums", "umr", "bms", "bmr".
|
||||
Default: none.
|
||||
|
||||
@PAR@ monitor-prop bluez5.bap-server-gmap-features = null # array of string
|
||||
Override advertised GMAP service features. See GMAP specification for their meaning.
|
||||
Possible values: "ugg", "ugt", "bgs", "bgr", "ugg-multiplex", "ugg-96kbps-source", "ugg-multisink",
|
||||
"ugt-source", "ugt-80kbps-source", "ugt-sink", "ugt-64kbps-sink", "ugt-multiplex", "ugt-multisink",
|
||||
"ugt-multisource", "bgs-96kbps", "bgr-multisink", "bgr-multiplex".
|
||||
Default: none.
|
||||
|
||||
## Device properties
|
||||
|
||||
@PAR@ device-prop bluez5.auto-connect # boolean
|
||||
|
|
|
|||
|
|
@ -1130,6 +1130,8 @@ follows:
|
|||
|<----------------------------------------|
|
||||
| ClientNode::PortSetMixInfo | mixer inputs for each linked port
|
||||
|<----------------------------------------|
|
||||
| ClientNode::PortSetParam | PeerCapability of the ports
|
||||
|<----------------------------------------|
|
||||
| ClientNode::PortSetParam | Latency of the ports
|
||||
|<----------------------------------------|
|
||||
| ClientNode::SetActivation | activation of port peers
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
project('pipewire', ['c' ],
|
||||
version : '1.5.81',
|
||||
version : '1.5.84',
|
||||
license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ],
|
||||
meson_version : '>= 0.61.1',
|
||||
default_options : [ 'warning_level=3',
|
||||
|
|
|
|||
304
po/sl.po
304
po/sl.po
|
|
@ -9,16 +9,16 @@ msgstr ""
|
|||
"Project-Id-Version: PipeWire master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
|
||||
"issues\n"
|
||||
"POT-Creation-Date: 2025-09-11 03:34+0000\n"
|
||||
"PO-Revision-Date: 2025-09-11 11:47+0200\n"
|
||||
"POT-Creation-Date: 2025-12-04 15:34+0000\n"
|
||||
"PO-Revision-Date: 2025-12-07 08:53+0100\n"
|
||||
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
||||
"Language-Team: Slovenian <gnome-si@googlegroups.com>\n"
|
||||
"Language: sl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && "
|
||||
"n%100<=4 ? 2 : 3);\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n"
|
||||
"%100<=4 ? 2 : 3);\n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
|
||||
#: src/daemon/pipewire.c:29
|
||||
|
|
@ -57,7 +57,7 @@ msgstr "Prehod do %s%s%s"
|
|||
msgid "Dummy Output"
|
||||
msgstr "Lažni izhod"
|
||||
|
||||
#: src/modules/module-pulse-tunnel.c:760
|
||||
#: src/modules/module-pulse-tunnel.c:761
|
||||
#, c-format
|
||||
msgid "Tunnel for %s@%s"
|
||||
msgstr "Prehod za %s@%s"
|
||||
|
|
@ -76,7 +76,7 @@ msgstr "%s na %s@%s"
|
|||
msgid "%s on %s"
|
||||
msgstr "%s na %s"
|
||||
|
||||
#: src/tools/pw-cat.c:1084
|
||||
#: src/tools/pw-cat.c:1103
|
||||
#, c-format
|
||||
msgid ""
|
||||
"%s [options] [<file>|-]\n"
|
||||
|
|
@ -92,7 +92,7 @@ msgstr ""
|
|||
"\n"
|
||||
"</file>\n"
|
||||
|
||||
#: src/tools/pw-cat.c:1091
|
||||
#: src/tools/pw-cat.c:1110
|
||||
#, c-format
|
||||
msgid ""
|
||||
" -R, --remote Remote daemon name\n"
|
||||
|
|
@ -129,7 +129,7 @@ msgstr ""
|
|||
" -P --properties Nastavi lastnosti vozlišča\n"
|
||||
"\n"
|
||||
|
||||
#: src/tools/pw-cat.c:1109
|
||||
#: src/tools/pw-cat.c:1128
|
||||
#, c-format
|
||||
msgid ""
|
||||
" --rate Sample rate (req. for rec) (default "
|
||||
|
|
@ -137,8 +137,8 @@ msgid ""
|
|||
" --channels Number of channels (req. for rec) "
|
||||
"(default %u)\n"
|
||||
" --channel-map Channel map\n"
|
||||
" one of: \"stereo\", "
|
||||
"\"surround-51\",... or\n"
|
||||
" one of: \"Stereo\", \"5.1\",... "
|
||||
"or\n"
|
||||
" comma separated list of channel "
|
||||
"names: eg. \"FL,FR\"\n"
|
||||
" --format Sample format %s (req. for rec) "
|
||||
|
|
@ -157,8 +157,8 @@ msgstr ""
|
|||
" --channels Število kanalov (zaht. za snemanje) "
|
||||
"(privzeto %u)\n"
|
||||
" --channel-map Preslikava kanalov\n"
|
||||
" Ena izmed: \"stereo\", "
|
||||
"\"surround-51\",... ali\n"
|
||||
" Ena izmed: \"Stereo\", "
|
||||
"\"5.1\",... ali\n"
|
||||
" seznam imen kanalov, ločen z "
|
||||
"vejico: npr. \"FL,FR\"\n"
|
||||
" --format Vzorčne oblike zapisa %s (zahtevano "
|
||||
|
|
@ -167,12 +167,12 @@ msgstr ""
|
|||
" -q --quality Kakovost prevzorčenja (0 - 15) "
|
||||
"(privzeto %d)\n"
|
||||
" -a, --raw neobdelan način (RAW)\n"
|
||||
" -M, --force-midi Vsili zapis midi, eden izmed "
|
||||
"\"midi\" ali \"ump\" (privzeto ump)\n"
|
||||
" -M, --force-midi Vsili zapis midi, eden izmed \"midi"
|
||||
"\" ali \"ump\" (privzeto ump)\n"
|
||||
" -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n"
|
||||
"\n"
|
||||
|
||||
#: src/tools/pw-cat.c:1129
|
||||
#: src/tools/pw-cat.c:1148
|
||||
msgid ""
|
||||
" -p, --playback Playback mode\n"
|
||||
" -r, --record Recording mode\n"
|
||||
|
|
@ -212,203 +212,203 @@ msgstr ""
|
|||
" -m, --monitor Spremljaj dejavnosti\n"
|
||||
"\n"
|
||||
|
||||
#: spa/plugins/alsa/acp/acp.c:351
|
||||
#: spa/plugins/alsa/acp/acp.c:361
|
||||
msgid "Pro Audio"
|
||||
msgstr "Profesionalni zvok"
|
||||
|
||||
#: spa/plugins/alsa/acp/acp.c:527 spa/plugins/alsa/acp/alsa-mixer.c:4635
|
||||
#: spa/plugins/bluez5/bluez5-device.c:1974
|
||||
#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699
|
||||
#: spa/plugins/bluez5/bluez5-device.c:1976
|
||||
msgid "Off"
|
||||
msgstr "Izklopljeno"
|
||||
|
||||
#: spa/plugins/alsa/acp/acp.c:610
|
||||
#: spa/plugins/alsa/acp/acp.c:620
|
||||
#, c-format
|
||||
msgid "%s [ALSA UCM error]"
|
||||
msgstr "%s [napaka ALSA UCM]"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2652
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2721
|
||||
msgid "Input"
|
||||
msgstr "Vhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2653
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2722
|
||||
msgid "Docking Station Input"
|
||||
msgstr "Vhod priklopne postaje"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2654
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2723
|
||||
msgid "Docking Station Microphone"
|
||||
msgstr "Mikrofon priklopne postaje"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2655
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2724
|
||||
msgid "Docking Station Line In"
|
||||
msgstr "Linijski vhod priklopne postaje"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2656
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2747
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2725
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2816
|
||||
msgid "Line In"
|
||||
msgstr "Linijski vhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2657
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2372
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2726
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2810
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2374
|
||||
msgid "Microphone"
|
||||
msgstr "Mikrofon"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2658
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2727
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2811
|
||||
msgid "Front Microphone"
|
||||
msgstr "Sprednji mikrofon"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2659
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2743
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2728
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2812
|
||||
msgid "Rear Microphone"
|
||||
msgstr "Zadnji mikrofon"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2660
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2729
|
||||
msgid "External Microphone"
|
||||
msgstr "Zunanji mikrofon"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2661
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2745
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2730
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2814
|
||||
msgid "Internal Microphone"
|
||||
msgstr "Notranji mikrofon"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2662
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2748
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2731
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2817
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2663
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2749
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2732
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2818
|
||||
msgid "Video"
|
||||
msgstr "Video"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2664
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2733
|
||||
msgid "Automatic Gain Control"
|
||||
msgstr "Samodejni nadzor ojačanja"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2665
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2734
|
||||
msgid "No Automatic Gain Control"
|
||||
msgstr "Brez samodejnega nadzora ojačanja"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2666
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2735
|
||||
msgid "Boost"
|
||||
msgstr "Ojačitev"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2667
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2736
|
||||
msgid "No Boost"
|
||||
msgstr "Brez ojačitve"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2668
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2737
|
||||
msgid "Amplifier"
|
||||
msgstr "Ojačevalnik"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2669
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2738
|
||||
msgid "No Amplifier"
|
||||
msgstr "Brez ojačevalnika"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2670
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2739
|
||||
msgid "Bass Boost"
|
||||
msgstr "Ojačitev nizkih tonov"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2671
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
|
||||
msgid "No Bass Boost"
|
||||
msgstr "Brez ojačitve nizkih tonov"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2672
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2378
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2380
|
||||
msgid "Speaker"
|
||||
msgstr "Zvočnik"
|
||||
|
||||
#. Don't call it "headset", the HF one has the mic
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2673
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2751
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2384
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2451
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2820
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2386
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2453
|
||||
msgid "Headphones"
|
||||
msgstr "Slušalke"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2809
|
||||
msgid "Analog Input"
|
||||
msgstr "Analogni vhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2744
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2813
|
||||
msgid "Dock Microphone"
|
||||
msgstr "Priklopni mikrofon"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2746
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2815
|
||||
msgid "Headset Microphone"
|
||||
msgstr "Mikrofon s slušalkami"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2750
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2819
|
||||
msgid "Analog Output"
|
||||
msgstr "Analogni izhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2752
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2821
|
||||
msgid "Headphones 2"
|
||||
msgstr "Slušalke 2"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2753
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2822
|
||||
msgid "Headphones Mono Output"
|
||||
msgstr "Mono izhod slušalk"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2754
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2823
|
||||
msgid "Line Out"
|
||||
msgstr "Linijsk izhod"
|
||||
msgstr "Linijski izhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2755
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2824
|
||||
msgid "Analog Mono Output"
|
||||
msgstr "Analogni mono izhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2756
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2825
|
||||
msgid "Speakers"
|
||||
msgstr "Govorniki"
|
||||
msgstr "Zvočniki"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2757
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2826
|
||||
msgid "HDMI / DisplayPort"
|
||||
msgstr "HDMI / DisplayPort"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2758
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2827
|
||||
msgid "Digital Output (S/PDIF)"
|
||||
msgstr "Digitalni izhod (S/PDIF)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2759
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2828
|
||||
msgid "Digital Input (S/PDIF)"
|
||||
msgstr "Digitalni vhod (S/PDIF)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2760
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2829
|
||||
msgid "Multichannel Input"
|
||||
msgstr "Večkanalni vhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2761
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2830
|
||||
msgid "Multichannel Output"
|
||||
msgstr "Večkanalni izhod"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2762
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2831
|
||||
msgid "Game Output"
|
||||
msgstr "Vhod igre"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2763
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2764
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2832
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2833
|
||||
msgid "Chat Output"
|
||||
msgstr "Izhod klepeta"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2765
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2834
|
||||
msgid "Chat Input"
|
||||
msgstr "Vhod klepeta"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2766
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2835
|
||||
msgid "Virtual Surround 7.1"
|
||||
msgstr "Navidezni prostorski zvok 7.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4458
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4522
|
||||
msgid "Analog Mono"
|
||||
msgstr "Analogni mono"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4459
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4523
|
||||
msgid "Analog Mono (Left)"
|
||||
msgstr "Analogni mono (levo)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4460
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4524
|
||||
msgid "Analog Mono (Right)"
|
||||
msgstr "Analogni mono (desno)"
|
||||
|
||||
|
|
@ -417,142 +417,142 @@ msgstr "Analogni mono (desno)"
|
|||
#. * here would lead to the source name to become "Analog Stereo Input
|
||||
#. * Input". The same logic applies to analog-stereo-output,
|
||||
#. * multichannel-input and multichannel-output.
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4461
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4469
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4470
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4525
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4533
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4534
|
||||
msgid "Analog Stereo"
|
||||
msgstr "Analogni stereo"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4462
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4526
|
||||
msgid "Mono"
|
||||
msgstr "Mono"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4463
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4527
|
||||
msgid "Stereo"
|
||||
msgstr "Stereo"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4471
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4629
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2360
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4535
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4693
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2362
|
||||
msgid "Headset"
|
||||
msgstr "Slušalka"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4472
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4630
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4536
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4694
|
||||
msgid "Speakerphone"
|
||||
msgstr "Zvočnik telefona"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4473
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4474
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4537
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4538
|
||||
msgid "Multichannel"
|
||||
msgstr "Večkanalno"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4475
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4539
|
||||
msgid "Analog Surround 2.1"
|
||||
msgstr "Analogni prostorski zvok 2.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4476
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4540
|
||||
msgid "Analog Surround 3.0"
|
||||
msgstr "Analogni prostorski zvok 3.0"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4477
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4541
|
||||
msgid "Analog Surround 3.1"
|
||||
msgstr "Analogni prostorski zvok 3.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4478
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4542
|
||||
msgid "Analog Surround 4.0"
|
||||
msgstr "Analogni prostorski zvok 4.0"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4479
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4543
|
||||
msgid "Analog Surround 4.1"
|
||||
msgstr "Analogni prostorski zvok 4.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4480
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4544
|
||||
msgid "Analog Surround 5.0"
|
||||
msgstr "Analogni prostorski zvok 5.0"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4481
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4545
|
||||
msgid "Analog Surround 5.1"
|
||||
msgstr "Analogni prostorski zvok 5.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4482
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4546
|
||||
msgid "Analog Surround 6.0"
|
||||
msgstr "Analogni prostorski zvok 6.0"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4483
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4547
|
||||
msgid "Analog Surround 6.1"
|
||||
msgstr "Analogni prostorski zvok 6.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4484
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4548
|
||||
msgid "Analog Surround 7.0"
|
||||
msgstr "Analogni prostorski zvok 7.0"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4485
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4549
|
||||
msgid "Analog Surround 7.1"
|
||||
msgstr "Analogni prostorski zvok 7.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4486
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4550
|
||||
msgid "Digital Stereo (IEC958)"
|
||||
msgstr "Digitalni stereo (IEC958)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4487
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4551
|
||||
msgid "Digital Surround 4.0 (IEC958/AC3)"
|
||||
msgstr "Digitalni prostorski zvok 4.0 (IEC958/AC3)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4488
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4552
|
||||
msgid "Digital Surround 5.1 (IEC958/AC3)"
|
||||
msgstr "Digitalni prostorski zvok 5.1 (IEC958/AC3)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4489
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4553
|
||||
msgid "Digital Surround 5.1 (IEC958/DTS)"
|
||||
msgstr "Digitalni prostorski zvok 5.1 (IEC958/DTS)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4490
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4554
|
||||
msgid "Digital Stereo (HDMI)"
|
||||
msgstr "Digitalni stereo (HDMI)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4491
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4555
|
||||
msgid "Digital Surround 5.1 (HDMI)"
|
||||
msgstr "Digitalni prostorski zvok 5.1 (HDMI)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4492
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4556
|
||||
msgid "Chat"
|
||||
msgstr "Klepet"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4493
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4557
|
||||
msgid "Game"
|
||||
msgstr "Igra"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4627
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4691
|
||||
msgid "Analog Mono Duplex"
|
||||
msgstr "Analogni mono dupleks"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4628
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4692
|
||||
msgid "Analog Stereo Duplex"
|
||||
msgstr "Analogni stereo dupleks"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4631
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4695
|
||||
msgid "Digital Stereo Duplex (IEC958)"
|
||||
msgstr "Digitalni stereo dupleks (IEC958)"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4632
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4696
|
||||
msgid "Multichannel Duplex"
|
||||
msgstr "Večkanalni dupleks"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4633
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4697
|
||||
msgid "Stereo Duplex"
|
||||
msgstr "Stereo dupleks"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4634
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4698
|
||||
msgid "Mono Chat + 7.1 Surround"
|
||||
msgstr "Mono klepet + prostorski zvok 7.1"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4735
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4799
|
||||
#, c-format
|
||||
msgid "%s Output"
|
||||
msgstr "Izhod %s"
|
||||
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4743
|
||||
#: spa/plugins/alsa/acp/alsa-mixer.c:4807
|
||||
#, c-format
|
||||
msgid "%s Input"
|
||||
msgstr "Vhod %s"
|
||||
|
|
@ -592,13 +592,13 @@ msgstr[3] ""
|
|||
#: spa/plugins/alsa/acp/alsa-util.c:1299
|
||||
#, c-format
|
||||
msgid ""
|
||||
"snd_pcm_delay() returned a value that is exceptionally large: %li byte "
|
||||
"(%s%lu ms).\n"
|
||||
"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s"
|
||||
"%lu ms).\n"
|
||||
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
|
||||
"to the ALSA developers."
|
||||
msgid_plural ""
|
||||
"snd_pcm_delay() returned a value that is exceptionally large: %li bytes "
|
||||
"(%s%lu ms).\n"
|
||||
"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s"
|
||||
"%lu ms).\n"
|
||||
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
|
||||
"to the ALSA developers."
|
||||
msgstr[0] ""
|
||||
|
|
@ -668,7 +668,7 @@ msgstr[3] ""
|
|||
"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite "
|
||||
"razvijalce ALSA."
|
||||
|
||||
#: spa/plugins/alsa/acp/channelmap.h:457
|
||||
#: spa/plugins/alsa/acp/channelmap.h:460
|
||||
msgid "(invalid)"
|
||||
msgstr "(neveljavno)"
|
||||
|
||||
|
|
@ -680,100 +680,100 @@ msgstr "Vgrajen zvok"
|
|||
msgid "Modem"
|
||||
msgstr "Modem"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:1985
|
||||
#: spa/plugins/bluez5/bluez5-device.c:1987
|
||||
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
|
||||
msgstr "Zvožni prehod (vir A2DP in HSP/HFP AG)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2014
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2016
|
||||
msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
|
||||
msgstr "Pretakanje zvoka za slušne aparate (ponor ASHA)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2057
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2059
|
||||
#, c-format
|
||||
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
|
||||
msgstr "Predvajanje visoke ločljivosti (ponor A2DP, kodek %s)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2060
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2062
|
||||
#, c-format
|
||||
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
|
||||
msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP, kodek %s)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2068
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2070
|
||||
msgid "High Fidelity Playback (A2DP Sink)"
|
||||
msgstr "Predvajanje visoke ločljivosti (ponor A2DP)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2070
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2072
|
||||
msgid "High Fidelity Duplex (A2DP Source/Sink)"
|
||||
msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2144
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2146
|
||||
#, c-format
|
||||
msgid "High Fidelity Playback (BAP Sink, codec %s)"
|
||||
msgstr "Predvajanje visoke ločljivosti (ponor BAP, kodek %s)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2149
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2151
|
||||
#, c-format
|
||||
msgid "High Fidelity Input (BAP Source, codec %s)"
|
||||
msgstr "Vhod visoke ločljivosti (vir BAP, kodek %s)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2153
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2155
|
||||
#, c-format
|
||||
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
|
||||
msgstr "Dupleks visoke ločljivosti (vir/ponor BAP, kodek %s)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2162
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2164
|
||||
msgid "High Fidelity Playback (BAP Sink)"
|
||||
msgstr "Predvajanje visoke ločljivosti (ponor BAP)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2166
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2168
|
||||
msgid "High Fidelity Input (BAP Source)"
|
||||
msgstr "Vhod visoke ločljivosti (vir BAP)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2169
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2171
|
||||
msgid "High Fidelity Duplex (BAP Source/Sink)"
|
||||
msgstr "Dupleks visoke ločljivosti (vir/ponor BAP)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2209
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2211
|
||||
#, c-format
|
||||
msgid "Headset Head Unit (HSP/HFP, codec %s)"
|
||||
msgstr "Naglavna enota slušalk (HSP/HFP, kodek %s)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2361
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2366
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2373
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2379
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2385
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2391
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2397
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2403
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2409
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2363
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2368
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2375
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2381
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2387
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2393
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2399
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2405
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2411
|
||||
msgid "Handsfree"
|
||||
msgstr "Prostoročno telefoniranje"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2367
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2369
|
||||
msgid "Handsfree (HFP)"
|
||||
msgstr "Prostoročno telefoniranje (HFP)"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2390
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2392
|
||||
msgid "Portable"
|
||||
msgstr "Prenosna naprava"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2396
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2398
|
||||
msgid "Car"
|
||||
msgstr "Avtomobil"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2402
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2404
|
||||
msgid "HiFi"
|
||||
msgstr "HiFi"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2408
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2410
|
||||
msgid "Phone"
|
||||
msgstr "Telefon"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2415
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2417
|
||||
msgid "Bluetooth"
|
||||
msgstr "Bluetooth"
|
||||
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2416
|
||||
msgid "Bluetooth (HFP)"
|
||||
msgstr "Bluetooth (HFP)"
|
||||
#: spa/plugins/bluez5/bluez5-device.c:2418
|
||||
msgid "Bluetooth Handsfree"
|
||||
msgstr "Bluetooth - prostoročno"
|
||||
|
|
|
|||
22
po/zh_CN.po
22
po/zh_CN.po
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"Project-Id-Version: pipewire.master-tx\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
|
||||
"issues\n"
|
||||
"POT-Creation-Date: 2025-11-04 15:35+0000\n"
|
||||
"PO-Revision-Date: 2025-11-05 07:47+0800\n"
|
||||
"POT-Creation-Date: 2025-11-25 15:35+0000\n"
|
||||
"PO-Revision-Date: 2025-11-26 10:19+0800\n"
|
||||
"Last-Translator: lumingzh <lumingzh@qq.com>\n"
|
||||
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
|
||||
"Language: zh_CN\n"
|
||||
|
|
@ -79,7 +79,7 @@ msgstr "%2$s@%3$s 上的 %1$s"
|
|||
msgid "%s on %s"
|
||||
msgstr "%2$s 上的 %1$s"
|
||||
|
||||
#: src/tools/pw-cat.c:1096
|
||||
#: src/tools/pw-cat.c:1088
|
||||
#, c-format
|
||||
msgid ""
|
||||
"%s [options] [<file>|-]\n"
|
||||
|
|
@ -94,7 +94,7 @@ msgstr ""
|
|||
" -v, --verbose 输出详细操作\n"
|
||||
"\n"
|
||||
|
||||
#: src/tools/pw-cat.c:1103
|
||||
#: src/tools/pw-cat.c:1095
|
||||
#, c-format
|
||||
msgid ""
|
||||
" -R, --remote Remote daemon name\n"
|
||||
|
|
@ -126,7 +126,7 @@ msgstr ""
|
|||
" -P --properties 设置节点属性\n"
|
||||
"\n"
|
||||
|
||||
#: src/tools/pw-cat.c:1121
|
||||
#: src/tools/pw-cat.c:1113
|
||||
#, c-format
|
||||
msgid ""
|
||||
" --rate Sample rate (req. for rec) (default "
|
||||
|
|
@ -134,8 +134,8 @@ msgid ""
|
|||
" --channels Number of channels (req. for rec) "
|
||||
"(default %u)\n"
|
||||
" --channel-map Channel map\n"
|
||||
" one of: \"stereo\", "
|
||||
"\"surround-51\",... or\n"
|
||||
" one of: \"Stereo\", \"5.1\",... "
|
||||
"or\n"
|
||||
" comma separated list of channel "
|
||||
"names: eg. \"FL,FR\"\n"
|
||||
" --format Sample format %s (req. for rec) "
|
||||
|
|
@ -152,9 +152,9 @@ msgstr ""
|
|||
" --rate 采样率 (录制模式需要) (默认 %u)\n"
|
||||
" --channels 通道数 (录制模式需要) (默认 %u)\n"
|
||||
" --channel-map 通道映射\n"
|
||||
" \"stereo\", \"surround-51\",... "
|
||||
"中的其一或\n"
|
||||
" 以\",\"分隔的通道名列表: 如 "
|
||||
" \"stereo\", \"5.1\",... 中的其一"
|
||||
"或\n"
|
||||
" 以英文逗号分隔的通道名列表: 如 "
|
||||
"\"FL,FR\"\n"
|
||||
" --format 采样格式 %s (录制模式需要) (默认 "
|
||||
"%s)\n"
|
||||
|
|
@ -166,7 +166,7 @@ msgstr ""
|
|||
" -n, --sample-count COUNT 计数采样后停止\n"
|
||||
"\n"
|
||||
|
||||
#: src/tools/pw-cat.c:1141
|
||||
#: src/tools/pw-cat.c:1133
|
||||
msgid ""
|
||||
" -p, --playback Playback mode\n"
|
||||
" -r, --record Recording mode\n"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d
|
|||
{
|
||||
struct spa_pod *position = NULL;
|
||||
int res;
|
||||
uint32_t max_position = SPA_N_ELEMENTS(info->position);
|
||||
|
||||
info->flags = 0;
|
||||
res = spa_pod_parse_object(format,
|
||||
SPA_TYPE_OBJECT_Format, NULL,
|
||||
|
|
@ -41,10 +43,13 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d
|
|||
SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate),
|
||||
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels),
|
||||
SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position));
|
||||
if (info->channels > max_position)
|
||||
return -ECHRNG;
|
||||
if (position == NULL ||
|
||||
!spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_N_ELEMENTS(info->position)))
|
||||
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) {
|
||||
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
|
||||
|
||||
spa_memzero(info->position, max_position * sizeof(info->position[0]));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ static const struct spa_type_audio_layout_info {
|
|||
{ "7.1", { SPA_AUDIO_LAYOUT_7_1 } },
|
||||
{ "7.1W", { SPA_AUDIO_LAYOUT_7_1W } },
|
||||
{ "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } },
|
||||
{ NULL, },
|
||||
{ NULL, { 0, { SPA_AUDIO_CHANNEL_UNKNOWN } } },
|
||||
};
|
||||
|
||||
SPA_API_AUDIO_LAYOUT_TYPES int
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in
|
|||
if (info->channels > max_position)
|
||||
return -ECHRNG;
|
||||
if (position == NULL ||
|
||||
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels)
|
||||
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) {
|
||||
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
|
||||
spa_memzero(info->position, max_position * sizeof(info->position[0]));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
38
spa/include/spa/param/dict-types.h
Normal file
38
spa/include/spa/param/dict-types.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* Simple Plugin API */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef SPA_PARAM_DICT_TYPES_H
|
||||
#define SPA_PARAM_DICT_TYPES_H
|
||||
|
||||
#include <spa/utils/enum-types.h>
|
||||
#include <spa/param/param-types.h>
|
||||
#include <spa/param/dict.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup spa_param
|
||||
* \{
|
||||
*/
|
||||
|
||||
#define SPA_TYPE_INFO_PARAM_Dict SPA_TYPE_INFO_PARAM_BASE "Dict"
|
||||
#define SPA_TYPE_INFO_PARAM_DICT_BASE SPA_TYPE_INFO_PARAM_Dict ":"
|
||||
|
||||
static const struct spa_type_info spa_type_param_dict[] = {
|
||||
{ SPA_PARAM_DICT_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_DICT_BASE, spa_type_param, },
|
||||
{ SPA_PARAM_DICT_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_DICT_BASE "info", NULL, },
|
||||
{ 0, 0, NULL, NULL },
|
||||
};
|
||||
|
||||
/**
|
||||
* \}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_PARAM_DICT_TYPES_H */
|
||||
125
spa/include/spa/param/dict-utils.h
Normal file
125
spa/include/spa/param/dict-utils.h
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/* Simple Plugin API */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef SPA_PARAM_DICT_UTILS_H
|
||||
#define SPA_PARAM_DICT_UTILS_H
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include <spa/utils/dict.h>
|
||||
#include <spa/pod/builder.h>
|
||||
#include <spa/pod/iter.h>
|
||||
#include <spa/pod/parser.h>
|
||||
#include <spa/pod/compare.h>
|
||||
#include <spa/param/dict.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup spa_param
|
||||
* \{
|
||||
*/
|
||||
|
||||
#ifndef SPA_API_DICT_UTILS
|
||||
#ifdef SPA_API_IMPL
|
||||
#define SPA_API_DICT_UTILS SPA_API_IMPL
|
||||
#else
|
||||
#define SPA_API_DICT_UTILS static inline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SPA_API_DICT_UTILS int
|
||||
spa_param_dict_compare(const struct spa_pod *a, const struct spa_pod *b)
|
||||
{
|
||||
return spa_pod_memcmp(a, b);
|
||||
}
|
||||
|
||||
SPA_API_DICT_UTILS struct spa_pod *
|
||||
spa_param_dict_build_dict(struct spa_pod_builder *builder, uint32_t id, struct spa_dict *dict)
|
||||
{
|
||||
struct spa_pod_frame f[2];
|
||||
uint32_t i, n_items;
|
||||
|
||||
spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_ParamDict, id);
|
||||
|
||||
n_items = dict ? dict->n_items : 0;
|
||||
|
||||
spa_pod_builder_prop(builder, SPA_PARAM_DICT_info, SPA_POD_PROP_FLAG_HINT_DICT);
|
||||
spa_pod_builder_push_struct(builder, &f[1]);
|
||||
spa_pod_builder_int(builder, n_items);
|
||||
for (i = 0; i < n_items; i++) {
|
||||
spa_pod_builder_string(builder, dict->items[i].key);
|
||||
spa_pod_builder_string(builder, dict->items[i].value);
|
||||
}
|
||||
spa_pod_builder_pop(builder, &f[1]);
|
||||
|
||||
return (struct spa_pod*)spa_pod_builder_pop(builder, &f[0]);
|
||||
}
|
||||
|
||||
SPA_API_DICT_UTILS struct spa_pod *
|
||||
spa_param_dict_build_info(struct spa_pod_builder *builder, uint32_t id, struct spa_param_dict_info *info)
|
||||
{
|
||||
struct spa_pod_frame f;
|
||||
spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_ParamDict, id);
|
||||
spa_pod_builder_add(builder, SPA_PARAM_DICT_info, SPA_POD_PROP_FLAG_HINT_DICT);
|
||||
spa_pod_builder_primitive(builder, info->info);
|
||||
return (struct spa_pod*)spa_pod_builder_pop(builder, &f);
|
||||
}
|
||||
|
||||
SPA_API_DICT_UTILS int
|
||||
spa_param_dict_parse(const struct spa_pod *dict, struct spa_param_dict_info *info, size_t size)
|
||||
{
|
||||
memset(info, 0, size);
|
||||
return spa_pod_parse_object(dict,
|
||||
SPA_TYPE_OBJECT_ParamDict, NULL,
|
||||
SPA_PARAM_DICT_info, SPA_POD_PodStruct(&info->info));
|
||||
}
|
||||
|
||||
SPA_API_DICT_UTILS int
|
||||
spa_param_dict_info_parse(const struct spa_param_dict_info *info, size_t size,
|
||||
struct spa_dict *dict, struct spa_dict_item *items)
|
||||
{
|
||||
struct spa_pod_parser prs;
|
||||
uint32_t n, n_items;
|
||||
const char *key, *value;
|
||||
struct spa_pod_frame f[1];
|
||||
|
||||
spa_pod_parser_pod(&prs, info->info);
|
||||
if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
|
||||
spa_pod_parser_get_int(&prs, (int32_t*)&n_items) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (items == NULL) {
|
||||
dict->n_items = n_items;
|
||||
return 0;
|
||||
}
|
||||
n_items = SPA_MIN(dict->n_items, n_items);
|
||||
|
||||
for (n = 0; n < n_items; n++) {
|
||||
if (spa_pod_parser_get(&prs,
|
||||
SPA_POD_String(&key),
|
||||
SPA_POD_String(&value),
|
||||
NULL) < 0)
|
||||
break;
|
||||
if (key == NULL || value == NULL)
|
||||
return -EINVAL;
|
||||
items[n].key = key;
|
||||
items[n].value = value;
|
||||
}
|
||||
dict->items = items;
|
||||
spa_pod_parser_pop(&prs, &f[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_PARAM_DICT_UTILS_H */
|
||||
42
spa/include/spa/param/dict.h
Normal file
42
spa/include/spa/param/dict.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* Simple Plugin API */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef SPA_PARAM_DICT_H
|
||||
#define SPA_PARAM_DICT_H
|
||||
|
||||
#include <spa/param/param.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup spa_param
|
||||
* \{
|
||||
*/
|
||||
|
||||
/** properties for SPA_TYPE_OBJECT_ParamDict */
|
||||
enum spa_param_dict {
|
||||
SPA_PARAM_DICT_START,
|
||||
SPA_PARAM_DICT_info, /**< Struct(
|
||||
* Int: n_items
|
||||
* (String: key
|
||||
* String: value)*
|
||||
* ) */
|
||||
};
|
||||
|
||||
/** helper structure for managing info objects */
|
||||
struct spa_param_dict_info {
|
||||
const struct spa_pod *info;
|
||||
};
|
||||
|
||||
/**
|
||||
* \}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_PARAM_DICT_H */
|
||||
|
|
@ -41,7 +41,9 @@ static const struct spa_type_info spa_type_param[] = {
|
|||
{ SPA_PARAM_Latency, SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_INFO_PARAM_ID_BASE "Latency", NULL },
|
||||
{ SPA_PARAM_ProcessLatency, SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_INFO_PARAM_ID_BASE "ProcessLatency", NULL },
|
||||
{ SPA_PARAM_Tag, SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_INFO_PARAM_ID_BASE "Tag", NULL },
|
||||
{ SPA_PARAM_PeerFormats, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_ID_BASE "PeerFormats", NULL },
|
||||
{ SPA_PARAM_PeerEnumFormat, SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_INFO_PARAM_ID_BASE "PeerEnumFormat", NULL },
|
||||
{ SPA_PARAM_Capability, SPA_TYPE_OBJECT_ParamDict, SPA_TYPE_INFO_PARAM_ID_BASE "Capability", NULL },
|
||||
{ SPA_PARAM_PeerCapability, SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_INFO_PARAM_ID_BASE "PeerCapability", NULL },
|
||||
{ 0, 0, NULL, NULL },
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@ enum spa_param_type {
|
|||
SPA_PARAM_Latency, /**< latency reporting, a SPA_TYPE_OBJECT_ParamLatency */
|
||||
SPA_PARAM_ProcessLatency, /**< processing latency, a SPA_TYPE_OBJECT_ParamProcessLatency */
|
||||
SPA_PARAM_Tag, /**< tag reporting, a SPA_TYPE_OBJECT_ParamTag. Since 0.3.79 */
|
||||
SPA_PARAM_PeerFormats, /**< peer formats, a SPA_TYPE_Struct of SPA_TYPE_OBJECT_Format. Since 1.5.0 */
|
||||
SPA_PARAM_PeerEnumFormat, /**< peer formats, a SPA_TYPE_OBJECT_PeerParam with
|
||||
* SPA_TYPE_OBJECT_Format. Since 1.5.0 */
|
||||
SPA_PARAM_Capability, /**< capability info, a SPA_TYPE_OBJECT_ParamDict, Since 1.5.84 */
|
||||
SPA_PARAM_PeerCapability, /**< peer capabilities, a SPA_TYPE_OBJECT_PeerParam with
|
||||
* SPA_TYPE_OBJECT_ParamDict, since 1.5.84 */
|
||||
};
|
||||
|
||||
/** information about a parameter */
|
||||
|
|
|
|||
38
spa/include/spa/param/peer-types.h
Normal file
38
spa/include/spa/param/peer-types.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* Simple Plugin API */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef SPA_PARAM_PEER_TYPES_H
|
||||
#define SPA_PARAM_PEER_TYPES_H
|
||||
|
||||
#include <spa/utils/enum-types.h>
|
||||
#include <spa/param/param-types.h>
|
||||
#include <spa/param/peer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup spa_param
|
||||
* \{
|
||||
*/
|
||||
|
||||
#define SPA_TYPE_INFO_PeerParam SPA_TYPE_INFO_OBJECT_BASE "PeerParam"
|
||||
#define SPA_TYPE_INFO_PEER_PARAM_BASE SPA_TYPE_INFO_PeerParam ":"
|
||||
|
||||
static const struct spa_type_info spa_type_peer_param[] = {
|
||||
{ SPA_PEER_PARAM_START, SPA_TYPE_Id, SPA_TYPE_INFO_PEER_PARAM_BASE, spa_type_param, },
|
||||
{ SPA_ID_INVALID, SPA_TYPE_Id, SPA_TYPE_INFO_PEER_PARAM_BASE, NULL, },
|
||||
{ 0, 0, NULL, NULL },
|
||||
};
|
||||
|
||||
/**
|
||||
* \}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_PARAM_PEER_TYPES_H */
|
||||
92
spa/include/spa/param/peer-utils.h
Normal file
92
spa/include/spa/param/peer-utils.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/* Simple Plugin API */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef SPA_PARAM_PEER_PARAM_UTILS_H
|
||||
#define SPA_PARAM_PEER_PARAM_UTILS_H
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include <spa/utils/dict.h>
|
||||
#include <spa/pod/builder.h>
|
||||
#include <spa/pod/iter.h>
|
||||
#include <spa/pod/parser.h>
|
||||
#include <spa/pod/compare.h>
|
||||
#include <spa/param/peer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup spa_param
|
||||
* \{
|
||||
*/
|
||||
|
||||
#ifndef SPA_API_PEER_PARAM_UTILS
|
||||
#ifdef SPA_API_IMPL
|
||||
#define SPA_API_PEER_PARAM_UTILS SPA_API_IMPL
|
||||
#else
|
||||
#define SPA_API_PEER_PARAM_UTILS static inline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SPA_API_PEER_PARAM_UTILS int
|
||||
spa_peer_param_parse(const struct spa_pod *param, struct spa_peer_param_info *info,
|
||||
size_t size, void **state)
|
||||
{
|
||||
int res;
|
||||
const struct spa_pod_object *obj = (const struct spa_pod_object*)param;
|
||||
const struct spa_pod_prop *first, *start, *cur;
|
||||
|
||||
if ((res = spa_pod_parse_object(param,
|
||||
SPA_TYPE_OBJECT_PeerParam, NULL)) < 0)
|
||||
return res;
|
||||
|
||||
first = spa_pod_prop_first(&obj->body);
|
||||
start = *state ? spa_pod_prop_next((struct spa_pod_prop*)*state) : first;
|
||||
|
||||
res = 0;
|
||||
for (cur = start; spa_pod_prop_is_inside(&obj->body, obj->pod.size, cur);
|
||||
cur = spa_pod_prop_next(cur)) {
|
||||
info->peer_id = cur->key;
|
||||
info->param = &cur->value;
|
||||
*state = (void*)cur;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
SPA_API_PEER_PARAM_UTILS void
|
||||
spa_peer_param_build_start(struct spa_pod_builder *builder, struct spa_pod_frame *f, uint32_t id)
|
||||
{
|
||||
spa_pod_builder_push_object(builder, f, SPA_TYPE_OBJECT_PeerParam, id);
|
||||
}
|
||||
|
||||
SPA_API_PEER_PARAM_UTILS void
|
||||
spa_peer_param_build_add_param(struct spa_pod_builder *builder, uint32_t peer_id,
|
||||
const struct spa_pod *param)
|
||||
{
|
||||
spa_pod_builder_prop(builder, peer_id, 0);
|
||||
if (param)
|
||||
spa_pod_builder_primitive(builder, param);
|
||||
else
|
||||
spa_pod_builder_none(builder);
|
||||
}
|
||||
|
||||
SPA_API_PEER_PARAM_UTILS struct spa_pod *
|
||||
spa_peer_param_build_end(struct spa_pod_builder *builder, struct spa_pod_frame *f)
|
||||
{
|
||||
return (struct spa_pod*)spa_pod_builder_pop(builder, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* \}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_PARAM_PEER_PARAM_UTILS_H */
|
||||
37
spa/include/spa/param/peer.h
Normal file
37
spa/include/spa/param/peer.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* Simple Plugin API */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef SPA_PARAM_PEER_PARAM_H
|
||||
#define SPA_PARAM_PEER_PARAM_H
|
||||
|
||||
#include <spa/param/param.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup spa_param
|
||||
* \{
|
||||
*/
|
||||
|
||||
/** properties for SPA_TYPE_OBJECT_PeerParam */
|
||||
enum spa_peer_param {
|
||||
SPA_PEER_PARAM_START, /**< id of peer as key, SPA_TYPE_Pod as value */
|
||||
SPA_PEER_PARAM_END = 0xfffffffe,
|
||||
};
|
||||
|
||||
struct spa_peer_param_info {
|
||||
uint32_t peer_id;
|
||||
const struct spa_pod *param;
|
||||
};
|
||||
/**
|
||||
* \}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_PARAM_PEER_PARAM_H */
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
#include <spa/pod/builder.h>
|
||||
#include <spa/pod/iter.h>
|
||||
#include <spa/pod/parser.h>
|
||||
#include <spa/pod/compare.h>
|
||||
#include <spa/param/tag.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
@ -33,8 +34,7 @@ extern "C" {
|
|||
SPA_API_TAG_UTILS int
|
||||
spa_tag_compare(const struct spa_pod *a, const struct spa_pod *b)
|
||||
{
|
||||
return ((a == b) || (a && b && SPA_POD_SIZE(a) == SPA_POD_SIZE(b) &&
|
||||
memcmp(a, b, SPA_POD_SIZE(b)) == 0)) ? 0 : 1;
|
||||
return spa_pod_memcmp(a, b);
|
||||
}
|
||||
|
||||
SPA_API_TAG_UTILS int
|
||||
|
|
|
|||
|
|
@ -15,5 +15,7 @@
|
|||
#include <spa/param/profile-types.h>
|
||||
#include <spa/param/route-types.h>
|
||||
#include <spa/param/tag-types.h>
|
||||
#include <spa/param/dict-types.h>
|
||||
#include <spa/param/peer-types.h>
|
||||
|
||||
#endif /* SPA_PARAM_TYPE_INFO_H */
|
||||
|
|
|
|||
|
|
@ -80,6 +80,13 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con
|
|||
return 0;
|
||||
}
|
||||
|
||||
SPA_API_POD_COMPARE int spa_pod_memcmp(const struct spa_pod *a,
|
||||
const struct spa_pod *b)
|
||||
{
|
||||
return ((a == b) || (a && b && SPA_POD_SIZE(a) == SPA_POD_SIZE(b) &&
|
||||
memcmp(a, b, SPA_POD_SIZE(b)) == 0)) ? 0 : 1;
|
||||
}
|
||||
|
||||
SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1,
|
||||
const struct spa_pod *pod2)
|
||||
{
|
||||
|
|
@ -149,12 +156,8 @@ SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1,
|
|||
break;
|
||||
}
|
||||
case SPA_TYPE_Array:
|
||||
{
|
||||
if (pod1->size != pod2->size)
|
||||
return -EINVAL;
|
||||
res = memcmp(SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), pod2->size);
|
||||
res = spa_pod_memcmp(pod1, pod2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (pod1->size != pod2->size)
|
||||
return -EINVAL;
|
||||
|
|
|
|||
|
|
@ -70,8 +70,10 @@ SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_continue(struct spa_pod_dynamic
|
|||
|
||||
SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_clean(struct spa_pod_dynamic_builder *builder)
|
||||
{
|
||||
if (builder->data != builder->b.data)
|
||||
if (builder->data != builder->b.data) {
|
||||
free(builder->b.data);
|
||||
builder->b.data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SPA_DEFINE_AUTO_CLEANUP(spa_pod_dynamic_builder, struct spa_pod_dynamic_builder, {
|
||||
|
|
|
|||
|
|
@ -339,9 +339,7 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b,
|
|||
|
||||
default:
|
||||
if (pf != NULL) {
|
||||
if (pp->size != pf->size)
|
||||
return -EINVAL;
|
||||
if (memcmp(pp, pf, pp->size) != 0)
|
||||
if (spa_pod_memcmp(pp, pf) != 0)
|
||||
return -EINVAL;
|
||||
do_advance = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#ifndef SPA_TYPE_INFO_H
|
||||
#define SPA_TYPE_INFO_H
|
||||
|
||||
|
||||
#include <spa/utils/defs.h>
|
||||
#include <spa/utils/type.h>
|
||||
#include <spa/utils/enum-types.h>
|
||||
|
|
@ -78,6 +79,8 @@ static const struct spa_type_info spa_types[] = {
|
|||
{ SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Latency, spa_type_param_latency },
|
||||
{ SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_ProcessLatency, spa_type_param_process_latency },
|
||||
{ SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Tag, spa_type_param_tag },
|
||||
{ SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_Object, SPA_TYPE_INFO_PeerParam, spa_type_peer_param },
|
||||
{ SPA_TYPE_OBJECT_ParamDict, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Dict, spa_type_param_dict },
|
||||
|
||||
{ 0, 0, NULL, NULL }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ enum {
|
|||
SPA_TYPE_OBJECT_ParamLatency,
|
||||
SPA_TYPE_OBJECT_ParamProcessLatency,
|
||||
SPA_TYPE_OBJECT_ParamTag,
|
||||
SPA_TYPE_OBJECT_PeerParam,
|
||||
SPA_TYPE_OBJECT_ParamDict,
|
||||
_SPA_TYPE_OBJECT_LAST, /**< not part of ABI */
|
||||
|
||||
/* vendor extensions */
|
||||
|
|
|
|||
|
|
@ -1137,7 +1137,7 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
|
|||
}
|
||||
|
||||
old_position = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
|
||||
if (eld.speakers == 0) {
|
||||
if (eld.speakers == 0 || eld.lpcm_channels == 0) {
|
||||
changed |= old_position != NULL;
|
||||
pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
|
||||
} else {
|
||||
|
|
@ -1146,32 +1146,38 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
|
|||
struct spa_strbuf b;
|
||||
int i = 0;
|
||||
|
||||
#define _ADD_CHANNEL_POSITION(pos) \
|
||||
{ \
|
||||
if (i < eld.lpcm_channels) \
|
||||
positions[i++] = pos; \
|
||||
}
|
||||
|
||||
if (eld.speakers & 0x01) {
|
||||
positions[i++] = ACP_CHANNEL_FL;
|
||||
positions[i++] = ACP_CHANNEL_FR;
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FL);
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FR);
|
||||
}
|
||||
if (eld.speakers & 0x02) {
|
||||
positions[i++] = ACP_CHANNEL_LFE;
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_LFE);
|
||||
}
|
||||
if (eld.speakers & 0x04) {
|
||||
positions[i++] = ACP_CHANNEL_FC;
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FC);
|
||||
}
|
||||
if (eld.speakers & 0x08) {
|
||||
positions[i++] = ACP_CHANNEL_RL;
|
||||
positions[i++] = ACP_CHANNEL_RR;
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RL);
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RR);
|
||||
}
|
||||
/* The rest are out of order in order of what channels we would prefer to use/expose first */
|
||||
if (eld.speakers & 0x40) {
|
||||
/* Use SL/SR instead of RLC/RRC */
|
||||
positions[i++] = ACP_CHANNEL_SL;
|
||||
positions[i++] = ACP_CHANNEL_SR;
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_SL);
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_SR);
|
||||
}
|
||||
if (eld.speakers & 0x20) {
|
||||
positions[i++] = ACP_CHANNEL_RLC;
|
||||
positions[i++] = ACP_CHANNEL_RRC;
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RLC);
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RRC);
|
||||
}
|
||||
if (eld.speakers & 0x10) {
|
||||
positions[i++] = ACP_CHANNEL_RC;
|
||||
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RC);
|
||||
}
|
||||
while (i < eld.lpcm_channels)
|
||||
positions[i++] = ACP_CHANNEL_UNKNOWN;
|
||||
|
|
|
|||
|
|
@ -2036,7 +2036,9 @@ static void recalc_headroom(struct state *state)
|
|||
uint32_t latency;
|
||||
uint32_t rate = 0;
|
||||
|
||||
if (state->position != NULL)
|
||||
if (state->force_quantum && !state->following)
|
||||
rate = state->rate;
|
||||
else if (state->position != NULL)
|
||||
rate = state->position->clock.target_rate.denom;
|
||||
|
||||
if (state->use_period_size_min_as_headroom)
|
||||
|
|
@ -2063,8 +2065,6 @@ static void recalc_headroom(struct state *state)
|
|||
state->headroom = 0;
|
||||
|
||||
latency = SPA_MAX(state->min_delay, SPA_MIN(state->max_delay, state->headroom));
|
||||
if (rate != 0 && state->rate != 0)
|
||||
latency = SPA_SCALE32_UP(latency, rate, state->rate);
|
||||
|
||||
if (state->is_firewire) {
|
||||
/* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size
|
||||
|
|
@ -2072,6 +2072,8 @@ static void recalc_headroom(struct state *state)
|
|||
*/
|
||||
latency += state->buffer_frames;
|
||||
}
|
||||
if (rate != 0 && state->rate != 0)
|
||||
latency = SPA_SCALE32_UP(latency, rate, state->rate);
|
||||
|
||||
state->latency[state->port_direction].min_rate =
|
||||
state->latency[state->port_direction].max_rate = latency;
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ struct props {
|
|||
unsigned int mix_disabled:1;
|
||||
unsigned int resample_disabled:1;
|
||||
unsigned int resample_quality;
|
||||
struct resample_config resample_config;
|
||||
double rate;
|
||||
char wav_path[512];
|
||||
unsigned int lock_volumes:1;
|
||||
|
|
@ -122,6 +123,7 @@ static void props_reset(struct props *props)
|
|||
props->mix_disabled = false;
|
||||
props->resample_disabled = false;
|
||||
props->resample_quality = RESAMPLE_DEFAULT_QUALITY;
|
||||
spa_zero(props->resample_config);
|
||||
props->rate = 1.0;
|
||||
spa_zero(props->wav_path);
|
||||
props->lock_volumes = false;
|
||||
|
|
@ -1019,7 +1021,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
|
|||
if (this->io_position && this->io_clock &&
|
||||
this->io_position->clock.target_rate.denom != this->io_clock->target_rate.denom &&
|
||||
!this->props.resample_disabled) {
|
||||
spa_log_warn(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id,
|
||||
spa_log_debug(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id,
|
||||
this->io_clock->target_rate.denom,
|
||||
this->io_position->clock.target_rate.denom);
|
||||
|
||||
|
|
@ -1342,6 +1344,14 @@ static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void sync_filter_graph(struct impl *impl)
|
||||
{
|
||||
if (impl->data_loop)
|
||||
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
|
||||
else
|
||||
do_sync_filter_graph(NULL, false, 0, NULL, 0, impl);
|
||||
}
|
||||
|
||||
static void clean_filter_handles(struct impl *impl, bool force)
|
||||
{
|
||||
struct filter_graph *g, *t;
|
||||
|
|
@ -1429,7 +1439,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
|
|||
if (impl->setup)
|
||||
res = setup_filter_graphs(impl, false);
|
||||
|
||||
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
|
||||
sync_filter_graph(impl);
|
||||
|
||||
if (impl->in_filter_props == 0)
|
||||
clean_filter_handles(impl, false);
|
||||
|
|
@ -1478,6 +1488,16 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
|
|||
this->props.resample_quality = atoi(s);
|
||||
else if (spa_streq(k, "resample.disable"))
|
||||
this->props.resample_disabled = spa_atob(s);
|
||||
else if (spa_streq(k, "resample.window"))
|
||||
this->props.resample_config.window = resample_window_from_label(s);
|
||||
else if (spa_streq(k, "resample.cutoff"))
|
||||
spa_atod(s, &this->props.resample_config.cutoff);
|
||||
else if (spa_streq(k, "resample.n-taps"))
|
||||
spa_atou32(s, &this->props.resample_config.n_taps, 0);
|
||||
else if (spa_strstartswith(k, "resample.param.")) {
|
||||
uint32_t idx = resample_param_from_label(k+strlen("resample.param."));
|
||||
spa_atod(s, &this->props.resample_config.params[idx]);
|
||||
}
|
||||
else if (spa_streq(k, "dither.noise"))
|
||||
spa_atou32(s, &this->dir[1].conv.noise_bits, 0);
|
||||
else if (spa_streq(k, "dither.method"))
|
||||
|
|
@ -1488,6 +1508,10 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
|
|||
}
|
||||
else if (spa_streq(k, "channelmix.lock-volumes"))
|
||||
this->props.lock_volumes = spa_atob(s);
|
||||
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
|
||||
if (!*disable_filter)
|
||||
*disable_filter = spa_atob(s);
|
||||
}
|
||||
else if (spa_strstartswith(k, "audioconvert.filter-graph.")) {
|
||||
int order = atoi(k + strlen("audioconvert.filter-graph."));
|
||||
if ((res = load_filter_graph(this, s, order)) < 0) {
|
||||
|
|
@ -1495,10 +1519,6 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
|
|||
order, spa_strerror(res));
|
||||
}
|
||||
}
|
||||
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
|
||||
if (!*disable_filter)
|
||||
*disable_filter = spa_atob(s);
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
return 1;
|
||||
|
|
@ -2289,6 +2309,7 @@ static int setup_resample(struct impl *this)
|
|||
this->resample.o_rate = out->format.info.raw.rate;
|
||||
this->resample.log = this->log;
|
||||
this->resample.quality = this->props.resample_quality;
|
||||
this->resample.config = this->props.resample_config;
|
||||
this->resample.cpu_flags = this->cpu_flags;
|
||||
|
||||
this->rate_adjust = this->props.rate != 1.0;
|
||||
|
|
@ -2532,6 +2553,8 @@ static int setup_convert(struct impl *this)
|
|||
this->setup = true;
|
||||
this->recalc = true;
|
||||
|
||||
sync_filter_graph(this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -2548,6 +2571,7 @@ static void reset_node(struct impl *this)
|
|||
resample_reset(&this->resample);
|
||||
this->in_offset = 0;
|
||||
this->out_offset = 0;
|
||||
this->setup = false;
|
||||
}
|
||||
|
||||
static int impl_node_send_command(void *object, const struct spa_command *command)
|
||||
|
|
@ -2568,7 +2592,6 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
|
|||
break;
|
||||
case SPA_NODE_COMMAND_Suspend:
|
||||
reset_node(this);
|
||||
this->setup = false;
|
||||
SPA_FALLTHROUGH;
|
||||
case SPA_NODE_COMMAND_Pause:
|
||||
this->started = false;
|
||||
|
|
|
|||
147
spa/plugins/audioconvert/dbesi0.c
Normal file
147
spa/plugins/audioconvert/dbesi0.c
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* Copyright(C) 1996 Takuya OOURA
|
||||
|
||||
You may use, copy, modify this code for any purpose and
|
||||
without fee.
|
||||
|
||||
Package home: http://www.kurims.kyoto-u.ac.jp/~ooura/bessel.html
|
||||
*/
|
||||
|
||||
/* Bessel I_0(x) function in double precision */
|
||||
|
||||
#include <math.h>
|
||||
|
||||
static double
|
||||
dbesi0 (double x)
|
||||
{
|
||||
int k;
|
||||
double w, t, y;
|
||||
static double a[65] = {
|
||||
8.5246820682016865877e-11, 2.5966600546497407288e-9,
|
||||
7.9689994568640180274e-8, 1.9906710409667748239e-6,
|
||||
4.0312469446528002532e-5, 6.4499871606224265421e-4,
|
||||
0.0079012345761930579108, 0.071111111109207045212,
|
||||
0.444444444444724909, 1.7777777777777532045,
|
||||
4.0000000000000011182, 3.99999999999999998,
|
||||
1.0000000000000000001,
|
||||
1.1520919130377195927e-10, 2.2287613013610985225e-9,
|
||||
8.1903951930694585113e-8, 1.9821560631611544984e-6,
|
||||
4.0335461940910133184e-5, 6.4495330974432203401e-4,
|
||||
0.0079013012611467520626, 0.071111038160875566622,
|
||||
0.44444450319062699316, 1.7777777439146450067,
|
||||
4.0000000132337935071, 3.9999999968569015366,
|
||||
1.0000000003426703174,
|
||||
1.5476870780515238488e-10, 1.2685004214732975355e-9,
|
||||
9.2776861851114223267e-8, 1.9063070109379044378e-6,
|
||||
4.0698004389917945832e-5, 6.4370447244298070713e-4,
|
||||
0.0079044749458444976958, 0.071105052411749363882,
|
||||
0.44445280640924755082, 1.7777694934432109713,
|
||||
4.0000055808824003386, 3.9999977081165740932,
|
||||
1.0000004333949319118,
|
||||
2.0675200625006793075e-10, -6.1689554705125681442e-10,
|
||||
1.2436765915401571654e-7, 1.5830429403520613423e-6,
|
||||
4.2947227560776583326e-5, 6.3249861665073441312e-4,
|
||||
0.0079454472840953930811, 0.070994327785661860575,
|
||||
0.44467219586283000332, 1.7774588182255374745,
|
||||
4.0003038986252717972, 3.9998233869142057195,
|
||||
1.0000472932961288324,
|
||||
2.7475684794982708655e-10, -3.8991472076521332023e-9,
|
||||
1.9730170483976049388e-7, 5.9651531561967674521e-7,
|
||||
5.1992971474748995357e-5, 5.7327338675433770752e-4,
|
||||
0.0082293143836530412024, 0.069990934858728039037,
|
||||
0.44726764292723985087, 1.7726685170014087784,
|
||||
4.0062907863712704432, 3.9952750700487845355,
|
||||
1.0016354346654179322
|
||||
};
|
||||
static double b[70] = {
|
||||
6.7852367144945531383e-8, 4.6266061382821826854e-7,
|
||||
6.9703135812354071774e-6, 7.6637663462953234134e-5,
|
||||
7.9113515222612691636e-4, 0.0073401204731103808981,
|
||||
0.060677114958668837046, 0.43994941411651569622,
|
||||
2.7420017097661750609, 14.289661921740860534,
|
||||
59.820609640320710779, 188.78998681199150629,
|
||||
399.8731367825601118, 427.56411572180478514,
|
||||
1.8042097874891098754e-7, 1.2277164312044637357e-6,
|
||||
1.8484393221474274861e-5, 2.0293995900091309208e-4,
|
||||
0.0020918539850246207459, 0.019375315654033949297,
|
||||
0.15985869016767185908, 1.1565260527420641724,
|
||||
7.1896341224206072113, 37.354773811947484532,
|
||||
155.80993164266268457, 489.5211371158540918,
|
||||
1030.9147225169564806, 1093.5883545113746958,
|
||||
4.8017305613187493564e-7, 3.261317843912380074e-6,
|
||||
4.9073137508166159639e-5, 5.3806506676487583755e-4,
|
||||
0.0055387918291051866561, 0.051223717488786549025,
|
||||
0.42190298621367914765, 3.0463625987357355872,
|
||||
18.895299447327733204, 97.915189029455461554,
|
||||
407.13940115493494659, 1274.3088990480582632,
|
||||
2670.9883037012547506, 2815.7166284662544712,
|
||||
1.2789926338424623394e-6, 8.6718263067604918916e-6,
|
||||
1.3041508821299929489e-4, 0.001428224737372747892,
|
||||
0.014684070635768789378, 0.13561403190404185755,
|
||||
1.1152592585977393953, 8.0387088559465389038,
|
||||
49.761318895895479206, 257.2684232313529138,
|
||||
1066.8543146269566231, 3328.3874581009636362,
|
||||
6948.8586598121634874, 7288.4893398212481055,
|
||||
3.409350368197032893e-6, 2.3079025203103376076e-5,
|
||||
3.4691373283901830239e-4, 0.003794994977222908545,
|
||||
0.038974209677945602145, 0.3594948380414878371,
|
||||
2.9522878893539528226, 21.246564609514287056,
|
||||
131.28727387146173141, 677.38107093296675421,
|
||||
2802.3724744545046518, 8718.5731420798254081,
|
||||
18141.348781638832286, 18948.925349296308859
|
||||
};
|
||||
static double c[45] = {
|
||||
2.5568678676452702768e-15, 3.0393953792305924324e-14,
|
||||
6.3343751991094840009e-13, 1.5041298011833009649e-11,
|
||||
4.4569436918556541414e-10, 1.746393051427167951e-8,
|
||||
1.0059224011079852317e-6, 1.0729838945088577089e-4,
|
||||
0.05150322693642527738,
|
||||
5.2527963991711562216e-15, 7.202118481421005641e-15,
|
||||
7.2561421229904797156e-13, 1.482312146673104251e-11,
|
||||
4.4602670450376245434e-10, 1.7463600061788679671e-8,
|
||||
1.005922609132234756e-6, 1.0729838937545111487e-4,
|
||||
0.051503226936437300716,
|
||||
1.3365917359358069908e-14, -1.2932643065888544835e-13,
|
||||
1.7450199447905602915e-12, 1.0419051209056979788e-11,
|
||||
4.58047881980598326e-10, 1.7442405450073548966e-8,
|
||||
1.0059461453281292278e-6, 1.0729837434500161228e-4,
|
||||
0.051503226940658446941,
|
||||
5.3771611477352308649e-14, -1.1396193006413731702e-12,
|
||||
1.2858641335221653409e-11, -5.9802086004570057703e-11,
|
||||
7.3666894305929510222e-10, 1.6731837150730356448e-8,
|
||||
1.0070831435812128922e-6, 1.0729733111203704813e-4,
|
||||
0.051503227360726294675,
|
||||
3.7819492084858931093e-14, -4.8600496888588034879e-13,
|
||||
1.6898350504817224909e-12, 4.5884624327524255865e-11,
|
||||
1.2521615963377513729e-10, 1.8959658437754727957e-8,
|
||||
1.0020716710561353622e-6, 1.073037119856927559e-4,
|
||||
0.05150322383300230775
|
||||
};
|
||||
|
||||
w = fabs (x);
|
||||
if (w < 8.5) {
|
||||
t = w * w * 0.0625;
|
||||
k = 13 * ((int) t);
|
||||
y = (((((((((((a[k] * t + a[k + 1]) * t +
|
||||
a[k + 2]) * t + a[k + 3]) * t +
|
||||
a[k + 4]) * t + a[k + 5]) * t + a[k +
|
||||
6]) * t + a[k + 7]) * t + a[k + 8]) * t + a[k +
|
||||
9]) * t + a[k + 10]) * t + a[k + 11]) * t + a[k + 12];
|
||||
} else if (w < 12.5) {
|
||||
k = (int) w;
|
||||
t = w - k;
|
||||
k = 14 * (k - 8);
|
||||
y = ((((((((((((b[k] * t + b[k + 1]) * t + b[k + 2]) * t + b[k + 3]) * t +
|
||||
b[k + 4]) * t + b[k + 5]) * t + b[k +
|
||||
6]) * t + b[k + 7]) * t + b[k + 8]) * t +
|
||||
b[k + 9]) * t + b[k + 10]) * t + b[k + 11]) * t + b[k +
|
||||
12]) * t + b[k + 13];
|
||||
} else {
|
||||
t = 60 / w;
|
||||
k = 9 * ((int) t);
|
||||
y = ((((((((c[k] * t + c[k + 1]) * t +
|
||||
c[k + 2]) * t + c[k + 3]) * t + c[k + 4]) * t +
|
||||
c[k + 5]) * t + c[k + 6]) * t + c[k + 7]) * t +
|
||||
c[k + 8]) * sqrt (t) * exp (w);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
|
@ -284,7 +284,8 @@ conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
// Non static, referenced by `fmt-ops-sse41.c`.
|
||||
void
|
||||
conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
|
||||
uint32_t n_channels, uint32_t n_samples)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
#include <tmmintrin.h>
|
||||
|
||||
static void
|
||||
// Non static, referenced by `fmt-ops-sse41.c`.
|
||||
void
|
||||
conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
|
||||
uint32_t n_channels, uint32_t n_samples)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,72 +13,186 @@
|
|||
|
||||
SPA_LOG_TOPIC_DEFINE(resample_log_topic, "spa.resample");
|
||||
|
||||
#define INHERIT_PARAM(c,q,p) if ((c)->params[p] == 0.0) (c)->params[p] = (q)->params[p];
|
||||
|
||||
struct quality {
|
||||
uint32_t n_taps;
|
||||
double cutoff;
|
||||
double cutoff_up; /* when upsampling */
|
||||
double cutoff_down; /* for downsampling */
|
||||
double params[RESAMPLE_MAX_PARAMS];
|
||||
};
|
||||
|
||||
static const struct quality window_qualities[] = {
|
||||
{ 8, 0.53, },
|
||||
{ 16, 0.67, },
|
||||
{ 24, 0.75, },
|
||||
{ 32, 0.80, },
|
||||
{ 48, 0.85, }, /* default */
|
||||
{ 64, 0.88, },
|
||||
{ 80, 0.895, },
|
||||
{ 96, 0.910, },
|
||||
{ 128, 0.936, },
|
||||
{ 144, 0.945, },
|
||||
{ 160, 0.950, },
|
||||
{ 192, 0.960, },
|
||||
{ 256, 0.970, },
|
||||
{ 896, 0.990, },
|
||||
{ 1024, 0.995, },
|
||||
|
||||
struct window_info {
|
||||
uint32_t window;
|
||||
void (*func) (struct resample *r, double *w, double t, uint32_t n_taps);
|
||||
uint32_t n_qualities;
|
||||
const struct quality *qualities;
|
||||
void (*config) (struct resample *r);
|
||||
};
|
||||
struct window_info window_info[];
|
||||
|
||||
static const struct quality blackman_qualities[] = {
|
||||
{ 8, 0.58, 0.58, { 0.16, }},
|
||||
{ 16, 0.70, 0.70, { 0.20, }},
|
||||
{ 24, 0.77, 0.77, { 0.16, }},
|
||||
{ 32, 0.82, 0.82, { 0.16, }},
|
||||
{ 48, 0.87, 0.87, { 0.16, }}, /* default */
|
||||
{ 64, 0.895, 0.895, { 0.16, }},
|
||||
{ 80, 0.910, 0.910, { 0.16, }},
|
||||
{ 96, 0.925, 0.925, { 0.16, }},
|
||||
{ 128, 0.942, 0.942, { 0.16, }},
|
||||
{ 144, 0.950, 0.950, { 0.16, }},
|
||||
{ 160, 0.958, 0.958, { 0.16, }},
|
||||
{ 192, 0.966, 0.966, { 0.16, }},
|
||||
{ 256, 0.975, 0.975, { 0.16, }},
|
||||
{ 896, 0.988, 0.988, { 0.16, }},
|
||||
{ 1024, 0.990, 0.990, { 0.16, }},
|
||||
};
|
||||
|
||||
static inline double sinc(double x)
|
||||
static inline void blackman_window(struct resample *r, double *w, double t, uint32_t n_taps)
|
||||
{
|
||||
if (x < 1e-6) return 1.0;
|
||||
double x, alpha = r->config.params[RESAMPLE_PARAM_BLACKMAN_ALPHA];
|
||||
uint32_t i, n_taps12 = n_taps/2;
|
||||
for (i = 0; i < n_taps12; i++, t += 1.0) {
|
||||
x = 2.0 * M_PI * t / n_taps;
|
||||
w[i] = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) +
|
||||
(alpha / 2.0) * cos(2.0 * x);
|
||||
}
|
||||
}
|
||||
static inline void blackman_config(struct resample *r)
|
||||
{
|
||||
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
|
||||
INHERIT_PARAM(&r->config, q, RESAMPLE_PARAM_BLACKMAN_ALPHA);
|
||||
}
|
||||
|
||||
static const struct quality exp_qualities[] = {
|
||||
{ 8, 0.58, 0.58, { 16.97789, }},
|
||||
{ 16, 0.70, 0.70, { 16.97789, }},
|
||||
{ 24, 0.77, 0.77, { 16.97789, }},
|
||||
{ 32, 0.82, 0.82, { 16.97789, }},
|
||||
{ 48, 0.87, 0.87, { 16.97789, }}, /* default */
|
||||
{ 64, 0.895, 0.895, { 16.97789, }},
|
||||
{ 80, 0.910, 0.910, { 16.97789, }},
|
||||
{ 96, 0.925, 0.925, { 16.97789, }},
|
||||
{ 128, 0.942, 0.942, { 16.97789, }},
|
||||
{ 144, 0.950, 0.950, { 16.97789, }},
|
||||
{ 160, 0.958, 0.958, { 16.97789, }},
|
||||
{ 192, 0.966, 0.966, { 16.97789, }},
|
||||
{ 256, 0.975, 0.975, { 16.97789, }},
|
||||
{ 896, 0.988, 0.988, { 16.97789, }},
|
||||
{ 1024, 0.990, 0.990, { 16.97789, }},
|
||||
};
|
||||
|
||||
static inline void exp_window(struct resample *r, double *w, double t, uint32_t n_taps)
|
||||
{
|
||||
double x, A = r->config.params[RESAMPLE_PARAM_EXP_A];
|
||||
uint32_t i, n_taps12 = n_taps/2;
|
||||
|
||||
for (i = 0; i < n_taps12; i++, t += 1.0) {
|
||||
x = 2.0 * t / n_taps;
|
||||
/* doi:10.1109/RME.2008.4595727 with tweak */
|
||||
w[i] = (exp(A * sqrt(fmax(0.0, 1.0 - x*x))) - 1) / (exp(A) - 1);
|
||||
}
|
||||
}
|
||||
static inline void exp_config(struct resample *r)
|
||||
{
|
||||
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
|
||||
INHERIT_PARAM(&r->config, q, RESAMPLE_PARAM_EXP_A);
|
||||
}
|
||||
|
||||
#include "dbesi0.c"
|
||||
|
||||
static const struct quality kaiser_qualities[] = {
|
||||
{ 8, 0.620000, 0.620000, { 3.553376, 110.000000, 0.888064 }},
|
||||
{ 16, 0.780000, 0.780000, { 3.553376, 110.000000, 0.444032 }},
|
||||
{ 24, 0.820000, 0.820000, { 3.904154, 120.000000, 0.325043 }},
|
||||
{ 32, 0.865000, 0.865000, { 4.254931, 130.000000, 0.265548 }},
|
||||
{ 48, 0.895000, 0.895000, { 4.254931, 130.000000, 0.177032 }},
|
||||
{ 64, 0.915000, 0.915000, { 4.254931, 130.000000, 0.132774 }},
|
||||
{ 80, 0.928000, 0.928000, { 4.254931, 130.000000, 0.106219 }},
|
||||
{ 96, 0.942000, 0.942000, { 4.254931, 130.000000, 0.088516 }},
|
||||
{ 128, 0.952000, 0.952000, { 4.254931, 130.000000, 0.066387 }},
|
||||
{ 160, 0.960000, 0.960000, { 4.254931, 130.000000, 0.053110 }},
|
||||
{ 192, 0.968000, 0.968000, { 4.254931, 130.000000, 0.044258 }},
|
||||
{ 256, 0.976000, 0.976000, { 4.605709, 140.000000, 0.035914 }},
|
||||
{ 512, 0.985000, 0.985000, { 4.781097, 145.000000, 0.018637 }},
|
||||
{ 768, 0.990000, 0.990000, { 4.956486, 150.000000, 0.012878 }},
|
||||
{ 1024, 0.993000, 0.993000, { 5.131875, 155.000000, 0.009999 }},
|
||||
};
|
||||
|
||||
static inline void kaiser_window(struct resample *r, double *w, double t, uint32_t n_taps)
|
||||
{
|
||||
double x, beta = r->config.params[RESAMPLE_PARAM_KAISER_ALPHA] * M_PI;
|
||||
double den = dbesi0(beta);
|
||||
uint32_t i, n_taps12 = n_taps/2;
|
||||
for (i = 0; i < n_taps12; i++, t += 1.0) {
|
||||
x = 2.0 * t / n_taps;
|
||||
w[i] = dbesi0(beta * sqrt(fmax(0.0, 1.0 - x*x))) / den;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void kaiser_config(struct resample *r)
|
||||
{
|
||||
double A, B, dw, tr_bw, alpha;
|
||||
uint32_t n;
|
||||
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
|
||||
|
||||
if ((A = r->config.params[RESAMPLE_PARAM_KAISER_SB_ATT]) == 0.0)
|
||||
A = q->params[RESAMPLE_PARAM_KAISER_SB_ATT];
|
||||
if ((tr_bw = r->config.params[RESAMPLE_PARAM_KAISER_TR_BW]) == 0.0)
|
||||
tr_bw = q->params[RESAMPLE_PARAM_KAISER_TR_BW];
|
||||
|
||||
if ((alpha = r->config.params[RESAMPLE_PARAM_KAISER_ALPHA]) == 0.0) {
|
||||
/* calculate Beta and alpha */
|
||||
if (A > 50)
|
||||
B = 0.1102 * (A - 8.7);
|
||||
else if (A >= 21)
|
||||
B = 0.5842 * pow (A - 21, 0.4) + 0.07886 * (A - 21);
|
||||
else
|
||||
B = 0.0;
|
||||
|
||||
r->config.params[RESAMPLE_PARAM_KAISER_ALPHA] = B / M_PI;
|
||||
}
|
||||
if (r->config.n_taps == 0) {
|
||||
/* calculate transition width in radians */
|
||||
dw = 2 * M_PI * (tr_bw);
|
||||
/* order of the filter */
|
||||
n = (uint32_t)((A - 8.0) / (2.285 * dw));
|
||||
r->config.n_taps = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
struct window_info window_info[] = {
|
||||
[RESAMPLE_WINDOW_EXP] = { RESAMPLE_WINDOW_EXP, exp_window,
|
||||
SPA_N_ELEMENTS(exp_qualities), exp_qualities, exp_config },
|
||||
[RESAMPLE_WINDOW_BLACKMAN] = { RESAMPLE_WINDOW_BLACKMAN, blackman_window,
|
||||
SPA_N_ELEMENTS(blackman_qualities), blackman_qualities, blackman_config },
|
||||
[RESAMPLE_WINDOW_KAISER] = { RESAMPLE_WINDOW_KAISER, kaiser_window,
|
||||
SPA_N_ELEMENTS(kaiser_qualities), kaiser_qualities, kaiser_config },
|
||||
};
|
||||
|
||||
static inline double sinc(double x, double cutoff)
|
||||
{
|
||||
if (x < 1e-6) return cutoff;
|
||||
x *= M_PI;
|
||||
return sin(x) / x;
|
||||
return sin(x * cutoff) / x;
|
||||
}
|
||||
|
||||
static inline double window_blackman(double x, double n_taps)
|
||||
{
|
||||
double alpha = 0.232, r;
|
||||
x = 2.0 * M_PI * x / n_taps;
|
||||
r = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) +
|
||||
(alpha / 2.0) * cos(2.0 * x);
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline double window_cosh(double x, double n_taps)
|
||||
{
|
||||
double r;
|
||||
double A = 16.97789;
|
||||
double x2;
|
||||
x = 2.0 * x / n_taps;
|
||||
x2 = x * x;
|
||||
if (x2 >= 1.0)
|
||||
return 0.0;
|
||||
/* doi:10.1109/RME.2008.4595727 with tweak */
|
||||
r = (exp(A * sqrt(1 - x2)) - 1) / (exp(A) - 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
#define window (1 ? window_cosh : window_blackman)
|
||||
|
||||
static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff)
|
||||
static int build_filter(struct resample *r, float *taps, uint32_t stride, uint32_t n_taps,
|
||||
uint32_t n_phases, double cutoff)
|
||||
{
|
||||
uint32_t i, j, n_taps12 = n_taps/2;
|
||||
double window[n_taps12+1];
|
||||
|
||||
for (i = 0; i <= n_phases; i++) {
|
||||
double t = (double) i / (double) n_phases;
|
||||
window_info[r->config.window].func(r, window, t, n_taps);
|
||||
for (j = 0; j < n_taps12; j++, t += 1.0) {
|
||||
/* exploit symmetry in filter taps */
|
||||
taps[(n_phases - i) * stride + n_taps12 + j] =
|
||||
taps[i * stride + (n_taps12 - j - 1)] = (float)
|
||||
(cutoff * sinc(t * cutoff) * window(t, n_taps));
|
||||
(sinc(t, cutoff) * window[j]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -352,11 +466,18 @@ int resample_native_init(struct resample *r)
|
|||
{
|
||||
struct native_data *d;
|
||||
const struct quality *q;
|
||||
double scale;
|
||||
uint32_t c, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride;
|
||||
double scale, cutoff;
|
||||
uint32_t i, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride;
|
||||
uint32_t history_stride, history_size, oversample;
|
||||
struct resample_config *c = &r->config;
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
struct resample_config def = { 0 };
|
||||
bool default_config;
|
||||
|
||||
r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(window_qualities) - 1);
|
||||
default_config = memcmp(c, &def, sizeof(def)) == 0;
|
||||
#endif
|
||||
c->window = SPA_CLAMP(c->window, 0u, SPA_N_ELEMENTS(window_info)-1);
|
||||
r->quality = SPA_CLAMP(r->quality, 0, (int)(window_info[c->window].n_qualities - 1));
|
||||
r->free = impl_native_free;
|
||||
r->update_rate = impl_native_update_rate;
|
||||
r->in_len = impl_native_in_len;
|
||||
|
|
@ -366,17 +487,22 @@ int resample_native_init(struct resample *r)
|
|||
r->delay = impl_native_delay;
|
||||
r->phase = impl_native_phase;
|
||||
|
||||
q = &window_qualities[r->quality];
|
||||
window_info[c->window].config(r);
|
||||
|
||||
q = &window_info[c->window].qualities[r->quality];
|
||||
cutoff = r->o_rate < r->i_rate ? q->cutoff_down : q->cutoff_up;
|
||||
c->cutoff = c->cutoff <= 0.0 ? cutoff: c->cutoff;
|
||||
n_taps = c->n_taps == 0 ? q->n_taps : c->n_taps;
|
||||
|
||||
gcd = calc_gcd(r->i_rate, r->o_rate);
|
||||
|
||||
in_rate = r->i_rate / gcd;
|
||||
out_rate = r->o_rate / gcd;
|
||||
|
||||
scale = SPA_MIN(q->cutoff * out_rate / in_rate, q->cutoff);
|
||||
scale = SPA_MIN(c->cutoff * out_rate / in_rate, c->cutoff);
|
||||
|
||||
/* multiple of 8 taps to ease simd optimizations */
|
||||
n_taps = SPA_ROUND_UP_N((uint32_t)ceil(q->n_taps / scale), 8);
|
||||
n_taps = SPA_ROUND_UP_N((uint32_t)ceil(n_taps / scale), 8);
|
||||
n_taps = SPA_MIN(n_taps, 1u << 18);
|
||||
|
||||
/* try to get at least 256 phases so that interpolation is
|
||||
|
|
@ -400,7 +526,7 @@ int resample_native_init(struct resample *r)
|
|||
return -errno;
|
||||
|
||||
r->data = d;
|
||||
d->n_taps = n_taps;
|
||||
c->n_taps = d->n_taps = n_taps;
|
||||
d->n_phases = n_phases;
|
||||
d->in_rate = UINT32_TO_FIXP(in_rate);
|
||||
d->out_rate = out_rate;
|
||||
|
|
@ -411,25 +537,26 @@ int resample_native_init(struct resample *r)
|
|||
d->history = SPA_PTROFF(d->hist_mem, history_size, float*);
|
||||
d->filter_stride = filter_stride / sizeof(float);
|
||||
d->filter_stride_os = d->filter_stride * oversample;
|
||||
for (c = 0; c < r->channels; c++)
|
||||
d->history[c] = SPA_PTROFF(d->hist_mem, c * history_stride, float);
|
||||
for (i = 0; i < r->channels; i++)
|
||||
d->history[i] = SPA_PTROFF(d->hist_mem, i * history_stride, float);
|
||||
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
/* See if we have precomputed coefficients */
|
||||
for (c = 0; precomp_coeffs[c].filter; c++) {
|
||||
if (precomp_coeffs[c].in_rate == r->i_rate &&
|
||||
precomp_coeffs[c].out_rate == r->o_rate &&
|
||||
precomp_coeffs[c].quality == r->quality)
|
||||
for (i = 0; precomp_coeffs[i].filter; i++) {
|
||||
if (default_config &&
|
||||
precomp_coeffs[i].in_rate == r->i_rate &&
|
||||
precomp_coeffs[i].out_rate == r->o_rate &&
|
||||
precomp_coeffs[i].quality == r->quality)
|
||||
break;
|
||||
}
|
||||
|
||||
if (precomp_coeffs[c].filter) {
|
||||
spa_log_debug(r->log, "using precomputed filter for %u->%u(%u)",
|
||||
if (precomp_coeffs[i].filter) {
|
||||
spa_log_info(r->log, "using precomputed filter for %u->%u(%u)",
|
||||
r->i_rate, r->o_rate, r->quality);
|
||||
spa_memcpy(d->filter, precomp_coeffs[c].filter, filter_size);
|
||||
spa_memcpy(d->filter, precomp_coeffs[i].filter, filter_size);
|
||||
} else {
|
||||
#endif
|
||||
build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale);
|
||||
build_filter(r, d->filter, d->filter_stride, n_taps, n_phases, scale);
|
||||
#ifndef RESAMPLE_DISABLE_PRECOMP
|
||||
}
|
||||
#endif
|
||||
|
|
@ -440,8 +567,8 @@ int resample_native_init(struct resample *r)
|
|||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
spa_log_debug(r->log, "native %p: q:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x",
|
||||
r, r->quality, r->i_rate, r->o_rate, gcd, n_taps, n_phases,
|
||||
spa_log_info(r->log, "native %p: c:%f q:%d w:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x",
|
||||
r, c->cutoff, r->quality, c->window, r->i_rate, r->o_rate, gcd, n_taps, n_phases,
|
||||
r->cpu_flags, d->info->cpu_flags);
|
||||
|
||||
r->cpu_flags = d->info->cpu_flags;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,30 @@
|
|||
#include <spa/support/cpu.h>
|
||||
#include <spa/support/log.h>
|
||||
|
||||
#ifndef RESAMPLE_DEFAULT_QUALITY
|
||||
#define RESAMPLE_DEFAULT_QUALITY 4
|
||||
#endif
|
||||
|
||||
#define RESAMPLE_WINDOW_DEFAULT RESAMPLE_WINDOW_EXP
|
||||
#define RESAMPLE_MAX_PARAMS 16
|
||||
|
||||
struct resample_config {
|
||||
#define RESAMPLE_WINDOW_EXP 0
|
||||
#define RESAMPLE_WINDOW_BLACKMAN 1
|
||||
#define RESAMPLE_WINDOW_KAISER 2
|
||||
uint32_t window;
|
||||
|
||||
double cutoff;
|
||||
uint32_t n_taps;
|
||||
|
||||
#define RESAMPLE_PARAM_EXP_A 0
|
||||
#define RESAMPLE_PARAM_BLACKMAN_ALPHA 0
|
||||
#define RESAMPLE_PARAM_KAISER_ALPHA 0
|
||||
#define RESAMPLE_PARAM_KAISER_SB_ATT 1 /* stopband attenuation */
|
||||
#define RESAMPLE_PARAM_KAISER_TR_BW 2 /* transition bandwidth */
|
||||
#define RESAMPLE_PARAM_INVALID (RESAMPLE_MAX_PARAMS-1)
|
||||
double params[RESAMPLE_MAX_PARAMS];
|
||||
};
|
||||
|
||||
struct resample {
|
||||
struct spa_log *log;
|
||||
|
|
@ -23,6 +46,8 @@ struct resample {
|
|||
double rate;
|
||||
int quality;
|
||||
|
||||
struct resample_config config; /* set to all 0 for defaults */
|
||||
|
||||
void (*free) (struct resample *r);
|
||||
void (*update_rate) (struct resample *r, double rate);
|
||||
uint32_t (*in_len) (struct resample *r, uint32_t out_len);
|
||||
|
|
@ -49,6 +74,56 @@ struct resample {
|
|||
#define resample_phase(r) (r)->phase(r)
|
||||
|
||||
int resample_native_init(struct resample *r);
|
||||
int resample_native_init_config(struct resample *r, struct resample_config *conf);
|
||||
int resample_peaks_init(struct resample *r);
|
||||
|
||||
static const struct resample_window_info {
|
||||
uint32_t window;
|
||||
const char *label;
|
||||
const char *description;
|
||||
uint32_t n_params;
|
||||
} resample_window_info[] = {
|
||||
[RESAMPLE_WINDOW_EXP] = { RESAMPLE_WINDOW_EXP,
|
||||
"exp", "Exponential window", 1 },
|
||||
[RESAMPLE_WINDOW_BLACKMAN] = { RESAMPLE_WINDOW_BLACKMAN,
|
||||
"blackman", "Blackman window", 1 },
|
||||
[RESAMPLE_WINDOW_KAISER] = { RESAMPLE_WINDOW_KAISER,
|
||||
"kaiser", "Kaiser window", 3 },
|
||||
};
|
||||
|
||||
static inline uint32_t resample_window_from_label(const char *label)
|
||||
{
|
||||
SPA_FOR_EACH_ELEMENT_VAR(resample_window_info, i) {
|
||||
if (spa_streq(i->label, label))
|
||||
return i->window;
|
||||
}
|
||||
return RESAMPLE_WINDOW_EXP;
|
||||
}
|
||||
|
||||
static inline const char *resample_window_name(uint32_t idx)
|
||||
{
|
||||
return resample_window_info[SPA_CLAMP(idx, 0u, SPA_N_ELEMENTS(resample_window_info)-1)].label;
|
||||
}
|
||||
|
||||
static const struct resample_param_info {
|
||||
uint32_t window;
|
||||
uint32_t idx;
|
||||
const char *label;
|
||||
} resample_param_info[] = {
|
||||
{ RESAMPLE_WINDOW_EXP, RESAMPLE_PARAM_EXP_A, "exp.A" },
|
||||
{ RESAMPLE_WINDOW_BLACKMAN, RESAMPLE_PARAM_BLACKMAN_ALPHA, "blackman.alpha" },
|
||||
{ RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_ALPHA, "kaiser.alpha" },
|
||||
{ RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_SB_ATT, "kaiser.stopband-attenuation" },
|
||||
{ RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_TR_BW, "kaiser.transition-bandwidth" },
|
||||
};
|
||||
|
||||
static inline uint32_t resample_param_from_label(const char *label)
|
||||
{
|
||||
SPA_FOR_EACH_ELEMENT_VAR(resample_param_info, i) {
|
||||
if (spa_streq(i->label, label))
|
||||
return i->idx;
|
||||
}
|
||||
return RESAMPLE_PARAM_INVALID;
|
||||
}
|
||||
|
||||
#endif /* RESAMPLE_H */
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ struct data {
|
|||
bool verbose;
|
||||
int rate;
|
||||
int format;
|
||||
uint32_t window;
|
||||
int quality;
|
||||
struct resample_config config;
|
||||
int cpu_flags;
|
||||
|
||||
const char *iname;
|
||||
|
|
@ -43,15 +45,19 @@ struct data {
|
|||
|
||||
#define STR_FMTS "(s8|s16|s32|f32|f64)"
|
||||
|
||||
#define OPTIONS "hvr:f:q:c:"
|
||||
#define OPTIONS "hvc:r:f:w:q:u:t:p:"
|
||||
static const struct option long_options[] = {
|
||||
{ "help", no_argument, NULL, 'h'},
|
||||
{ "verbose", no_argument, NULL, 'v'},
|
||||
{ "cpuflags", required_argument, NULL, 'c' },
|
||||
|
||||
{ "rate", required_argument, NULL, 'r' },
|
||||
{ "format", required_argument, NULL, 'f' },
|
||||
{ "window", required_argument, NULL, 'w' },
|
||||
{ "quality", required_argument, NULL, 'q' },
|
||||
{ "cpuflags", required_argument, NULL, 'c' },
|
||||
{ "cutoff", required_argument, NULL, 'u' },
|
||||
{ "taps", required_argument, NULL, 't' },
|
||||
{ "param", required_argument, NULL, 'p' },
|
||||
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
|
@ -59,6 +65,7 @@ static const struct option long_options[] = {
|
|||
static void show_usage(const char *name, bool is_error)
|
||||
{
|
||||
FILE *fp;
|
||||
uint32_t i;
|
||||
|
||||
fp = is_error ? stderr : stdout;
|
||||
|
||||
|
|
@ -66,14 +73,31 @@ static void show_usage(const char *name, bool is_error)
|
|||
fprintf(fp,
|
||||
" -h, --help Show this help\n"
|
||||
" -v --verbose Be verbose\n"
|
||||
" -c --cpuflags CPU flags (default 0)\n"
|
||||
"\n");
|
||||
fprintf(fp,
|
||||
" -r --rate Output sample rate (default as input)\n"
|
||||
" -f --format Output sample format %s (default as input)\n"
|
||||
" -f --format Output sample format %s (default as input)\n\n"
|
||||
" -w --window Window function (default %s)\n",
|
||||
STR_FMTS, resample_window_name(RESAMPLE_WINDOW_DEFAULT));
|
||||
for (i = 0; i < SPA_N_ELEMENTS(resample_window_info); i++) {
|
||||
fprintf(fp,
|
||||
" %s: %s\n",
|
||||
resample_window_info[i].label,
|
||||
resample_window_info[i].description);
|
||||
}
|
||||
fprintf(fp,
|
||||
" -q --quality Resampler quality (default %u)\n"
|
||||
" -c --cpuflags CPU flags (default 0)\n"
|
||||
"\n",
|
||||
STR_FMTS, DEFAULT_QUALITY);
|
||||
" -u --cutoff Cutoff frequency [0.0..1.0] (default from quality)\n"
|
||||
" -t --taps Resampler taps (default from quality)\n"
|
||||
" -p --param Resampler param <name>=<value> (default from quality)\n",
|
||||
DEFAULT_QUALITY);
|
||||
for (i = 0; i < SPA_N_ELEMENTS(resample_param_info); i++) {
|
||||
fprintf(fp,
|
||||
" %s\n",
|
||||
resample_param_info[i].label);
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
static inline const char *
|
||||
|
|
@ -163,6 +187,8 @@ static int open_files(struct data *d)
|
|||
d->oname, sf_strerror(NULL));
|
||||
return -EIO;
|
||||
}
|
||||
sf_command(d->ofile, SFC_SET_CLIPPING, NULL, 1);
|
||||
|
||||
if (d->verbose) {
|
||||
fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n",
|
||||
d->iname, d->iinfo.channels, d->iinfo.samplerate,
|
||||
|
|
@ -207,10 +233,23 @@ static int do_conversion(struct data *d)
|
|||
r.i_rate = d->iinfo.samplerate;
|
||||
r.o_rate = d->oinfo.samplerate;
|
||||
r.quality = d->quality < 0 ? DEFAULT_QUALITY : d->quality;
|
||||
r.config = d->config;
|
||||
if ((res = resample_native_init(&r)) < 0) {
|
||||
fprintf(stderr, "can't init converter: %s\n", spa_strerror(res));
|
||||
return res;
|
||||
}
|
||||
if (d->verbose) {
|
||||
fprintf(stdout, "window:%s cutoff:%f n_taps:%u\n",
|
||||
resample_window_name(r.config.window),
|
||||
r.config.cutoff, r.config.n_taps);
|
||||
for (i = 0; i < SPA_N_ELEMENTS(resample_param_info); i++) {
|
||||
if (resample_param_info[i].window != r.config.window)
|
||||
continue;
|
||||
fprintf(stdout, " param:%s %f\n",
|
||||
resample_param_info[i].label,
|
||||
r.config.params[resample_param_info[i].idx]);
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < channels; j++)
|
||||
src[j] = &in[MAX_SAMPLES * j];
|
||||
|
|
@ -259,9 +298,8 @@ static int do_conversion(struct data *d)
|
|||
|
||||
if (pout_len > 0) {
|
||||
for (k = 0, i = 0; i < pout_len; i++) {
|
||||
for (j = 0; j < channels; j++) {
|
||||
for (j = 0; j < channels; j++)
|
||||
obuf[k++] = out[MAX_SAMPLES * j + i];
|
||||
}
|
||||
}
|
||||
pout_len = sf_writef_float(d->ofile, obuf, pout_len);
|
||||
|
||||
|
|
@ -320,6 +358,27 @@ int main(int argc, char *argv[])
|
|||
case 'c':
|
||||
data.cpu_flags = strtol(optarg, NULL, 0);
|
||||
break;
|
||||
case 'u':
|
||||
data.config.cutoff = strtod(optarg, NULL);
|
||||
fprintf(stderr, "%f\n", data.config.cutoff);
|
||||
break;
|
||||
case 'w':
|
||||
data.config.window = resample_window_from_label(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
{
|
||||
char *eq;
|
||||
if ((eq = strchr(optarg, '=')) != NULL) {
|
||||
uint32_t idx;
|
||||
*eq = 0;
|
||||
idx = resample_param_from_label(optarg);
|
||||
data.config.params[idx] = atof(eq+1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 't':
|
||||
data.config.n_taps = atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "error: unknown option '%c'\n", c);
|
||||
goto error_usage;
|
||||
|
|
|
|||
|
|
@ -790,6 +790,7 @@ static int impl_node_process(void *object)
|
|||
uint32_t n_buffers, maxsize;
|
||||
struct buffer **buffers;
|
||||
struct buffer *outb;
|
||||
struct spa_data *d;
|
||||
const void **datas;
|
||||
uint32_t cycle = this->position->clock.cycle & 1;
|
||||
|
||||
|
|
@ -856,12 +857,11 @@ static int impl_node_process(void *object)
|
|||
outport->n_buffers);
|
||||
return -EPIPE;
|
||||
}
|
||||
d = outb->buf.datas;
|
||||
|
||||
if (n_buffers == 1) {
|
||||
if (n_buffers == 1 && SPA_FLAG_IS_SET(d[0].flags, SPA_DATA_FLAG_DYNAMIC)) {
|
||||
*outb->buffer = *buffers[0]->buffer;
|
||||
} else {
|
||||
struct spa_data *d = outb->buf.datas;
|
||||
|
||||
*outb->buffer = outb->buf;
|
||||
|
||||
maxsize = SPA_MIN(maxsize, d[0].maxsize);
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ struct impl {
|
|||
struct props props;
|
||||
struct spa_io_clock *clock;
|
||||
struct spa_io_position *position;
|
||||
bool following;
|
||||
|
||||
struct spa_hook_list hooks;
|
||||
struct spa_callbacks callbacks;
|
||||
|
|
@ -306,6 +307,8 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
|
|||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
this->following = this->position && this->clock && this->position->clock.id != this->clock->id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -422,7 +425,8 @@ static int make_buffer(struct impl *this)
|
|||
|
||||
this->sample_count += n_samples;
|
||||
this->elapsed_time = SAMPLES_TO_TIME(this, this->sample_count);
|
||||
set_timer(this, true);
|
||||
if (!this->following)
|
||||
set_timer(this, true);
|
||||
|
||||
io->buffer_id = b->id;
|
||||
io->status = SPA_STATUS_HAVE_DATA;
|
||||
|
|
@ -475,7 +479,8 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
|
|||
this->elapsed_time = 0;
|
||||
|
||||
this->started = true;
|
||||
set_timer(this, true);
|
||||
if (!this->following)
|
||||
set_timer(this, true);
|
||||
break;
|
||||
}
|
||||
case SPA_NODE_COMMAND_Suspend:
|
||||
|
|
@ -891,7 +896,7 @@ static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t i
|
|||
b->outstanding = false;
|
||||
spa_list_append(&port->empty, &b->link);
|
||||
|
||||
if (!this->props.live)
|
||||
if (!this->props.live && !this->following)
|
||||
set_timer(this, true);
|
||||
}
|
||||
|
||||
|
|
@ -966,7 +971,7 @@ static int impl_node_process(void *object)
|
|||
io->buffer_id = SPA_ID_INVALID;
|
||||
}
|
||||
|
||||
if (!this->props.live)
|
||||
if (!this->props.live || this->following)
|
||||
return make_buffer(this);
|
||||
else
|
||||
return SPA_STATUS_OK;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,8 @@ static int get_valid_aac_bitrate(a2dp_aac_t *conf)
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_aac_t conf;
|
||||
int i;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ aptx_frequencies[] = {
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_aptx_t conf;
|
||||
int i;
|
||||
|
|
@ -146,7 +147,8 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
|||
static int codec_select_config_ll(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_aptx_ll_ext_t conf = { 0 };
|
||||
size_t actual_conf_size;
|
||||
|
|
@ -166,7 +168,7 @@ static int codec_select_config_ll(const struct media_codec *codec, uint32_t flag
|
|||
if (codec->duplex_codec && !conf.base.bidirect_link)
|
||||
return -ENOTSUP;
|
||||
|
||||
if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config)) < 0)
|
||||
if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config, NULL)) < 0)
|
||||
return res;
|
||||
|
||||
memcpy(&conf.base.aptx, config, sizeof(conf.base.aptx));
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ duplex_frequencies[] = {
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_faststream_t conf;
|
||||
int i;
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_lc3plus_hr_t conf;
|
||||
|
||||
|
|
@ -137,8 +138,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
|
|||
int a, b;
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2);
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1, NULL);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2, NULL);
|
||||
|
||||
#define PREFER_EXPR(expr) \
|
||||
do { \
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@ ldac_channel_modes[] = {
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_ldac_t conf;
|
||||
int i;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_opus_g_t conf;
|
||||
int frequency, duration, channels;
|
||||
|
|
@ -126,8 +127,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
|
|||
int a, b;
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1);
|
||||
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2);
|
||||
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL);
|
||||
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL);
|
||||
|
||||
#define PREFER_EXPR(expr) \
|
||||
do { \
|
||||
|
|
|
|||
|
|
@ -590,7 +590,8 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
struct props props;
|
||||
a2dp_opus_05_t conf;
|
||||
|
|
@ -699,8 +700,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
|
|||
int a, b;
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1);
|
||||
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2);
|
||||
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL);
|
||||
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL);
|
||||
|
||||
#define PREFER_EXPR(expr) \
|
||||
do { \
|
||||
|
|
|
|||
|
|
@ -135,7 +135,8 @@ sbc_xq_channel_modes[] = {
|
|||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
a2dp_sbc_t conf;
|
||||
int bitpool, i;
|
||||
|
|
@ -221,8 +222,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
|
|||
bool xq = (spa_streq(codec->name, "sbc_xq"));
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2);
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1, NULL);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2, NULL);
|
||||
|
||||
#define PREFER_EXPR(expr) \
|
||||
do { \
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ struct impl {
|
|||
struct spa_source *ring_timer;
|
||||
void *upower;
|
||||
struct spa_bt_telephony *telephony;
|
||||
bool pts;
|
||||
};
|
||||
|
||||
struct transport_data {
|
||||
|
|
@ -852,7 +853,7 @@ done:
|
|||
if (cfg)
|
||||
libusb_free_config_descriptor(cfg);
|
||||
if (devices)
|
||||
libusb_free_device_list(devices, 0);
|
||||
libusb_free_device_list(devices, true);
|
||||
if (ctx)
|
||||
libusb_exit(ctx);
|
||||
return ok;
|
||||
|
|
@ -1010,6 +1011,7 @@ static void process_hfp_hf_indicator(struct rfcomm *rfcomm, unsigned int indicat
|
|||
|
||||
switch (indicator) {
|
||||
case SPA_BT_HFP_HF_INDICATOR_ENHANCED_SAFETY:
|
||||
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
|
||||
break;
|
||||
case SPA_BT_HFP_HF_INDICATOR_BATTERY_LEVEL:
|
||||
// Battery level is reported in range 0-100
|
||||
|
|
@ -1018,12 +1020,15 @@ static void process_hfp_hf_indicator(struct rfcomm *rfcomm, unsigned int indicat
|
|||
if (value <= 100) {
|
||||
// TODO: report without Battery Provider (using props)
|
||||
spa_bt_device_report_battery_level(rfcomm->device, value);
|
||||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else {
|
||||
spa_log_warn(backend->log, "battery HF indicator %u outside of range [0, 100]: %u", indicator, value);
|
||||
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
spa_log_warn(backend->log, "unknown HF indicator:%u value:%u", indicator, value);
|
||||
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1307,7 +1312,7 @@ next_indicator:
|
|||
type = INTERNATIONAL_NUMBER;
|
||||
else
|
||||
type = NATIONAL_NUMBER;
|
||||
rfcomm_send_reply(rfcomm, "+CNUM: ,\"%s\",%u", backend->modem.own_number, type);
|
||||
rfcomm_send_reply(rfcomm, "+CNUM: ,\"%s\",%u,,4", backend->modem.own_number, type);
|
||||
}
|
||||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else if (spa_strstartswith(buf, "AT+COPS=")) {
|
||||
|
|
@ -1350,6 +1355,7 @@ next_indicator:
|
|||
rfcomm_send_reply(rfcomm, "+BIND: (2)");
|
||||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else if (spa_strstartswith(buf, "AT+BIND?")) {
|
||||
rfcomm_send_reply(rfcomm, "+BIND: 1,0");
|
||||
rfcomm_send_reply(rfcomm, "+BIND: 2,1");
|
||||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else if (spa_strstartswith(buf, "AT+BIND=")) {
|
||||
|
|
@ -1359,7 +1365,6 @@ next_indicator:
|
|||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else if (sscanf(buf, "AT+BIEV=%u,%u", &indicator, &indicator_value) == 2) {
|
||||
process_hfp_hf_indicator(rfcomm, indicator, indicator_value);
|
||||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else if (sscanf(buf, "AT+XAPL=%04x-%04x-%*[^,],%u", &xapl_vendor, &xapl_product, &xapl_features) == 3) {
|
||||
if (xapl_features & SPA_BT_HFP_HF_XAPL_FEATURE_BATTERY_REPORTING) {
|
||||
/* claim, that we support battery status reports */
|
||||
|
|
@ -1434,6 +1439,15 @@ next_indicator:
|
|||
rfcomm_send_error(rfcomm, error);
|
||||
return true;
|
||||
}
|
||||
} else if (spa_strstartswith(buf, "AT+BLDN") && backend->pts) {
|
||||
enum cmee_error error;
|
||||
|
||||
/* For PTS tests HFP/AG/OCL/BV-01-C and HFP/AG/OCL/BV-02-C, fake last dial
|
||||
* number by calling first memory */
|
||||
if (!mm_do_call(backend->modemmanager, ">1", rfcomm, &error)) {
|
||||
rfcomm_send_error(rfcomm, error);
|
||||
return true;
|
||||
}
|
||||
} else if (spa_strstartswith(buf, "AT+CHUP")) {
|
||||
enum cmee_error error;
|
||||
|
||||
|
|
@ -2759,7 +2773,8 @@ static int sco_acquire_cb(void *data, bool optional)
|
|||
goto fail;
|
||||
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||
rfcomm_hfp_ag_set_cind(td->rfcomm, true);
|
||||
if (!td->rfcomm->device->disable_dummy_call)
|
||||
rfcomm_hfp_ag_set_cind(td->rfcomm, true);
|
||||
#endif
|
||||
|
||||
t->fd = sock;
|
||||
|
|
@ -2813,7 +2828,8 @@ static int sco_release_cb(void *data)
|
|||
spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE);
|
||||
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||
rfcomm_hfp_ag_set_cind(td->rfcomm, false);
|
||||
if (!td->rfcomm->device->disable_dummy_call)
|
||||
rfcomm_hfp_ag_set_cind(td->rfcomm, false);
|
||||
#endif
|
||||
|
||||
sco_destroy_cb(t);
|
||||
|
|
@ -2926,7 +2942,11 @@ static void sco_listen_event(struct spa_source *source)
|
|||
|
||||
/* Find transport for local and remote address */
|
||||
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
|
||||
if ((rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) &&
|
||||
/* Audio connection is allowed from both side with legacy peer, i.e. HSP or codec negotion not supported
|
||||
* (except if PTS workaround has been enabled in which case audio coonection is allowed as for HSP),
|
||||
* or only from the HFP Audio Gateway. */
|
||||
if ((((!rfcomm->codec_negotiation_supported || backend->pts) && (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO)) ||
|
||||
(rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) &&
|
||||
rfcomm->transport &&
|
||||
spa_streq(rfcomm->device->address, remote_address) &&
|
||||
spa_streq(rfcomm->device->adapter->address, local_address)) {
|
||||
|
|
@ -2940,7 +2960,7 @@ static void sco_listen_event(struct spa_source *source)
|
|||
return;
|
||||
}
|
||||
|
||||
spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY);
|
||||
spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO);
|
||||
|
||||
if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) {
|
||||
spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true");
|
||||
|
|
@ -3381,6 +3401,43 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
|
|||
}
|
||||
spa_bt_device_add_profile(d, profile);
|
||||
|
||||
/* Prevent to connect HSP/HFP in both directions, i.e. HS->AG and AG->HS.
|
||||
* This may only occur when connecting to a device which provides both
|
||||
* HS and AG which should not be the case with headsets and phones. */
|
||||
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
|
||||
if (spa_streq(rfcomm->device->address, d->address) &&
|
||||
spa_streq(rfcomm->device->adapter->address, d->adapter->address)) {
|
||||
bool connected = false;
|
||||
|
||||
switch (profile) {
|
||||
case SPA_BT_PROFILE_HFP_HF:
|
||||
if (rfcomm->profile == SPA_BT_PROFILE_HFP_AG)
|
||||
connected = true;
|
||||
break;
|
||||
case SPA_BT_PROFILE_HFP_AG:
|
||||
if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF)
|
||||
connected = true;
|
||||
break;
|
||||
case SPA_BT_PROFILE_HSP_HS:
|
||||
if (rfcomm->profile == SPA_BT_PROFILE_HSP_AG)
|
||||
connected = true;
|
||||
break;
|
||||
case SPA_BT_PROFILE_HSP_AG:
|
||||
if (rfcomm->profile == SPA_BT_PROFILE_HSP_HS)
|
||||
connected = true;
|
||||
break;
|
||||
default:
|
||||
spa_log_warn(backend->log, "Unsupported profile: %s", handler);
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
spa_log_debug(backend->log, "Already connected in the opposite direction");
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbus_message_iter_next(&it);
|
||||
dbus_message_iter_get_basic(&it, &fd);
|
||||
|
||||
|
|
@ -4011,6 +4068,16 @@ static void parse_hfp_disable_nrec(struct impl *backend, const struct spa_dict *
|
|||
backend->hfp_disable_nrec = false;
|
||||
}
|
||||
|
||||
static void parse_hfp_pts(struct impl *backend, const struct spa_dict *info)
|
||||
{
|
||||
const char *str;
|
||||
|
||||
if ((str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-pts")) != NULL)
|
||||
backend->pts = spa_atob(str);
|
||||
else
|
||||
backend->pts = false;
|
||||
}
|
||||
|
||||
static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dict *info)
|
||||
{
|
||||
const char *str;
|
||||
|
|
@ -4095,6 +4162,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
|||
|
||||
parse_hfp_disable_nrec(backend, info);
|
||||
parse_hfp_default_volumes(backend, info);
|
||||
parse_hfp_pts(backend, info);
|
||||
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
|
||||
if (!dbus_connection_register_object_path(backend->conn,
|
||||
|
|
|
|||
|
|
@ -82,6 +82,22 @@
|
|||
|
||||
#define LC3_MAX_CHANNELS 28
|
||||
|
||||
/* Metadata types */
|
||||
#define BAP_META_TYPE_PREFERRED_CONTEXT 0x01
|
||||
#define BAP_META_TYPE_STREAMING_CONTEXT 0x02
|
||||
#define BAP_META_TYPE_PROGRAM_INFO 0x03
|
||||
#define BAP_META_TYPE_LANGUAGE 0x04
|
||||
#define BAP_META_TYPE_CCID_LIST 0x05
|
||||
#define BAP_META_TYPE_PARENTAL_RATING 0x06
|
||||
#define BAP_META_TYPE_PROGRAM_INFO_URI 0x07
|
||||
#define BAP_META_TYPE_AUDIO_ACTIVE_STATE 0x08
|
||||
#define BAP_META_TYPE_BCAST_IMMEDIATE 0x09
|
||||
#define BAP_META_TYPE_ASSISTED_LISTENING 0x0a
|
||||
#define BAP_META_TYPE_BCAST_NAME 0x0b
|
||||
#define BAP_META_TYPE_EXTENDED 0xfe
|
||||
#define BAP_META_TYPE_VENDOR 0xff
|
||||
|
||||
|
||||
#define BAP_CHANNEL_MONO 0x00000000 /* mono */
|
||||
#define BAP_CHANNEL_FL 0x00000001 /* front left */
|
||||
#define BAP_CHANNEL_FR 0x00000002 /* front right */
|
||||
|
|
@ -137,11 +153,68 @@
|
|||
#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02
|
||||
#define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03
|
||||
|
||||
struct __attribute__((packed)) ltv {
|
||||
uint8_t len;
|
||||
uint8_t type;
|
||||
uint8_t value[];
|
||||
};
|
||||
|
||||
#define BT_TMAP_UUID "00001855-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
#define BT_TMAP_ROLE_CG_STR "cg"
|
||||
#define BT_TMAP_ROLE_CT_STR "ct"
|
||||
#define BT_TMAP_ROLE_UMS_STR "ums"
|
||||
#define BT_TMAP_ROLE_UMR_STR "umr"
|
||||
#define BT_TMAP_ROLE_BMS_STR "bms"
|
||||
#define BT_TMAP_ROLE_BMR_STR "bmr"
|
||||
|
||||
#define BT_GMAP_ROLE_UGG_STR "ugg"
|
||||
#define BT_GMAP_ROLE_UGT_STR "ugt"
|
||||
#define BT_GMAP_ROLE_BGS_STR "bgs"
|
||||
#define BT_GMAP_ROLE_BGR_STR "bgr"
|
||||
|
||||
#define BT_TMAP_ROLE_LIST(role) \
|
||||
role(BT_TMAP_ROLE_CG) \
|
||||
role(BT_TMAP_ROLE_CT) \
|
||||
role(BT_TMAP_ROLE_UMS) \
|
||||
role(BT_TMAP_ROLE_UMR) \
|
||||
role(BT_TMAP_ROLE_BMS) \
|
||||
role(BT_TMAP_ROLE_BMR)
|
||||
|
||||
#define BT_GMAP_UUID "00001858-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
#define BT_GMAP_UGG_MULTIPLEX_STR "ugg-multiplex"
|
||||
#define BT_GMAP_UGG_96KBPS_SOURCE_STR "ugg-96kbps-source"
|
||||
#define BT_GMAP_UGG_MULTISINK_STR "ugg-multisink"
|
||||
|
||||
#define BT_GMAP_UGT_SOURCE_STR "ugt-source"
|
||||
#define BT_GMAP_UGT_80KBPS_SOURCE_STR "ugt-80kbps-source"
|
||||
#define BT_GMAP_UGT_SINK_STR "ugt-sink"
|
||||
#define BT_GMAP_UGT_64KBPS_SINK_STR "ugt-64kbps-sink"
|
||||
#define BT_GMAP_UGT_MULTIPLEX_STR "ugt-multiplex"
|
||||
#define BT_GMAP_UGT_MULTISINK_STR "ugt-multisink"
|
||||
#define BT_GMAP_UGT_MULTISOURCE_STR "ugt-multisource"
|
||||
|
||||
#define BT_GMAP_BGS_96KBPS_STR "bgs-96kbps"
|
||||
|
||||
#define BT_GMAP_BGR_MULTISINK_STR "bgr-multisink"
|
||||
#define BT_GMAP_BGR_MULTIPLEX_STR "bgr-multiplex"
|
||||
|
||||
#define BT_GMAP_ROLE_LIST(role) \
|
||||
role(BT_GMAP_ROLE_UGG) \
|
||||
role(BT_GMAP_ROLE_UGT) \
|
||||
role(BT_GMAP_ROLE_BGS) \
|
||||
role(BT_GMAP_ROLE_BGR)
|
||||
|
||||
#define BT_GMAP_FEATURE_LIST(feature) \
|
||||
feature(BT_GMAP_UGG_MULTIPLEX) \
|
||||
feature(BT_GMAP_UGG_96KBPS_SOURCE) \
|
||||
feature(BT_GMAP_UGG_MULTISINK) \
|
||||
feature(BT_GMAP_UGT_SOURCE) \
|
||||
feature(BT_GMAP_UGT_80KBPS_SOURCE) \
|
||||
feature(BT_GMAP_UGT_SINK) \
|
||||
feature(BT_GMAP_UGT_64KBPS_SINK) \
|
||||
feature(BT_GMAP_UGT_MULTIPLEX) \
|
||||
feature(BT_GMAP_UGT_MULTISINK) \
|
||||
feature(BT_GMAP_UGT_MULTISOURCE) \
|
||||
feature(BT_GMAP_BGS_96KBPS) \
|
||||
feature(BT_GMAP_BGR_MULTISINK) \
|
||||
feature(BT_GMAP_BGR_MULTIPLEX)
|
||||
|
||||
struct bap_endpoint_qos {
|
||||
uint8_t framing;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <spa/param/audio/format-utils.h>
|
||||
#include <spa/utils/string.h>
|
||||
#include <spa/utils/json.h>
|
||||
#include <spa/utils/cleanup.h>
|
||||
#include <spa/debug/log.h>
|
||||
|
||||
#include <lc3.h>
|
||||
|
|
@ -45,9 +46,11 @@ struct impl {
|
|||
struct settings {
|
||||
uint32_t locations;
|
||||
uint32_t channel_allocation;
|
||||
uint16_t supported_context;
|
||||
uint16_t available_context;
|
||||
bool sink;
|
||||
bool duplex;
|
||||
const char *qos_name;
|
||||
char *qos_name;
|
||||
int retransmission;
|
||||
int latency;
|
||||
int64_t delay;
|
||||
|
|
@ -55,8 +58,10 @@ struct settings {
|
|||
};
|
||||
|
||||
struct pac_data {
|
||||
const uint8_t *data;
|
||||
const void *data;
|
||||
size_t size;
|
||||
const void *metadata;
|
||||
size_t metadata_size;
|
||||
int index;
|
||||
const struct settings *settings;
|
||||
};
|
||||
|
|
@ -81,9 +86,16 @@ typedef struct {
|
|||
uint8_t n_blks;
|
||||
bool sink;
|
||||
bool duplex;
|
||||
uint16_t preferred_context;
|
||||
unsigned int priority;
|
||||
} bap_lc3_t;
|
||||
|
||||
struct config_data {
|
||||
bap_lc3_t conf;
|
||||
int pac_index;
|
||||
struct settings settings;
|
||||
};
|
||||
|
||||
#define BAP_QOS(name_, rate_, duration_, framing_, framelen_, rtn_, latency_, delay_, priority_) \
|
||||
((struct bap_qos){ .name = (name_), .rate = (rate_), .frame_duration = (duration_), .framing = (framing_), \
|
||||
.framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \
|
||||
|
|
@ -191,32 +203,6 @@ static unsigned int get_duration_mask(uint8_t rate) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int write_ltv(uint8_t *dest, uint8_t type, void* value, size_t len)
|
||||
{
|
||||
struct ltv *ltv = (struct ltv *)dest;
|
||||
|
||||
ltv->len = len + 1;
|
||||
ltv->type = type;
|
||||
memcpy(ltv->value, value, len);
|
||||
|
||||
return len + 2;
|
||||
}
|
||||
|
||||
static int write_ltv_uint8(uint8_t *dest, uint8_t type, uint8_t value)
|
||||
{
|
||||
return write_ltv(dest, type, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static int write_ltv_uint16(uint8_t *dest, uint8_t type, uint16_t value)
|
||||
{
|
||||
return write_ltv(dest, type, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static int write_ltv_uint32(uint8_t *dest, uint8_t type, uint32_t value)
|
||||
{
|
||||
return write_ltv(dest, type, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static uint16_t parse_rates(const char *str)
|
||||
{
|
||||
struct spa_json it;
|
||||
|
|
@ -319,7 +305,6 @@ static uint8_t parse_channel_counts(const char *str)
|
|||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
uint8_t *data = caps;
|
||||
const char *str;
|
||||
uint16_t framelen[2];
|
||||
uint16_t rate_mask = LC3_FREQ_48KHZ | LC3_FREQ_44KHZ | LC3_FREQ_32KHZ | \
|
||||
|
|
@ -330,6 +315,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
|||
uint16_t framelen_max = LC3_MAX_FRAME_BYTES;
|
||||
uint8_t max_frames = 2;
|
||||
uint32_t value;
|
||||
struct ltv_writer writer = LTV_WRITER(caps, A2DP_MAX_CAPS_SIZE);
|
||||
|
||||
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.rates")))
|
||||
rate_mask = parse_rates(str);
|
||||
|
|
@ -355,17 +341,17 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
|||
framelen[0] = htobs(framelen_min);
|
||||
framelen[1] = htobs(framelen_max);
|
||||
|
||||
data += write_ltv_uint16(data, LC3_TYPE_FREQ, htobs(rate_mask));
|
||||
data += write_ltv_uint8(data, LC3_TYPE_DUR, duration_mask);
|
||||
data += write_ltv_uint8(data, LC3_TYPE_CHAN, channel_counts);
|
||||
data += write_ltv(data, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen));
|
||||
ltv_writer_uint16(&writer, LC3_TYPE_FREQ, rate_mask);
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_DUR, duration_mask);
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_CHAN, channel_counts);
|
||||
ltv_writer_data(&writer, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen));
|
||||
/* XXX: we support only one frame block -> max 2 frames per SDU */
|
||||
if (max_frames > 2)
|
||||
max_frames = 2;
|
||||
|
||||
data += write_ltv_uint8(data, LC3_TYPE_BLKS, max_frames);
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_BLKS, max_frames);
|
||||
|
||||
return data - caps;
|
||||
return ltv_writer_end(&writer);
|
||||
}
|
||||
|
||||
static void debugc_ltv(struct spa_debug_context *debug_ctx, int pac, struct ltv *ltv)
|
||||
|
|
@ -391,7 +377,7 @@ static void debugc_ltv(struct spa_debug_context *debug_ctx, int pac, struct ltv
|
|||
}
|
||||
}
|
||||
|
||||
static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS],
|
||||
static int parse_bluez_pacs_data(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS],
|
||||
struct spa_debug_context *debug_ctx)
|
||||
{
|
||||
/*
|
||||
|
|
@ -400,7 +386,7 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
|
|||
*/
|
||||
int pac = 0;
|
||||
|
||||
pacs[pac] = (struct pac_data){ data, 0 };
|
||||
pacs[pac] = (struct pac_data){ .data = data };
|
||||
|
||||
while (data_size > 0) {
|
||||
struct ltv *ltv = (struct ltv *)data;
|
||||
|
|
@ -411,7 +397,7 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
|
|||
break;
|
||||
|
||||
++pac;
|
||||
pacs[pac] = (struct pac_data){ data + 1, 0, pac };
|
||||
pacs[pac] = (struct pac_data){ .data = data + 1, .index = pac };
|
||||
} else if (ltv->len >= data_size) {
|
||||
return -EINVAL;
|
||||
} else {
|
||||
|
|
@ -425,6 +411,28 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
|
|||
return pac + 1;
|
||||
}
|
||||
|
||||
static int parse_bluez_pacs(const uint8_t *data, size_t data_size,
|
||||
const uint8_t *metadata, size_t metadata_size, struct pac_data pacs[MAX_PACS],
|
||||
struct spa_debug_context *debug_ctx)
|
||||
{
|
||||
struct pac_data meta[MAX_PACS];
|
||||
int pac_count, meta_count;
|
||||
|
||||
pac_count = parse_bluez_pacs_data(data, data_size, pacs, debug_ctx);
|
||||
if (pac_count < 0)
|
||||
return pac_count;
|
||||
|
||||
meta_count = parse_bluez_pacs_data(metadata, metadata_size, meta, debug_ctx);
|
||||
if (meta_count == pac_count) {
|
||||
for (int i = 0; i < pac_count; ++i) {
|
||||
pacs[i].metadata = meta[i].data;
|
||||
pacs[i].metadata_size = meta[i].size;
|
||||
}
|
||||
}
|
||||
|
||||
return pac_count;
|
||||
}
|
||||
|
||||
static uint8_t get_channel_count(uint32_t channels)
|
||||
{
|
||||
uint8_t num;
|
||||
|
|
@ -549,7 +557,7 @@ static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t
|
|||
|
||||
static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx)
|
||||
{
|
||||
const uint8_t *data = pac->data;
|
||||
const void *data = pac->data;
|
||||
size_t data_size = pac->size;
|
||||
uint16_t framelen_min = 0, framelen_max = 0;
|
||||
int max_frames = -1;
|
||||
|
|
@ -557,9 +565,11 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
uint8_t max_channels = 0;
|
||||
uint8_t duration_mask = 0;
|
||||
uint16_t rate_mask = 0;
|
||||
uint16_t preferred_context = 0;
|
||||
struct bap_qos bap_qos;
|
||||
unsigned int i;
|
||||
bool found = false;
|
||||
const struct ltv *ltv;
|
||||
|
||||
if (!data_size)
|
||||
return false;
|
||||
|
|
@ -571,14 +581,7 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
/* XXX: we always use one frame block */
|
||||
conf->n_blks = 1;
|
||||
|
||||
while (data_size > 0) {
|
||||
struct ltv *ltv = (struct ltv *)data;
|
||||
|
||||
if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size) {
|
||||
spa_debugc(debug_ctx, "invalid LTV data");
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((ltv = ltv_next(&data, &data_size))) {
|
||||
switch (ltv->type) {
|
||||
case LC3_TYPE_FREQ:
|
||||
spa_return_val_if_fail(ltv->len == 3, false);
|
||||
|
|
@ -605,9 +608,25 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
spa_debugc(debug_ctx, "unknown LTV type: 0x%02x", ltv->type);
|
||||
break;
|
||||
}
|
||||
data_size -= ltv->len + 1;
|
||||
data += ltv->len + 1;
|
||||
}
|
||||
if (data) {
|
||||
spa_debugc(debug_ctx, "invalid LTV data");
|
||||
return false;
|
||||
}
|
||||
|
||||
data = pac->metadata;
|
||||
data_size = pac->metadata_size;
|
||||
while ((ltv = ltv_next(&data, &data_size))) {
|
||||
switch (ltv->type) {
|
||||
case BAP_META_TYPE_PREFERRED_CONTEXT:
|
||||
if (ltv->len != 3)
|
||||
break;
|
||||
preferred_context = ltv->value[0] + (ltv->value[1] << 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data)
|
||||
spa_debugc(debug_ctx, "malformed metadata");
|
||||
|
||||
for (i = 0; i < 8; ++i)
|
||||
if (channel_counts & (1u << i))
|
||||
|
|
@ -646,8 +665,8 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
* and reassemble SDUs as needed.
|
||||
*/
|
||||
if (pac->settings->duplex) {
|
||||
/* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, so prefer
|
||||
* it or 32kHz for now for input rate in duplex configuration.
|
||||
/* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, and 32KHz in TMAP,
|
||||
* so prefer those for now for input rate in duplex configuration.
|
||||
*
|
||||
* It appears few devices support 48kHz out + input, so in duplex mode
|
||||
* try 32 kHz or 16 kHz also for output direction.
|
||||
|
|
@ -670,12 +689,15 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
|
|||
conf->frame_duration = bap_qos.frame_duration;
|
||||
conf->framelen = bap_qos.framelen;
|
||||
conf->priority = bap_qos.priority;
|
||||
conf->preferred_context = preferred_context;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
|
||||
static bool parse_conf(bap_lc3_t *conf, const void *data, size_t data_size)
|
||||
{
|
||||
const struct ltv *ltv;
|
||||
|
||||
if (!data_size)
|
||||
return false;
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
|
|
@ -685,12 +707,7 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
|
|||
/* Absent Codec_Frame_Blocks_Per_SDU means 0x1 (BAP v1.0.1 Sec 4.3.2) */
|
||||
conf->n_blks = 1;
|
||||
|
||||
while (data_size > 0) {
|
||||
struct ltv *ltv = (struct ltv *)data;
|
||||
|
||||
if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size)
|
||||
return false;
|
||||
|
||||
while ((ltv = ltv_next(&data, &data_size))) {
|
||||
switch (ltv->type) {
|
||||
case LC3_TYPE_FREQ:
|
||||
spa_return_val_if_fail(ltv->len == 2, false);
|
||||
|
|
@ -718,9 +735,9 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
|
|||
default:
|
||||
return false;
|
||||
}
|
||||
data_size -= ltv->len + 1;
|
||||
data += ltv->len + 1;
|
||||
}
|
||||
if (data)
|
||||
return false;
|
||||
|
||||
if (conf->frame_duration == 0xFF || !conf->rate)
|
||||
return false;
|
||||
|
|
@ -728,7 +745,32 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
|
|||
return true;
|
||||
}
|
||||
|
||||
static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2)
|
||||
static uint16_t get_wanted_context(const struct settings *settings)
|
||||
{
|
||||
uint16_t context;
|
||||
|
||||
/* Stick with contexts specified in TMAP. Anything else is probably crapshoot due
|
||||
* to interesting device firmware behavior.
|
||||
*
|
||||
* Eg. some Earfun firmwares fail if we set MEDIA | CONVERSATIONAL, other versions
|
||||
* disconnect if LIVE is set, Samsung Galaxy Buds fail on microphone Enable if
|
||||
* CONVERSATIONAL is not set.
|
||||
*/
|
||||
if (settings->sink || settings->duplex)
|
||||
context = BAP_CONTEXT_CONVERSATIONAL;
|
||||
else
|
||||
context = BAP_CONTEXT_MEDIA;
|
||||
|
||||
/* CAP v1.0.1 Sec 7.3.1.2.1: drop contexts if not available, otherwise unspecified */
|
||||
context &= settings->available_context;
|
||||
if (!context)
|
||||
context = BAP_CONTEXT_UNSPECIFIED & settings->available_context;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2,
|
||||
const struct settings *settings)
|
||||
{
|
||||
const bap_lc3_t *conf;
|
||||
int a, b;
|
||||
|
|
@ -753,6 +795,9 @@ static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, in
|
|||
|
||||
PREFER_EXPR(conf->priority == UINT_MAX);
|
||||
|
||||
/* CAP v1.0.1 Sec 7.3.1.2.4: should use PAC preferred context if possible */
|
||||
PREFER_BOOL(conf->preferred_context & get_wanted_context(settings));
|
||||
|
||||
PREFER_BOOL(conf->channels & LC3_CHAN_2);
|
||||
PREFER_BOOL(conf->channels & LC3_CHAN_1);
|
||||
|
||||
|
|
@ -778,7 +823,7 @@ static int pac_cmp(const void *p1, const void *p2)
|
|||
res1 = select_config(&conf1, pac1, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL;
|
||||
res2 = select_config(&conf2, pac2, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL;
|
||||
|
||||
return conf_cmp(&conf1, res1, &conf2, res2);
|
||||
return conf_cmp(&conf1, res1, &conf2, res2, pac1->settings);
|
||||
}
|
||||
|
||||
static void parse_settings(struct settings *s, const struct spa_dict *settings,
|
||||
|
|
@ -797,7 +842,7 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
|
|||
return;
|
||||
|
||||
if ((str = spa_dict_lookup(settings, "bluez5.bap.preset")))
|
||||
s->qos_name = str;
|
||||
s->qos_name = strdup(str);
|
||||
|
||||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.rtn"), &value, 0))
|
||||
s->retransmission = value;
|
||||
|
|
@ -817,12 +862,18 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
|
|||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.channel-allocation"), &value, 0))
|
||||
s->channel_allocation = value;
|
||||
|
||||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.supported-context"), &value, 0))
|
||||
s->supported_context = value;
|
||||
|
||||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.available-context"), &value, 0))
|
||||
s->available_context = value;
|
||||
|
||||
if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug")))
|
||||
*debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_DEBUG);
|
||||
else
|
||||
*debug_ctx = SPA_LOG_DEBUG_INIT(NULL, SPA_LOG_LEVEL_TRACE);
|
||||
|
||||
/* Is remote endpoint sink or source */
|
||||
/* Is local endpoint sink or source */
|
||||
s->sink = spa_atob(spa_dict_lookup(settings, "bluez5.bap.sink"));
|
||||
|
||||
/* Is remote endpoint duplex */
|
||||
|
|
@ -837,26 +888,48 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
|
|||
(int)s->sink, (int)s->duplex);
|
||||
}
|
||||
|
||||
static void free_config_data(struct config_data *d)
|
||||
{
|
||||
if (!d)
|
||||
return;
|
||||
free(d->settings.qos_name);
|
||||
free(d);
|
||||
}
|
||||
|
||||
SPA_DEFINE_AUTOPTR_CLEANUP(config_data, struct config_data, { spa_clear_ptr(*thing, free_config_data); });
|
||||
|
||||
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data)
|
||||
{
|
||||
struct pac_data pacs[MAX_PACS];
|
||||
int npacs;
|
||||
bap_lc3_t conf;
|
||||
uint8_t *data = config;
|
||||
struct spa_debug_log_ctx debug_ctx;
|
||||
struct settings s;
|
||||
int i;
|
||||
spa_autoptr(config_data) d = NULL;
|
||||
int i, ret;
|
||||
struct ltv_writer writer = LTV_WRITER(config, A2DP_MAX_CAPS_SIZE);
|
||||
const void *metadata = NULL;
|
||||
uint32_t metadata_len = 0;
|
||||
|
||||
if (caps == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
parse_settings(&s, settings, &debug_ctx);
|
||||
d = calloc(1, sizeof(*d));
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
parse_settings(&d->settings, settings, &debug_ctx);
|
||||
|
||||
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.metadata"), &metadata_len, 0))
|
||||
metadata = spa_dict_lookup(settings, "bluez5.bap.metadata");
|
||||
if (!metadata)
|
||||
metadata_len = 0;
|
||||
|
||||
/* Select best conf from those possible */
|
||||
npacs = parse_bluez_pacs(caps, caps_size, pacs, &debug_ctx.ctx);
|
||||
npacs = parse_bluez_pacs(caps, caps_size, metadata, metadata_len, pacs, &debug_ctx.ctx);
|
||||
if (npacs < 0) {
|
||||
spa_debugc(&debug_ctx.ctx, "malformed PACS");
|
||||
return npacs;
|
||||
|
|
@ -866,7 +939,7 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
|||
}
|
||||
|
||||
for (i = 0; i < npacs; ++i)
|
||||
pacs[i].settings = &s;
|
||||
pacs[i].settings = &d->settings;
|
||||
|
||||
qsort(pacs, npacs, sizeof(struct pac_data), pac_cmp);
|
||||
|
||||
|
|
@ -875,30 +948,47 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
|||
if (!select_config(&conf, &pacs[0], &debug_ctx.ctx))
|
||||
return -ENOTSUP;
|
||||
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate);
|
||||
data += write_ltv_uint8(data, LC3_TYPE_DUR, conf.frame_duration);
|
||||
d->conf = conf;
|
||||
d->pac_index = pacs[0].index;
|
||||
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_FREQ, conf.rate);
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_DUR, conf.frame_duration);
|
||||
|
||||
/* Indicate MONO with absent Audio_Channel_Allocation (BAP v1.0.1 Sec. 4.3.2) */
|
||||
if (conf.channels != 0)
|
||||
data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(conf.channels));
|
||||
ltv_writer_uint32(&writer, LC3_TYPE_CHAN, conf.channels);
|
||||
|
||||
data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(conf.framelen));
|
||||
data += write_ltv_uint8(data, LC3_TYPE_BLKS, conf.n_blks);
|
||||
ltv_writer_uint16(&writer, LC3_TYPE_FRAMELEN, conf.framelen);
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_BLKS, conf.n_blks);
|
||||
|
||||
return data - config;
|
||||
ret = ltv_writer_end(&writer);
|
||||
|
||||
if (ret >= 0 && config_data)
|
||||
*config_data = spa_steal_ptr(d);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size,
|
||||
const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings)
|
||||
{
|
||||
bap_lc3_t conf1, conf2;
|
||||
int res1, res2;
|
||||
int res1, res2, res;
|
||||
void *data1 = NULL;
|
||||
void *data2 = NULL;
|
||||
const struct config_data *d;
|
||||
|
||||
/* Order selected configurations by preference */
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2);
|
||||
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, &data1);
|
||||
res2 = codec->select_config(codec, 0, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, &data2);
|
||||
|
||||
return conf_cmp(&conf1, res1, &conf2, res2);
|
||||
d = data1 ? data1 : data2;
|
||||
res = conf_cmp(&conf1, res1, &conf2, res2, d ? &d->settings : NULL);
|
||||
|
||||
codec->free_config_data(codec, data1);
|
||||
codec->free_config_data(codec, data2);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint8_t channels_to_positions(uint32_t channels, uint32_t *position, uint32_t max_position)
|
||||
|
|
@ -1062,24 +1152,23 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
|
|||
}
|
||||
|
||||
static int codec_get_qos(const struct media_codec *codec,
|
||||
const void *config, size_t config_size,
|
||||
const struct bap_endpoint_qos *endpoint_qos,
|
||||
struct bap_codec_qos *qos, const struct spa_dict *settings)
|
||||
const void *config_data,
|
||||
struct bap_codec_qos *qos)
|
||||
{
|
||||
struct bap_qos bap_qos;
|
||||
bap_lc3_t conf;
|
||||
bool found = false;
|
||||
struct settings s;
|
||||
struct spa_debug_log_ctx debug_ctx;
|
||||
const struct config_data *d = config_data;
|
||||
|
||||
spa_zero(*qos);
|
||||
|
||||
if (!parse_conf(&conf, config, config_size))
|
||||
if (!d)
|
||||
return -EINVAL;
|
||||
|
||||
parse_settings(&s, settings, &debug_ctx);
|
||||
conf = d->conf;
|
||||
|
||||
found = select_bap_qos(&bap_qos, &s, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration),
|
||||
found = select_bap_qos(&bap_qos, &d->settings, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration),
|
||||
conf.framelen, conf.framelen);
|
||||
if (!found) {
|
||||
/* shouldn't happen: select_config should pick existing one */
|
||||
|
|
@ -1122,6 +1211,27 @@ static int codec_get_qos(const struct media_codec *codec,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int codec_get_metadata(const struct media_codec *codec, const void *config_data,
|
||||
uint8_t *meta, size_t meta_max_size)
|
||||
{
|
||||
const struct config_data *d = config_data;
|
||||
struct ltv_writer writer = LTV_WRITER(meta, meta_max_size);
|
||||
uint16_t ctx;
|
||||
|
||||
ctx = get_wanted_context(&d->settings);
|
||||
if (!ctx)
|
||||
ctx = BAP_CONTEXT_UNSPECIFIED;
|
||||
|
||||
ltv_writer_uint16(&writer, BAP_META_TYPE_STREAMING_CONTEXT, ctx);
|
||||
|
||||
return ltv_writer_end(&writer);
|
||||
}
|
||||
|
||||
static void codec_free_config_data(const struct media_codec *codec, void *config_data)
|
||||
{
|
||||
free_config_data(config_data);
|
||||
}
|
||||
|
||||
static void *codec_init(const struct media_codec *codec, uint32_t flags,
|
||||
void *config, size_t config_len, const struct spa_audio_info *info,
|
||||
void *props, size_t mtu)
|
||||
|
|
@ -1378,80 +1488,60 @@ static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps,
|
|||
uint8_t *caps_size, struct spa_dict *settings,
|
||||
struct bap_codec_qos *qos)
|
||||
{
|
||||
int index = 0x0;
|
||||
bool preset_found = false;
|
||||
const char *preset = NULL;
|
||||
const char *preset_name = NULL;
|
||||
int channel_allocation = 0;
|
||||
uint8_t *data = caps;
|
||||
int i, ret;
|
||||
struct ltv_writer writer = LTV_WRITER(caps, *caps_size);
|
||||
const struct bap_qos *preset = NULL;
|
||||
|
||||
*caps_size = 0;
|
||||
int i;
|
||||
|
||||
if (settings) {
|
||||
for (i = 0; i < (int)settings->n_items; ++i) {
|
||||
if (spa_streq(settings->items[i].key, "channel_allocation"))
|
||||
sscanf(settings->items[i].value, "%"PRIu32, &channel_allocation);
|
||||
if (spa_streq(settings->items[i].key, "preset"))
|
||||
preset = spa_dict_lookup(settings, "preset");
|
||||
preset_name = settings->items[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
if (preset == NULL)
|
||||
if (preset_name == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
SPA_FOR_EACH_ELEMENT_VAR(bap_bcast_qos_configs, c) {
|
||||
if (spa_streq(c->name, preset)) {
|
||||
preset_found = true;
|
||||
if (spa_streq(c->name, preset_name)) {
|
||||
preset = c;
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!preset_found)
|
||||
if (!preset)
|
||||
return -EINVAL;
|
||||
|
||||
switch (bap_bcast_qos_configs[index].rate) {
|
||||
case LC3_CONFIG_FREQ_48KHZ:
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_48KHZ);
|
||||
break;
|
||||
case LC3_CONFIG_FREQ_44KHZ:
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_44KHZ);
|
||||
break;
|
||||
case LC3_CONFIG_FREQ_32KHZ:
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_32KHZ);
|
||||
break;
|
||||
case LC3_CONFIG_FREQ_24KHZ:
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_24KHZ);
|
||||
break;
|
||||
case LC3_CONFIG_FREQ_16KHZ:
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_16KHZ);
|
||||
break;
|
||||
case LC3_CONFIG_FREQ_8KHZ:
|
||||
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_8KHZ);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
*caps_size += 3;
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_FREQ, preset->rate);
|
||||
ltv_writer_uint16(&writer, LC3_TYPE_FRAMELEN, preset->framelen);
|
||||
ltv_writer_uint8(&writer, LC3_TYPE_DUR, preset->frame_duration);
|
||||
ltv_writer_uint32(&writer, LC3_TYPE_CHAN, channel_allocation);
|
||||
|
||||
data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(bap_bcast_qos_configs[index].framelen));
|
||||
*caps_size += 4;
|
||||
data += write_ltv_uint8(data, LC3_TYPE_DUR, bap_bcast_qos_configs[index].frame_duration);
|
||||
*caps_size += 3;
|
||||
data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(channel_allocation));
|
||||
*caps_size += 6;
|
||||
|
||||
if(bap_bcast_qos_configs[index].framing)
|
||||
if (preset->framing)
|
||||
qos->framing = 1;
|
||||
else
|
||||
qos->framing = 0;
|
||||
qos->sdu = bap_bcast_qos_configs[index].framelen * get_channel_count(channel_allocation);
|
||||
qos->retransmission = bap_bcast_qos_configs[index].retransmission;
|
||||
qos->latency = bap_bcast_qos_configs[index].latency;
|
||||
qos->delay = bap_bcast_qos_configs[index].delay;
|
||||
qos->sdu = preset->framelen * get_channel_count(channel_allocation);
|
||||
qos->retransmission = preset->retransmission;
|
||||
qos->latency = preset->latency;
|
||||
qos->delay = preset->delay;
|
||||
qos->phy = 2;
|
||||
qos->interval = (bap_bcast_qos_configs[index].frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000);
|
||||
qos->interval = (preset->frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000);
|
||||
|
||||
return true;
|
||||
ret = ltv_writer_end(&writer);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > UINT8_MAX)
|
||||
return -ENOSPC;
|
||||
|
||||
*caps_size = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct media_codec bap_codec_lc3 = {
|
||||
|
|
@ -1465,6 +1555,8 @@ const struct media_codec bap_codec_lc3 = {
|
|||
.enum_config = codec_enum_config,
|
||||
.validate_config = codec_validate_config,
|
||||
.get_qos = codec_get_qos,
|
||||
.get_metadata = codec_get_metadata,
|
||||
.free_config_data = codec_free_config_data,
|
||||
.caps_preference_cmp = codec_caps_preference_cmp,
|
||||
.init = codec_init,
|
||||
.deinit = codec_deinit,
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ enum backend_selection {
|
|||
#define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC)
|
||||
|
||||
|
||||
struct bap_features {
|
||||
struct spa_dict dict;
|
||||
struct spa_dict_item items[32];
|
||||
};
|
||||
|
||||
struct spa_bt_monitor {
|
||||
struct spa_handle handle;
|
||||
struct spa_device device;
|
||||
|
|
@ -131,6 +136,8 @@ struct spa_bt_monitor {
|
|||
uint32_t bap_source_contexts;
|
||||
uint32_t bap_source_supported_contexts;
|
||||
|
||||
struct bap_features bap_features;
|
||||
|
||||
struct spa_bt_quirks *quirks;
|
||||
|
||||
#define MAX_SETTINGS 128
|
||||
|
|
@ -152,13 +159,17 @@ struct spa_bt_remote_endpoint {
|
|||
char *uuid;
|
||||
unsigned int codec;
|
||||
struct spa_bt_device *device;
|
||||
uint8_t capabilities[A2DP_MAX_CAPS_SIZE];
|
||||
int capabilities_len;
|
||||
uint8_t *capabilities;
|
||||
size_t capabilities_len;
|
||||
uint8_t *metadata;
|
||||
size_t metadata_len;
|
||||
bool delay_reporting;
|
||||
bool acceptor;
|
||||
|
||||
struct bap_endpoint_qos qos;
|
||||
|
||||
struct bap_features bap_features;
|
||||
|
||||
bool asha_right_side;
|
||||
uint64_t hisyncid;
|
||||
};
|
||||
|
|
@ -656,6 +667,77 @@ static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor,
|
|||
codec->fill_caps;
|
||||
}
|
||||
|
||||
static bool bap_features_add(struct bap_features *feat, const char *uuid, const char *name)
|
||||
{
|
||||
#define TMAP_ITEM(item) { BT_TMAP_UUID, item ##_STR, BT_TMAP_UUID ":" item ##_STR },
|
||||
#define GMAP_ITEM(item) { BT_GMAP_UUID, item ##_STR, BT_GMAP_UUID ":" item ##_STR },
|
||||
static const struct {
|
||||
const char *const uuid;
|
||||
const char *const name;
|
||||
const char *const key;
|
||||
} values[] = {
|
||||
BT_TMAP_ROLE_LIST(TMAP_ITEM)
|
||||
BT_GMAP_ROLE_LIST(GMAP_ITEM)
|
||||
BT_GMAP_FEATURE_LIST(GMAP_ITEM)
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
SPA_STATIC_ASSERT(SPA_N_ELEMENTS(feat->items) >= SPA_N_ELEMENTS(values));
|
||||
size_t n_items = feat->dict.n_items;
|
||||
size_t i;
|
||||
|
||||
/* Accept only listed features */
|
||||
for (i = 0; values[i].uuid; ++i)
|
||||
if (spa_streq(values[i].uuid, uuid) && spa_streq(values[i].name, name))
|
||||
break;
|
||||
if (!values[i].uuid)
|
||||
return false;
|
||||
|
||||
if (spa_dict_lookup(&feat->dict, values[i].key))
|
||||
return false;
|
||||
|
||||
spa_assert(n_items < SPA_N_ELEMENTS(feat->items));
|
||||
|
||||
/* Add */
|
||||
feat->items[n_items].key = values[i].key;
|
||||
feat->items[n_items].value = values[i].uuid;
|
||||
n_items++;
|
||||
|
||||
feat->dict = SPA_DICT(feat->items, n_items);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Get feature uuid at \a i */
|
||||
static const char *bap_features_get_uuid(struct bap_features *feat, size_t i)
|
||||
{
|
||||
if (!SPA_FLAG_IS_SET(feat->dict.flags, SPA_DICT_FLAG_SORTED))
|
||||
spa_dict_qsort(&feat->dict);
|
||||
|
||||
if (i >= feat->dict.n_items)
|
||||
return NULL;
|
||||
return feat->dict.items[i].value;
|
||||
}
|
||||
|
||||
/** Get feature name at \a i, or NULL if uuid doesn't match */
|
||||
static const char *bap_features_get_name(struct bap_features *feat, size_t i, const char *uuid)
|
||||
{
|
||||
char *pos;
|
||||
|
||||
if (i >= feat->dict.n_items)
|
||||
return NULL;
|
||||
if (!spa_streq(feat->dict.items[i].value, uuid))
|
||||
return NULL;
|
||||
|
||||
pos = strchr(feat->dict.items[i].key, ':');
|
||||
if (!pos)
|
||||
return NULL;
|
||||
return pos + 1;
|
||||
}
|
||||
|
||||
static void bap_features_clear(struct bap_features *feat)
|
||||
{
|
||||
spa_zero(*feat);
|
||||
}
|
||||
|
||||
static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
|
||||
{
|
||||
struct spa_bt_monitor *monitor = userdata;
|
||||
|
|
@ -693,7 +775,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu
|
|||
* by codec switching.
|
||||
*/
|
||||
res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, cap, size, &monitor->default_audio_info,
|
||||
&monitor->global_settings, config);
|
||||
&monitor->global_settings, config, NULL);
|
||||
else
|
||||
res = -ENOTSUP;
|
||||
|
||||
|
|
@ -878,8 +960,9 @@ static void parse_endpoint_qos(struct spa_bt_monitor *monitor, DBusMessageIter *
|
|||
}
|
||||
|
||||
static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter *iter,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE], int *caps_size, const char **endpoint_path,
|
||||
struct bap_endpoint_qos *qos)
|
||||
uint8_t **caps, size_t *caps_size,
|
||||
uint8_t **meta, size_t *meta_size,
|
||||
const char **endpoint_path, struct bap_endpoint_qos *qos)
|
||||
{
|
||||
DBusMessageIter dict_iter = *iter;
|
||||
const char *key = NULL;
|
||||
|
|
@ -900,29 +983,46 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter
|
|||
|
||||
type = dbus_message_iter_get_arg_type(&it[1]);
|
||||
|
||||
if (spa_streq(key, "Capabilities")) {
|
||||
uint8_t *buf;
|
||||
if (spa_streq(key, "Capabilities") || spa_streq(key, "Metadata")) {
|
||||
uint8_t **dest;
|
||||
size_t *size;
|
||||
uint8_t *data, *buf;
|
||||
int n;
|
||||
|
||||
if (!caps)
|
||||
if (spa_streq(key, "Capabilities")) {
|
||||
dest = caps;
|
||||
size = caps_size;
|
||||
} else {
|
||||
dest = meta;
|
||||
size = meta_size;
|
||||
}
|
||||
|
||||
if (!dest)
|
||||
goto next;
|
||||
|
||||
if (type != DBUS_TYPE_ARRAY)
|
||||
spa_assert(dest && size);
|
||||
|
||||
if (!check_iter_signature(&it[1], "ay"))
|
||||
goto bad_property;
|
||||
|
||||
dbus_message_iter_recurse(&it[1], &it[2]);
|
||||
type = dbus_message_iter_get_arg_type(&it[2]);
|
||||
if (type != DBUS_TYPE_BYTE)
|
||||
goto bad_property;
|
||||
dbus_message_iter_get_fixed_array(&it[2], &data, &n);
|
||||
|
||||
dbus_message_iter_get_fixed_array(&it[2], &buf, caps_size);
|
||||
if (*caps_size > A2DP_MAX_CAPS_SIZE) {
|
||||
spa_log_error(monitor->log, "%s size:%d too large", key, (int)*caps_size);
|
||||
return -EINVAL;
|
||||
if (n) {
|
||||
buf = malloc(n);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
memcpy(buf, data, n);
|
||||
} else {
|
||||
buf = NULL;
|
||||
}
|
||||
memcpy(caps, buf, *caps_size);
|
||||
|
||||
spa_log_info(monitor->log, "%p: %s size:%d", monitor, key, *caps_size);
|
||||
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', caps, (size_t)*caps_size);
|
||||
free(*dest);
|
||||
*dest = buf;
|
||||
*size = n;
|
||||
|
||||
spa_log_info(monitor->log, "%p: %s size:%zu", monitor, key, *size);
|
||||
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', *dest, *size);
|
||||
} else if (spa_streq(key, "Endpoint")) {
|
||||
if (!endpoint_path)
|
||||
goto next;
|
||||
|
|
@ -1012,8 +1112,12 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
|
||||
const char *endpoint_path = NULL;
|
||||
uint8_t config[A2DP_MAX_CAPS_SIZE];
|
||||
void *config_data = NULL;
|
||||
char locations[64] = {0};
|
||||
char channel_allocation[64] = {0};
|
||||
char supported_context[64] = {0};
|
||||
char available_context[64] = {0};
|
||||
char metadata_len[64] = {0};
|
||||
int conf_size;
|
||||
DBusMessageIter dict;
|
||||
|
||||
|
|
@ -1028,6 +1132,9 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
|
||||
path = dbus_message_get_path(m);
|
||||
|
||||
if ((r = dbus_message_new_method_return(m)) == NULL)
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
|
||||
/* TODO: for codecs with shared endpoint, this currently always picks the default
|
||||
* one. However, currently we don't have BAP codecs with shared endpoint, so
|
||||
* this does not matter, but in case they are needed later we should pick the
|
||||
|
|
@ -1043,7 +1150,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
|
||||
/* Find endpoint */
|
||||
iter = props;
|
||||
if (parse_endpoint_props(monitor, &iter, NULL, NULL, &endpoint_path, NULL) < 0)
|
||||
if (parse_endpoint_props(monitor, &iter, NULL, NULL, NULL, NULL, &endpoint_path, NULL) < 0)
|
||||
goto error_invalid;
|
||||
|
||||
ep = remote_endpoint_find(monitor, endpoint_path);
|
||||
|
|
@ -1059,7 +1166,8 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
|
||||
/* Parse endpoint properties */
|
||||
iter = props;
|
||||
if (parse_endpoint_props(monitor, &iter, ep->capabilities, &ep->capabilities_len, NULL, &ep->qos) < 0)
|
||||
if (parse_endpoint_props(monitor, &iter, &ep->capabilities, &ep->capabilities_len,
|
||||
&ep->metadata, &ep->metadata_len, NULL, &ep->qos) < 0)
|
||||
goto error_invalid;
|
||||
|
||||
if (ep->qos.locations)
|
||||
|
|
@ -1067,6 +1175,10 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
if (ep->qos.channel_allocation)
|
||||
spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, ep->qos.channel_allocation);
|
||||
|
||||
spa_scnprintf(supported_context, sizeof(supported_context), "%"PRIu16, ep->qos.supported_context);
|
||||
spa_scnprintf(available_context, sizeof(available_context), "%"PRIu16, ep->qos.context);
|
||||
spa_scnprintf(metadata_len, sizeof(metadata_len), "%zu", ep->metadata_len);
|
||||
|
||||
if (!ep->device->preferred_profiles)
|
||||
ep->device->preferred_profiles = ep->device->profiles;
|
||||
|
||||
|
|
@ -1075,16 +1187,22 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
i = 0;
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations);
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.channel-allocation", channel_allocation);
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.supported-context", supported_context);
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.available-context", available_context);
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.sink", sink ? "true" : "false");
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.duplex", duplex ? "true" : "false");
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true");
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata", (void *)ep->metadata);
|
||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata-len", metadata_len);
|
||||
for (j = 0; j < ep->bap_features.dict.n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
|
||||
setting_items[i] = ep->bap_features.dict.items[j];
|
||||
if (ep->device->settings)
|
||||
for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
|
||||
setting_items[i] = ep->device->settings->items[j];
|
||||
settings = SPA_DICT_INIT(setting_items, i);
|
||||
|
||||
conf_size = codec->select_config(codec, 0, ep->capabilities, ep->capabilities_len,
|
||||
&monitor->default_audio_info, &settings, config);
|
||||
&monitor->default_audio_info, &settings, config, &config_data);
|
||||
if (conf_size < 0) {
|
||||
spa_log_error(monitor->log, "can't select config: %d (%s)",
|
||||
conf_size, spa_strerror(conf_size));
|
||||
|
|
@ -1093,8 +1211,6 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
spa_log_info(monitor->log, "%p: selected conf %d", monitor, conf_size);
|
||||
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', (uint8_t *)config, (size_t)conf_size);
|
||||
|
||||
if ((r = dbus_message_new_method_return(m)) == NULL)
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
dbus_message_iter_init_append(r, &iter);
|
||||
|
||||
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
||||
|
|
@ -1113,7 +1229,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
|
||||
spa_zero(qos);
|
||||
|
||||
res = codec->get_qos(codec, config, conf_size, &ep->qos, &qos, &settings);
|
||||
res = codec->get_qos(codec, &ep->qos, config_data, &qos);
|
||||
if (res < 0) {
|
||||
spa_log_error(monitor->log, "can't select QOS config: %d (%s)",
|
||||
res, spa_strerror(res));
|
||||
|
|
@ -1161,8 +1277,30 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
|||
dbus_message_iter_close_container(&dict, &entry);
|
||||
}
|
||||
|
||||
if (codec->get_metadata) {
|
||||
uint8_t meta[4096] = {};
|
||||
size_t meta_size;
|
||||
|
||||
meta_size = res = codec->get_metadata(codec, config_data, meta, sizeof(meta));
|
||||
if (res < 0) {
|
||||
spa_log_error(monitor->log, "can't select metadata config: %d (%s)",
|
||||
res, spa_strerror(res));
|
||||
goto error_invalid;
|
||||
}
|
||||
|
||||
if (meta_size) {
|
||||
spa_log_info(monitor->log, "%p: selected metadata %d", monitor, (int)meta_size);
|
||||
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', meta, meta_size);
|
||||
|
||||
append_basic_array_variant_dict_entry(&dict, "Metadata", "ay", "y", DBUS_TYPE_BYTE, &meta, meta_size);
|
||||
}
|
||||
}
|
||||
|
||||
dbus_message_iter_close_container(&iter, &dict);
|
||||
|
||||
if (config_data && codec->free_config_data)
|
||||
codec->free_config_data(codec, config_data);
|
||||
|
||||
if (!dbus_connection_send(conn, r, NULL))
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
|
||||
|
|
@ -1173,6 +1311,9 @@ error_invalid:
|
|||
goto error;
|
||||
|
||||
error:
|
||||
if (config_data && codec->free_config_data)
|
||||
codec->free_config_data(codec, config_data);
|
||||
|
||||
if (!reply_with_error(conn, m, "org.bluez.Error.InvalidArguments", err_msg))
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
|
|
@ -2797,6 +2938,38 @@ static struct spa_bt_device *create_bcast_device(struct spa_bt_monitor *monitor,
|
|||
|
||||
static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor);
|
||||
|
||||
static void parse_supported_features(struct spa_bt_monitor *monitor,
|
||||
DBusMessageIter *dict, struct bap_features *features)
|
||||
{
|
||||
while (dbus_message_iter_get_arg_type(dict) == DBUS_TYPE_DICT_ENTRY) {
|
||||
DBusMessageIter entry, variant, array;
|
||||
const char *key;
|
||||
|
||||
dbus_message_iter_recurse(dict, &entry);
|
||||
dbus_message_iter_get_basic(&entry, &key);
|
||||
dbus_message_iter_next(&entry);
|
||||
dbus_message_iter_recurse(&entry, &variant);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY)
|
||||
goto next;
|
||||
|
||||
dbus_message_iter_recurse(&variant, &array);
|
||||
|
||||
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
|
||||
const char *name;
|
||||
|
||||
dbus_message_iter_get_basic(&array, &name);
|
||||
if (bap_features_add(features, key, name))
|
||||
spa_log_debug(monitor->log, "remote_endpoint: BAP feature %s %s", key, name);
|
||||
dbus_message_iter_next(&array);
|
||||
}
|
||||
|
||||
next:
|
||||
dbus_message_iter_next(dict);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint,
|
||||
DBusMessageIter *props_iter,
|
||||
DBusMessageIter *invalidated_iter)
|
||||
|
|
@ -2805,8 +2978,9 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
|
|||
DBusMessageIter copy_iter = *props_iter;
|
||||
|
||||
parse_endpoint_props(monitor, ©_iter,
|
||||
remote_endpoint->capabilities, &remote_endpoint->capabilities_len, NULL,
|
||||
&remote_endpoint->qos);
|
||||
&remote_endpoint->capabilities, &remote_endpoint->capabilities_len,
|
||||
&remote_endpoint->metadata, &remote_endpoint->metadata_len,
|
||||
NULL, &remote_endpoint->qos);
|
||||
|
||||
while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) {
|
||||
DBusMessageIter it[2];
|
||||
|
|
@ -2820,7 +2994,8 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
|
|||
|
||||
type = dbus_message_iter_get_arg_type(&it[1]);
|
||||
|
||||
if (spa_streq(key, "Capabilities") || spa_streq(key, "Locations") ||
|
||||
if (spa_streq(key, "Capabilities") || spa_streq(key, "Metadata") ||
|
||||
spa_streq(key, "Locations") ||
|
||||
spa_streq(key, "QoS") || spa_streq(key, "Context") ||
|
||||
spa_streq(key, "SupportedContext")) {
|
||||
/* parsed by parse_endpoint_props */
|
||||
|
|
@ -2930,8 +3105,13 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
|
|||
remote_endpoint->hisyncid = *(uint64_t *)value;
|
||||
|
||||
spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid);
|
||||
}
|
||||
else {
|
||||
} else if (spa_streq(key, "SupportedFeatures")) {
|
||||
if (!check_iter_signature(&it[1], "a{sv}"))
|
||||
goto next;
|
||||
|
||||
dbus_message_iter_recurse(&it[1], &it[2]);
|
||||
parse_supported_features(monitor, &it[2], &remote_endpoint->bap_features);
|
||||
} else {
|
||||
unhandled:
|
||||
spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key);
|
||||
}
|
||||
|
|
@ -2986,10 +3166,14 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint)
|
|||
if (remote_endpoint->device)
|
||||
spa_list_remove(&remote_endpoint->device_link);
|
||||
|
||||
bap_features_clear(&remote_endpoint->bap_features);
|
||||
|
||||
spa_list_remove(&remote_endpoint->link);
|
||||
free(remote_endpoint->path);
|
||||
free(remote_endpoint->transport_path);
|
||||
free(remote_endpoint->uuid);
|
||||
free(remote_endpoint->capabilities);
|
||||
free(remote_endpoint->metadata);
|
||||
free(remote_endpoint);
|
||||
}
|
||||
|
||||
|
|
@ -3587,6 +3771,11 @@ static int transport_update_props(struct spa_bt_transport *transport,
|
|||
free(transport->configuration);
|
||||
transport->configuration_len = 0;
|
||||
|
||||
if (!len) {
|
||||
transport->configuration = NULL;
|
||||
goto next;
|
||||
}
|
||||
|
||||
transport->configuration = malloc(len);
|
||||
if (transport->configuration) {
|
||||
memcpy(transport->configuration, value, len);
|
||||
|
|
@ -4087,13 +4276,22 @@ static int transport_acquire(void *data, bool optional)
|
|||
return do_transport_acquire(data);
|
||||
}
|
||||
|
||||
static int do_transport_release(struct spa_bt_transport *transport)
|
||||
struct pending_release {
|
||||
struct spa_list link;
|
||||
DBusPendingCall *pending;
|
||||
struct spa_bt_transport *transport;
|
||||
bool is_idle;
|
||||
};
|
||||
|
||||
static struct pending_release *do_transport_release(struct spa_bt_transport *transport)
|
||||
{
|
||||
struct spa_bt_monitor *monitor = transport->monitor;
|
||||
spa_autoptr(DBusMessage) m = NULL, r = NULL;
|
||||
spa_autoptr(DBusMessage) m = NULL;
|
||||
struct spa_bt_transport *t_linked;
|
||||
bool is_idle = (transport->state == SPA_BT_TRANSPORT_STATE_IDLE);
|
||||
bool linked = false;
|
||||
struct pending_release *pending;
|
||||
DBusPendingCall *p;
|
||||
|
||||
spa_log_debug(monitor->log, "transport %p: Release %s",
|
||||
transport, transport->path);
|
||||
|
|
@ -4130,7 +4328,7 @@ static int do_transport_release(struct spa_bt_transport *transport)
|
|||
if (linked) {
|
||||
spa_log_info(monitor->log, "Linked transport %s released", transport->path);
|
||||
transport->fd = -1;
|
||||
return 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
release:
|
||||
|
|
@ -4146,46 +4344,39 @@ release:
|
|||
BLUEZ_MEDIA_TRANSPORT_INTERFACE,
|
||||
"Release");
|
||||
if (m == NULL)
|
||||
return -ENOMEM;
|
||||
return NULL;
|
||||
|
||||
spa_auto(DBusError) err = DBUS_ERROR_INIT;
|
||||
r = dbus_connection_send_with_reply_and_block(monitor->conn, m, -1, &err);
|
||||
if (r == NULL) {
|
||||
if (is_idle) {
|
||||
/* XXX: The fd always needs to be closed. However, Release()
|
||||
* XXX: apparently doesn't need to be called on idle transports
|
||||
* XXX: and fails. We call it just to be sure (e.g. in case
|
||||
* XXX: there's a race with updating the property), but tone down the error.
|
||||
*/
|
||||
spa_log_debug(monitor->log, "Failed to release idle transport %s: %s",
|
||||
transport->path, err.message);
|
||||
} else if (spa_streq(err.name, DBUS_ERROR_UNKNOWN_METHOD) ||
|
||||
spa_streq(err.name, DBUS_ERROR_UNKNOWN_OBJECT)) {
|
||||
/* Transport disappeared */
|
||||
spa_log_debug(monitor->log, "Failed to release (gone) transport %s: %s",
|
||||
transport->path, err.message);
|
||||
} else {
|
||||
spa_log_error(monitor->log, "Failed to release transport %s: %s",
|
||||
transport->path, err.message);
|
||||
}
|
||||
} else {
|
||||
spa_log_info(monitor->log, "Transport %s released", transport->path);
|
||||
p = send_with_reply(monitor->conn, m, NULL, NULL);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
pending = calloc(1, sizeof(*pending));
|
||||
if (!pending) {
|
||||
dbus_pending_call_block(p);
|
||||
dbus_pending_call_unref(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
pending->pending = p;
|
||||
pending->transport = transport;
|
||||
pending->is_idle = is_idle;
|
||||
return pending;
|
||||
}
|
||||
|
||||
static int transport_release(void *data)
|
||||
{
|
||||
struct spa_bt_transport *transport = data;
|
||||
struct spa_bt_monitor *monitor = transport->monitor;
|
||||
struct spa_bt_transport *t;
|
||||
struct spa_list pending = SPA_LIST_INIT(&pending);
|
||||
struct pending_release *item;
|
||||
|
||||
/*
|
||||
* XXX: When as BAP Central, release CIS in a CIG when the last transport
|
||||
* XXX: goes away.
|
||||
*/
|
||||
if (transport->bap_initiator) {
|
||||
struct spa_bt_transport *t;
|
||||
|
||||
/* Check if another transport is alive */
|
||||
if (another_cig_transport_active(transport)) {
|
||||
spa_log_debug(monitor->log, "Releasing %s: wait for CIG %d",
|
||||
|
|
@ -4201,15 +4392,61 @@ static int transport_release(void *data)
|
|||
spa_log_debug(monitor->log, "Release CIG %d: transport %s",
|
||||
transport->bap_cig, t->path);
|
||||
|
||||
if (t->fd >= 0)
|
||||
do_transport_release(t);
|
||||
if (t->fd >= 0) {
|
||||
item = do_transport_release(t);
|
||||
if (item)
|
||||
spa_list_append(&pending, &item->link);
|
||||
}
|
||||
}
|
||||
|
||||
spa_log_debug(monitor->log, "Release CIG %d: transport %s",
|
||||
transport->bap_cig, transport->path);
|
||||
}
|
||||
|
||||
return do_transport_release(data);
|
||||
item = do_transport_release(transport);
|
||||
if (item)
|
||||
spa_list_append(&pending, &item->link);
|
||||
|
||||
spa_list_consume(item, &pending, link) {
|
||||
struct spa_bt_transport *t = item->transport;
|
||||
bool is_idle = item->is_idle;
|
||||
DBusPendingCall *p = item->pending;
|
||||
spa_autoptr(DBusMessage) r = NULL;
|
||||
spa_auto(DBusError) err = DBUS_ERROR_INIT;
|
||||
|
||||
spa_list_remove(&item->link);
|
||||
free(item);
|
||||
if (!p)
|
||||
continue;
|
||||
|
||||
dbus_pending_call_block(p);
|
||||
r = steal_reply_and_unref(&p);
|
||||
|
||||
if (r == NULL) {
|
||||
if (is_idle) {
|
||||
/* XXX: The fd always needs to be closed. However, Release()
|
||||
* XXX: apparently doesn't need to be called on idle transports
|
||||
* XXX: and fails. We call it just to be sure (e.g. in case
|
||||
* XXX: there's a race with updating the property), but tone down the error.
|
||||
*/
|
||||
spa_log_debug(monitor->log, "Failed to release idle transport %s: %s",
|
||||
t->path, err.message);
|
||||
} else if (spa_streq(err.name, DBUS_ERROR_UNKNOWN_METHOD) ||
|
||||
spa_streq(err.name, DBUS_ERROR_UNKNOWN_OBJECT)) {
|
||||
/* Transport disappeared */
|
||||
spa_log_debug(monitor->log, "Failed to release (gone) transport %s: %s",
|
||||
t->path, err.message);
|
||||
} else {
|
||||
spa_log_error(monitor->log, "Failed to release transport %s: %s",
|
||||
t->path, err.message);
|
||||
}
|
||||
} else {
|
||||
spa_log_info(monitor->log, "Transport %s released", t->path);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int transport_set_delay(void *data, int64_t delay_nsec)
|
||||
|
|
@ -4481,7 +4718,7 @@ static bool codec_switch_configure_a2dp(struct spa_bt_codec_switch *sw, const ch
|
|||
}
|
||||
|
||||
res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, ep->capabilities, ep->capabilities_len,
|
||||
&monitor->default_audio_info, &monitor->global_settings, config);
|
||||
&monitor->default_audio_info, &monitor->global_settings, config, NULL);
|
||||
if (res < 0) {
|
||||
spa_log_error(monitor->log, "media codec switch %p: incompatible capabilities (%d)",
|
||||
sw, res);
|
||||
|
|
@ -5306,6 +5543,42 @@ out:
|
|||
return err;
|
||||
}
|
||||
|
||||
static void append_supported_features(DBusMessageIter *dict, struct bap_features *features)
|
||||
{
|
||||
const char *key = "SupportedFeatures";
|
||||
DBusMessageIter dict_entry, dict_variant, value_dict;
|
||||
DBusMessageIter entry, variant, array;
|
||||
const char *uuid, *name;
|
||||
size_t i;
|
||||
|
||||
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry);
|
||||
dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, &key);
|
||||
dbus_message_iter_open_container(&dict_entry, DBUS_TYPE_VARIANT, "a{sv}", &dict_variant);
|
||||
|
||||
dbus_message_iter_open_container(&dict_variant, DBUS_TYPE_ARRAY, "{sv}", &value_dict);
|
||||
|
||||
i = 0;
|
||||
while ((uuid = bap_features_get_uuid(features, i))) {
|
||||
dbus_message_iter_open_container(&value_dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
|
||||
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &uuid);
|
||||
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "as", &variant);
|
||||
dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array);
|
||||
|
||||
while ((name = bap_features_get_name(features, i, uuid))) {
|
||||
dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &name);
|
||||
++i;
|
||||
}
|
||||
|
||||
dbus_message_iter_close_container(&variant, &array);
|
||||
dbus_message_iter_close_container(&entry, &variant);
|
||||
dbus_message_iter_close_container(&value_dict, &entry);
|
||||
}
|
||||
|
||||
dbus_message_iter_close_container(&dict_variant, &value_dict);
|
||||
dbus_message_iter_close_container(&dict_entry, &dict_variant);
|
||||
dbus_message_iter_close_container(dict, &dict_entry);
|
||||
}
|
||||
|
||||
static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter *iter, const char *endpoint,
|
||||
const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size)
|
||||
{
|
||||
|
|
@ -5353,6 +5626,9 @@ static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter
|
|||
append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts);
|
||||
}
|
||||
|
||||
if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_BAP_AUDIO)
|
||||
append_supported_features(&dict, &monitor->bap_features);
|
||||
|
||||
dbus_message_iter_close_container(&entry, &dict);
|
||||
dbus_message_iter_close_container(&array, &entry);
|
||||
dbus_message_iter_close_container(&object, &array);
|
||||
|
|
@ -5582,10 +5858,10 @@ static int register_media_endpoint(struct spa_bt_monitor *monitor,
|
|||
static int register_media_application(struct spa_bt_monitor * monitor)
|
||||
{
|
||||
const struct media_codec * const * const media_codecs = monitor->media_codecs;
|
||||
const DBusObjectPathVTable vtable_object_manager_a2dp = {
|
||||
static const DBusObjectPathVTable vtable_object_manager_a2dp = {
|
||||
.message_function = object_manager_handler_a2dp,
|
||||
};
|
||||
const DBusObjectPathVTable vtable_object_manager_bap = {
|
||||
static const DBusObjectPathVTable vtable_object_manager_bap = {
|
||||
.message_function = object_manager_handler_bap,
|
||||
};
|
||||
|
||||
|
|
@ -5816,6 +6092,7 @@ static void configure_bis(struct spa_bt_monitor *monitor,
|
|||
int sync_cte_type = 0;
|
||||
int sync_timeout = 2000;
|
||||
int timeout = 2000;
|
||||
int ret;
|
||||
|
||||
/* Configure each BIS from a BIG */
|
||||
spa_list_for_each(metadata_entry, &bis->metadata_list, link) {
|
||||
|
|
@ -5839,7 +6116,12 @@ static void configure_bis(struct spa_bt_monitor *monitor,
|
|||
setting_items[1] = SPA_DICT_ITEM_INIT("preset", bis->qos_preset);
|
||||
settings = SPA_DICT_INIT(setting_items, 2);
|
||||
|
||||
codec->get_bis_config(codec, caps, &caps_size, &settings, &qos);
|
||||
caps_size = sizeof(caps);
|
||||
ret = codec->get_bis_config(codec, caps, &caps_size, &settings, &qos);
|
||||
if (ret < 0) {
|
||||
spa_log_warn(monitor->log, "Getting BIS config failed");
|
||||
return;
|
||||
}
|
||||
|
||||
msg = dbus_message_new_method_call(BLUEZ_SERVICE,
|
||||
object_path,
|
||||
|
|
@ -6568,6 +6850,8 @@ static int impl_clear(struct spa_handle *handle)
|
|||
monitor->backend = NULL;
|
||||
monitor->backend_selection = BACKEND_NATIVE;
|
||||
|
||||
bap_features_clear(&monitor->bap_features);
|
||||
|
||||
spa_bt_quirks_destroy(monitor->quirks);
|
||||
|
||||
free_media_codecs(monitor->media_codecs);
|
||||
|
|
@ -6881,10 +7165,36 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di
|
|||
*value = locations;
|
||||
}
|
||||
|
||||
static void bap_feature_parse(struct spa_bt_monitor *this, const char *uuid, const char *str)
|
||||
{
|
||||
struct spa_json it;
|
||||
char name[64];
|
||||
|
||||
if (!str)
|
||||
return;
|
||||
|
||||
if (spa_json_begin_array_relax(&it, str, strlen(str)) < 0)
|
||||
return;
|
||||
|
||||
while (spa_json_get_string(&it, name, sizeof(name)) > 0) {
|
||||
if (bap_features_add(&this->bap_features, uuid, name))
|
||||
spa_log_debug(this->log, "advertise BAP feature %s %s", uuid, name);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_bap_features(struct spa_bt_monitor *this, const struct spa_dict *info)
|
||||
{
|
||||
static const char *const tmap_uuid = "00001855-0000-1000-8000-00805f9b34fb";
|
||||
static const char *const gmap_uuid = "00001858-0000-1000-8000-00805f9b34fb";
|
||||
|
||||
bap_feature_parse(this, tmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-tmap-features"));
|
||||
bap_feature_parse(this, gmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-gmap-features"));
|
||||
}
|
||||
|
||||
static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info)
|
||||
{
|
||||
this->bap_sink_locations = BAP_CHANNEL_ALL;
|
||||
this->bap_source_locations = BAP_CHANNEL_ALL;
|
||||
this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
|
||||
this->bap_source_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
|
||||
this->bap_sink_contexts = this->bap_sink_supported_contexts = BAP_CONTEXT_ALL;
|
||||
this->bap_source_contexts = this->bap_source_supported_contexts = (BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL |
|
||||
BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME);
|
||||
|
|
@ -6899,6 +7209,8 @@ static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict
|
|||
parse_bap_locations(this, info, "bluez5.bap-server-capabilities.source.locations", &this->bap_source_locations);
|
||||
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.contexts"), &this->bap_source_contexts, 0);
|
||||
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.supported-contexts"), &this->bap_source_supported_contexts, 0);
|
||||
|
||||
parse_bap_features(this, info);
|
||||
}
|
||||
|
||||
static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <spa/support/log.h>
|
||||
#include <spa/utils/type.h>
|
||||
#include <spa/utils/json.h>
|
||||
#include <spa/utils/keys.h>
|
||||
#include <spa/utils/names.h>
|
||||
#include <spa/utils/string.h>
|
||||
|
|
@ -2676,11 +2677,24 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b
|
|||
static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id)
|
||||
{
|
||||
struct props *p = &this->props;
|
||||
struct spa_pod_frame f[2];
|
||||
struct spa_pod *param;
|
||||
|
||||
return spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_Props, id,
|
||||
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
|
||||
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active));
|
||||
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, id);
|
||||
spa_pod_builder_add(b,
|
||||
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
|
||||
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active),
|
||||
0);
|
||||
|
||||
spa_pod_builder_prop(b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(b, &f[1]);
|
||||
spa_pod_builder_string(b, "bluez5.disable-dummy-call");
|
||||
spa_pod_builder_bool(b, this->bt_dev->disable_dummy_call);
|
||||
spa_pod_builder_pop(b, &f[1]);
|
||||
|
||||
param = spa_pod_builder_pop(b, &f[0]);
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
static int impl_enum_params(void *object, int seq,
|
||||
|
|
@ -3017,6 +3031,47 @@ static void apply_prop_offload_active(struct impl *this, bool active)
|
|||
}
|
||||
}
|
||||
|
||||
static int parse_prop_params(struct impl *this, struct spa_pod *params)
|
||||
{
|
||||
struct spa_pod_parser prs;
|
||||
struct spa_pod_frame f;
|
||||
int changed = 0;
|
||||
|
||||
if (params == NULL)
|
||||
return 0;
|
||||
|
||||
spa_pod_parser_pod(&prs, params);
|
||||
if (spa_pod_parser_push_struct(&prs, &f) < 0)
|
||||
return 0;
|
||||
|
||||
while (true) {
|
||||
const char *name;
|
||||
struct spa_pod *pod;
|
||||
|
||||
if (spa_pod_parser_get_string(&prs, &name) < 0)
|
||||
break;
|
||||
|
||||
if (spa_pod_parser_get_pod(&prs, &pod) < 0)
|
||||
break;
|
||||
|
||||
if (spa_streq(name, "bluez5.disable-dummy-call") && spa_pod_is_bool(pod)) {
|
||||
bool disable_dummy_call = SPA_POD_VALUE(struct spa_pod_bool, pod);
|
||||
spa_log_info(this->log, "key:'%s' val:'%u'", name, disable_dummy_call);
|
||||
this->bt_dev->disable_dummy_call = disable_dummy_call;
|
||||
} else
|
||||
continue;
|
||||
|
||||
changed++;
|
||||
}
|
||||
|
||||
if (changed > 0) {
|
||||
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
|
||||
this->params[IDX_Props].user++;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int impl_set_param(void *object,
|
||||
uint32_t id, uint32_t flags,
|
||||
const struct spa_pod *param)
|
||||
|
|
@ -3093,6 +3148,7 @@ static int impl_set_param(void *object,
|
|||
{
|
||||
uint32_t codec_id = SPA_ID_INVALID;
|
||||
bool offload_active = this->props.offload_active;
|
||||
struct spa_pod *params = NULL;
|
||||
|
||||
if (param == NULL)
|
||||
return 0;
|
||||
|
|
@ -3100,7 +3156,8 @@ static int impl_set_param(void *object,
|
|||
if ((res = spa_pod_parse_object(param,
|
||||
SPA_TYPE_OBJECT_Props, NULL,
|
||||
SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id),
|
||||
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active))) < 0) {
|
||||
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active),
|
||||
SPA_PROP_params, SPA_POD_OPT_Pod(¶ms))) < 0) {
|
||||
spa_log_warn(this->log, "can't parse props");
|
||||
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
|
||||
return res;
|
||||
|
|
@ -3108,10 +3165,15 @@ static int impl_set_param(void *object,
|
|||
|
||||
spa_log_debug(this->log, "setting props codec:%d offload:%d", (int)codec_id, (int)offload_active);
|
||||
|
||||
parse_prop_params(this, params);
|
||||
|
||||
apply_prop_offload_active(this, offload_active);
|
||||
|
||||
if (codec_id == SPA_ID_INVALID)
|
||||
if (codec_id == SPA_ID_INVALID) {
|
||||
this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
emit_info(this, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) ||
|
||||
this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) {
|
||||
|
|
@ -3249,6 +3311,13 @@ impl_init(const struct spa_handle_factory *factory,
|
|||
if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0)
|
||||
this->bt_dev->hw_volume_profiles = profiles;
|
||||
}
|
||||
|
||||
if ((str = spa_dict_lookup(info, "bluez5.disable-dummy-call")) != NULL) {
|
||||
bool value;
|
||||
|
||||
if (spa_json_parse_bool(str, strlen(str), &value) > 0)
|
||||
this->bt_dev->disable_dummy_call = value;
|
||||
}
|
||||
}
|
||||
|
||||
this->device.iface = SPA_INTERFACE_INIT(
|
||||
|
|
|
|||
|
|
@ -573,6 +573,8 @@ struct spa_bt_device {
|
|||
|
||||
const struct media_codec *preferred_codec;
|
||||
uint32_t preferred_profiles;
|
||||
|
||||
bool disable_dummy_call;
|
||||
};
|
||||
|
||||
struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path);
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@ static void group_on_timeout(struct spa_source *source)
|
|||
if (stream->this.size == 0) {
|
||||
spa_log_debug(group->log, "%p: ISO group:%u miss fd:%d",
|
||||
group, group->id, stream->fd);
|
||||
stream->this.resync = true;
|
||||
if (stream_silence(stream) < 0) {
|
||||
fail = true;
|
||||
continue;
|
||||
|
|
@ -625,6 +626,17 @@ int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *this)
|
|||
struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this);
|
||||
struct group *group = stream->group;
|
||||
|
||||
if (!stream->sink) {
|
||||
struct stream *s;
|
||||
|
||||
spa_list_for_each(s, &group->streams, link) {
|
||||
if (s->sink && s->fd == stream->fd) {
|
||||
stream = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return spa_bt_latency_recv_errqueue(&stream->tx_latency, stream->fd, group->log);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
|
||||
#include <spa/utils/string.h>
|
||||
#include <spa/utils/cleanup.h>
|
||||
|
||||
|
|
@ -90,7 +92,7 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_
|
|||
if (caps == NULL)
|
||||
return false;
|
||||
|
||||
res = codec->select_config(codec, 0, caps, caps_size, info, global_settings, config);
|
||||
res = codec->select_config(codec, 0, caps, caps_size, info, global_settings, config, NULL);
|
||||
if (res < 0)
|
||||
return false;
|
||||
|
||||
|
|
@ -100,6 +102,52 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_
|
|||
return ((size_t)res == caps_size);
|
||||
}
|
||||
|
||||
void ltv_writer_data(struct ltv_writer *w, uint8_t type, void* value, size_t len)
|
||||
{
|
||||
struct ltv *ltv;
|
||||
size_t sz = (size_t)w->size + sizeof(struct ltv) + len;
|
||||
|
||||
if (!w->buf || sz > w->max_size || (uint16_t)sz != sz) {
|
||||
w->buf = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
ltv = SPA_PTROFF(w->buf, w->size, struct ltv);
|
||||
ltv->len = len + 1;
|
||||
ltv->type = type;
|
||||
memcpy(ltv->value, value, len);
|
||||
|
||||
w->size = sz;
|
||||
}
|
||||
|
||||
void ltv_writer_uint8(struct ltv_writer *w, uint8_t type, uint8_t v)
|
||||
{
|
||||
ltv_writer_data(w, type, &v, sizeof(v));
|
||||
}
|
||||
|
||||
void ltv_writer_uint16(struct ltv_writer *w, uint8_t type, uint16_t value)
|
||||
{
|
||||
uint16_t v = htobs(value);
|
||||
|
||||
ltv_writer_data(w, type, &v, sizeof(v));
|
||||
}
|
||||
|
||||
void ltv_writer_uint32(struct ltv_writer *w, uint8_t type, uint32_t value)
|
||||
{
|
||||
uint32_t v = htobl(value);
|
||||
|
||||
ltv_writer_data(w, type, &v, sizeof(v));
|
||||
}
|
||||
|
||||
int ltv_writer_end(struct ltv_writer *w)
|
||||
{
|
||||
if (!w->buf)
|
||||
return -ENOSPC;
|
||||
|
||||
w->buf = NULL;
|
||||
return w->size;
|
||||
}
|
||||
|
||||
#ifdef CODEC_PLUGIN
|
||||
|
||||
struct impl {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <spa/pod/pod.h>
|
||||
#include <spa/pod/builder.h>
|
||||
#include <spa/support/log.h>
|
||||
#include <spa/debug/log.h>
|
||||
|
||||
#include "a2dp-codec-caps.h"
|
||||
#include "bap-codec-caps.h"
|
||||
|
|
@ -26,7 +27,7 @@
|
|||
|
||||
#define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private"
|
||||
|
||||
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 15
|
||||
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 16
|
||||
|
||||
struct spa_bluez5_codec_a2dp {
|
||||
struct spa_interface iface;
|
||||
|
|
@ -93,7 +94,7 @@ struct media_codec {
|
|||
* called again to parse the remaining data. */
|
||||
|
||||
int (*get_bis_config)(const struct media_codec *codec, uint8_t *caps,
|
||||
uint8_t *caps_size, struct spa_dict *settings,
|
||||
uint8_t *caps_size, struct spa_dict *settings,
|
||||
struct bap_codec_qos *qos);
|
||||
|
||||
/** If fill_caps is NULL, no endpoint is registered (for sharing with another codec). */
|
||||
|
|
@ -103,7 +104,8 @@ struct media_codec {
|
|||
int (*select_config) (const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE]);
|
||||
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
||||
void **config_data);
|
||||
int (*enum_config) (const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
|
||||
struct spa_pod_builder *builder, struct spa_pod **param);
|
||||
|
|
@ -111,9 +113,12 @@ struct media_codec {
|
|||
const void *caps, size_t caps_size,
|
||||
struct spa_audio_info *info);
|
||||
int (*get_qos)(const struct media_codec *codec,
|
||||
const void *config, size_t config_size,
|
||||
const struct bap_endpoint_qos *endpoint_qos,
|
||||
struct bap_codec_qos *qos, const struct spa_dict *settings);
|
||||
const void *config_data,
|
||||
struct bap_codec_qos *qos);
|
||||
int (*get_metadata)(const struct media_codec *codec, const void *config_data,
|
||||
uint8_t *meta, size_t meta_max_size);
|
||||
void (*free_config_data)(const struct media_codec *codec, void *config_data);
|
||||
|
||||
/** qsort comparison sorting caps in order of preference for the codec.
|
||||
* Used in codec switching to select best remote endpoints.
|
||||
|
|
@ -264,4 +269,44 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_
|
|||
const void *caps, size_t caps_size, const struct media_codec_audio_info *info,
|
||||
const struct spa_dict *global_settings);
|
||||
|
||||
struct __attribute__((packed)) ltv {
|
||||
uint8_t len;
|
||||
uint8_t type;
|
||||
uint8_t value[];
|
||||
};
|
||||
|
||||
struct ltv_writer {
|
||||
void *buf;
|
||||
uint16_t size;
|
||||
size_t max_size;
|
||||
};
|
||||
|
||||
#define LTV_WRITER(ptr, max) ((struct ltv_writer) { .buf = (ptr), .max_size = (max) })
|
||||
|
||||
void ltv_writer_data(struct ltv_writer *w, uint8_t type, void* value, size_t len);
|
||||
void ltv_writer_uint8(struct ltv_writer *w, uint8_t type, uint8_t v);
|
||||
void ltv_writer_uint16(struct ltv_writer *w, uint8_t type, uint16_t value);
|
||||
void ltv_writer_uint32(struct ltv_writer *w, uint8_t type, uint32_t value);
|
||||
int ltv_writer_end(struct ltv_writer *w);
|
||||
|
||||
static inline const struct ltv *ltv_next(const void **data, size_t *size)
|
||||
{
|
||||
const struct ltv *ltv;
|
||||
|
||||
if (*size == 0) {
|
||||
*data = NULL;
|
||||
return NULL;
|
||||
}
|
||||
if (*size < sizeof(struct ltv))
|
||||
return NULL;
|
||||
|
||||
ltv = *data;
|
||||
if (ltv->len >= *size)
|
||||
return NULL;
|
||||
|
||||
*data = SPA_PTROFF(*data, ltv->len + 1, void);
|
||||
*size -= ltv->len + 1;
|
||||
return ltv;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -613,6 +613,10 @@ static void handle_errqueue(struct impl *this)
|
|||
{
|
||||
int res;
|
||||
|
||||
if (this->transport && this->transport->iso_io)
|
||||
if (spa_bt_iso_io_recv_errqueue(this->transport->iso_io) == 0)
|
||||
return;
|
||||
|
||||
/* iso-io/media-sink use these for TX latency.
|
||||
* Someone else should be reading them, so drop
|
||||
* only after yielding.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
#include "modemmanager.h"
|
||||
|
||||
SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.modemmanager");
|
||||
#undef SPA_LOG_TOPIC_DEFAULT
|
||||
#define SPA_LOG_TOPIC_DEFAULT &log_topic
|
||||
|
||||
#define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager"
|
||||
|
||||
struct modem {
|
||||
|
|
@ -32,6 +36,8 @@ struct impl {
|
|||
|
||||
struct modem modem;
|
||||
struct spa_list call_list;
|
||||
|
||||
bool pts;
|
||||
};
|
||||
|
||||
struct dbus_cmd_data {
|
||||
|
|
@ -412,6 +418,26 @@ static void mm_get_managed_objects_reply(DBusPendingCall *pending, void *user_da
|
|||
}
|
||||
}
|
||||
|
||||
static bool mm_get_managed_objects(struct impl *this)
|
||||
{
|
||||
spa_autoptr(DBusMessage) m = dbus_message_new_method_call(MM_DBUS_SERVICE,
|
||||
"/org/freedesktop/ModemManager1",
|
||||
DBUS_INTERFACE_OBJECTMANAGER,
|
||||
"GetManagedObjects");
|
||||
if (m == NULL)
|
||||
return false;
|
||||
|
||||
dbus_message_set_auto_start(m, false);
|
||||
|
||||
this->pending = send_with_reply(this->conn, m, mm_get_managed_objects_reply, this);
|
||||
if (!this->pending) {
|
||||
spa_log_error(this->log, "dbus call failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void call_free(struct call *call)
|
||||
{
|
||||
spa_list_remove(&call->link);
|
||||
|
|
@ -488,8 +514,12 @@ static DBusHandlerResult mm_filter_cb(DBusConnection *bus, DBusMessage *m, void
|
|||
mm_clean_modem(this);
|
||||
}
|
||||
|
||||
if (new_owner && *new_owner)
|
||||
if (new_owner && *new_owner) {
|
||||
spa_log_debug(this->log, "ModemManager daemon appeared (%s)", new_owner);
|
||||
|
||||
if (!mm_get_managed_objects(this))
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
} else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, DBUS_SIGNAL_INTERFACES_ADDED)) {
|
||||
DBusMessageIter arg_i;
|
||||
|
|
@ -916,8 +946,13 @@ bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cm
|
|||
spa_autofree struct dbus_cmd_data *data = NULL;
|
||||
spa_autoptr(DBusMessage) m = NULL;
|
||||
DBusMessageIter iter, dict;
|
||||
size_t i = 0;
|
||||
|
||||
for (size_t i = 0; number[i]; i++) {
|
||||
/* Allow memory dial for PTS tests HFP/AG/OCM/BV-01-C and HFP/AG/OCM/BV-02-C */
|
||||
if (this->pts && number[0] == '>')
|
||||
i++;
|
||||
|
||||
for (; number[i]; i++) {
|
||||
if (!is_valid_dial_string_char(number[i])) {
|
||||
spa_log_warn(this->log, "Call creation canceled, invalid character found in dial string: %c", number[i]);
|
||||
if (error)
|
||||
|
|
@ -1050,6 +1085,8 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
|
|||
{
|
||||
const char *modem_device_str = NULL;
|
||||
bool modem_device_found = false;
|
||||
const char *pts_str = NULL;
|
||||
bool pts = false;
|
||||
|
||||
spa_assert(log);
|
||||
spa_assert(dbus_connection);
|
||||
|
|
@ -1059,6 +1096,9 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
|
|||
if (!spa_streq(modem_device_str, "none"))
|
||||
modem_device_found = true;
|
||||
}
|
||||
if ((pts_str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-pts")) != NULL) {
|
||||
pts = spa_atob(pts_str);
|
||||
}
|
||||
}
|
||||
if (!modem_device_found) {
|
||||
spa_log_info(log, "No modem allowed, doesn't link to ModemManager");
|
||||
|
|
@ -1076,25 +1116,14 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
|
|||
if (modem_device_str && !spa_streq(modem_device_str, "any"))
|
||||
this->allowed_modem_device = strdup(modem_device_str);
|
||||
spa_list_init(&this->call_list);
|
||||
this->pts = pts;
|
||||
|
||||
if (add_filters(this) < 0)
|
||||
return NULL;
|
||||
|
||||
spa_autoptr(DBusMessage) m = dbus_message_new_method_call(MM_DBUS_SERVICE,
|
||||
"/org/freedesktop/ModemManager1",
|
||||
DBUS_INTERFACE_OBJECTMANAGER,
|
||||
"GetManagedObjects");
|
||||
if (m == NULL)
|
||||
if (!mm_get_managed_objects(this))
|
||||
return NULL;
|
||||
|
||||
dbus_message_set_auto_start(m, false);
|
||||
|
||||
this->pending = send_with_reply(this->conn, m, mm_get_managed_objects_reply, this);
|
||||
if (!this->pending) {
|
||||
spa_log_error(this->log, "dbus call failure");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return spa_steal_ptr(this);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -247,11 +247,12 @@ static void update_properties(struct impl *impl, bool send_signal)
|
|||
|
||||
struct spa_bt_player *spa_bt_player_new(void *dbus_connection, struct spa_log *log)
|
||||
{
|
||||
struct impl *impl;
|
||||
const DBusObjectPathVTable vtable = {
|
||||
static const DBusObjectPathVTable vtable = {
|
||||
.message_function = player_handler,
|
||||
};
|
||||
|
||||
struct impl *impl;
|
||||
|
||||
spa_log_topic_init(log, &log_topic);
|
||||
|
||||
impl = calloc(1, sizeof(struct impl));
|
||||
|
|
|
|||
|
|
@ -1148,20 +1148,23 @@ int telephony_ag_register(struct spa_bt_telephony_ag *ag)
|
|||
{
|
||||
struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this);
|
||||
struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this);
|
||||
char *path;
|
||||
|
||||
const DBusObjectPathVTable vtable = {
|
||||
static const DBusObjectPathVTable vtable = {
|
||||
.message_function = ag_handler,
|
||||
};
|
||||
|
||||
path = spa_aprintf (PW_TELEPHONY_OBJECT_PATH "/ag%d", agimpl->this.id);
|
||||
if (agimpl->path)
|
||||
return -EBUSY;
|
||||
|
||||
spa_autofree char *path = spa_aprintf(PW_TELEPHONY_OBJECT_PATH "/ag%d", agimpl->this.id);
|
||||
|
||||
/* register object */
|
||||
if (!dbus_connection_register_object_path(impl->conn, path, &vtable, agimpl)) {
|
||||
spa_log_error(impl->log, "failed to register %s", path);
|
||||
return -EIO;
|
||||
}
|
||||
agimpl->path = strdup(path);
|
||||
|
||||
agimpl->path = spa_steal_ptr(path);
|
||||
|
||||
/* notify on ObjectManager of the Manager object */
|
||||
{
|
||||
|
|
@ -1174,7 +1177,7 @@ int telephony_ag_register(struct spa_bt_telephony_ag *ag)
|
|||
dbus_iter_append_ag_interfaces(&iter, ag);
|
||||
|
||||
if (!dbus_connection_send(impl->conn, msg, NULL)) {
|
||||
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", path);
|
||||
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", agimpl->path);
|
||||
telephony_ag_unregister(ag);
|
||||
return -EIO;
|
||||
}
|
||||
|
|
@ -1188,18 +1191,18 @@ int telephony_ag_register(struct spa_bt_telephony_ag *ag)
|
|||
msg = dbus_message_new_signal(impl->path, OFONO_MANAGER_IFACE,
|
||||
"ModemAdded");
|
||||
dbus_message_iter_init_append(msg, &iter);
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &agimpl->path);
|
||||
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &props_dict);
|
||||
dbus_message_iter_close_container(&iter, &props_dict);
|
||||
|
||||
if (!dbus_connection_send(impl->conn, msg, NULL)) {
|
||||
spa_log_error(impl->log, "failed to send ModemAdded for %s", path);
|
||||
spa_log_error(impl->log, "failed to send ModemAdded for %s", agimpl->path);
|
||||
telephony_ag_unregister(ag);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
spa_log_debug(impl->log, "registered AudioGateway: %s", path);
|
||||
spa_log_debug(impl->log, "registered AudioGateway: %s", agimpl->path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1646,20 +1649,23 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
|
|||
struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this);
|
||||
struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this);
|
||||
struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this);
|
||||
char *path;
|
||||
|
||||
const DBusObjectPathVTable vtable = {
|
||||
static const DBusObjectPathVTable vtable = {
|
||||
.message_function = call_handler,
|
||||
};
|
||||
|
||||
path = spa_aprintf ("%s/call%d", agimpl->path, callimpl->this.id);
|
||||
if (callimpl->path)
|
||||
return -EBUSY;
|
||||
|
||||
spa_autofree char *path = spa_aprintf("%s/call%d", agimpl->path, callimpl->this.id);
|
||||
|
||||
/* register object */
|
||||
if (!dbus_connection_register_object_path(impl->conn, path, &vtable, callimpl)) {
|
||||
spa_log_error(impl->log, "failed to register %s", path);
|
||||
return -EIO;
|
||||
}
|
||||
callimpl->path = strdup(path);
|
||||
|
||||
callimpl->path = spa_steal_ptr(path);
|
||||
|
||||
/* notify on ObjectManager of the AudioGateway object */
|
||||
{
|
||||
|
|
@ -1671,7 +1677,7 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
|
|||
DBUS_INTERFACE_OBJECT_MANAGER,
|
||||
"InterfacesAdded");
|
||||
dbus_message_iter_init_append(msg, &iter);
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path);
|
||||
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict);
|
||||
dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
|
||||
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface);
|
||||
|
|
@ -1680,7 +1686,7 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
|
|||
dbus_message_iter_close_container(&iter, &dict);
|
||||
|
||||
if (!dbus_connection_send(impl->conn, msg, NULL)) {
|
||||
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", path);
|
||||
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", callimpl->path);
|
||||
telephony_call_unregister(call);
|
||||
return -EIO;
|
||||
}
|
||||
|
|
@ -1695,11 +1701,11 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
|
|||
OFONO_VOICE_CALL_MANAGER_IFACE,
|
||||
"CallAdded");
|
||||
dbus_message_iter_init_append(msg, &iter);
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path);
|
||||
dbus_iter_append_call_properties(&iter, call, true);
|
||||
|
||||
if (!dbus_connection_send(impl->conn, msg, NULL)) {
|
||||
spa_log_error(impl->log, "failed to send CallAdded for %s", path);
|
||||
spa_log_error(impl->log, "failed to send CallAdded for %s", callimpl->path);
|
||||
telephony_call_unregister(call);
|
||||
return -EIO;
|
||||
}
|
||||
|
|
@ -1707,7 +1713,7 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
|
|||
|
||||
telephony_call_commit_properties(call);
|
||||
|
||||
spa_log_debug(impl->log, "registered Call: %s", path);
|
||||
spa_log_debug(impl->log, "registered Call: %s", callimpl->path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,9 +205,10 @@ void dsp_linear_c(void *obj, float * dst,
|
|||
|
||||
|
||||
void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
|
||||
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
|
||||
uint32_t delay, float *dst, const float *src, uint32_t n_samples,
|
||||
float fb, float ff)
|
||||
{
|
||||
if (delay == 0) {
|
||||
if (delay == 0 && fb == 0.0f && ff == 0.0f) {
|
||||
dsp_copy_c(obj, dst, src, n_samples);
|
||||
} else {
|
||||
uint32_t w, o, i;
|
||||
|
|
@ -215,10 +216,20 @@ void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
|
|||
w = *pos;
|
||||
o = n_buffer - delay;
|
||||
|
||||
for (i = 0; i < n_samples; i++) {
|
||||
buffer[w] = buffer[w + n_buffer] = src[i];
|
||||
dst[i] = buffer[w + o];
|
||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||
if (fb == 0.0f && ff == 0.0f) {
|
||||
for (i = 0; i < n_samples; i++) {
|
||||
buffer[w] = buffer[w + n_buffer] = src[i];
|
||||
dst[i] = buffer[w + o];
|
||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < n_samples; i++) {
|
||||
float d = buffer[w + o];
|
||||
float s = src[i];
|
||||
buffer[w] = buffer[w + n_buffer] = s + d * fb;
|
||||
dst[i] = ff * s + d;
|
||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||
}
|
||||
}
|
||||
*pos = w;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ void dsp_biquad_run_##arch (void *obj, struct biquad *bq, uint32_t n_bq, uint32_
|
|||
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples)
|
||||
#define MAKE_DELAY_FUNC(arch) \
|
||||
void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \
|
||||
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
|
||||
uint32_t delay, float *dst, const float *src, uint32_t n_samples, float fb, float ff)
|
||||
|
||||
#define MAKE_FFT_NEW_FUNC(arch) \
|
||||
void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real)
|
||||
|
|
|
|||
|
|
@ -614,34 +614,70 @@ void dsp_biquad_run_sse(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq
|
|||
}
|
||||
|
||||
void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
||||
float *dst, const float *src, uint32_t n_samples)
|
||||
float *dst, const float *src, uint32_t n_samples, float fb, float ff)
|
||||
{
|
||||
__m128 t[1];
|
||||
__m128 t[4];
|
||||
uint32_t w = *pos;
|
||||
uint32_t o = n_buffer - delay;
|
||||
uint32_t n, unrolled;
|
||||
|
||||
if (SPA_IS_ALIGNED(src, 16) &&
|
||||
SPA_IS_ALIGNED(dst, 16))
|
||||
unrolled = n_samples & ~3;
|
||||
else
|
||||
unrolled = 0;
|
||||
if (fb == 0.0f && ff == 0.0f) {
|
||||
if (SPA_IS_ALIGNED(src, 16) &&
|
||||
SPA_IS_ALIGNED(dst, 16) && delay >= 4)
|
||||
unrolled = n_samples & ~3;
|
||||
else
|
||||
unrolled = 0;
|
||||
|
||||
for(n = 0; n < unrolled; n += 4) {
|
||||
t[0] = _mm_load_ps(&src[n]);
|
||||
_mm_storeu_ps(&buffer[w], t[0]);
|
||||
_mm_storeu_ps(&buffer[w+n_buffer], t[0]);
|
||||
t[0] = _mm_loadu_ps(&buffer[w+o]);
|
||||
_mm_store_ps(&dst[n], t[0]);
|
||||
w = w + 4 >= n_buffer ? 0 : w + 4;
|
||||
}
|
||||
for(; n < n_samples; n++) {
|
||||
t[0] = _mm_load_ss(&src[n]);
|
||||
_mm_store_ss(&buffer[w], t[0]);
|
||||
_mm_store_ss(&buffer[w+n_buffer], t[0]);
|
||||
t[0] = _mm_load_ss(&buffer[w+o]);
|
||||
_mm_store_ss(&dst[n], t[0]);
|
||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||
for(n = 0; n < unrolled; n += 4) {
|
||||
t[0] = _mm_load_ps(&src[n]);
|
||||
_mm_storeu_ps(&buffer[w], t[0]);
|
||||
_mm_storeu_ps(&buffer[w+n_buffer], t[0]);
|
||||
t[0] = _mm_loadu_ps(&buffer[w+o]);
|
||||
_mm_store_ps(&dst[n], t[0]);
|
||||
w = w + 4 >= n_buffer ? 0 : w + 4;
|
||||
}
|
||||
for(; n < n_samples; n++) {
|
||||
t[0] = _mm_load_ss(&src[n]);
|
||||
_mm_store_ss(&buffer[w], t[0]);
|
||||
_mm_store_ss(&buffer[w+n_buffer], t[0]);
|
||||
t[0] = _mm_load_ss(&buffer[w+o]);
|
||||
_mm_store_ss(&dst[n], t[0]);
|
||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||
}
|
||||
} else {
|
||||
__m128 fb0 = _mm_set1_ps(fb);
|
||||
__m128 ff0 = _mm_set1_ps(ff);
|
||||
|
||||
if (SPA_IS_ALIGNED(src, 16) &&
|
||||
SPA_IS_ALIGNED(dst, 16) && delay >= 4)
|
||||
unrolled = n_samples & ~3;
|
||||
else
|
||||
unrolled = 0;
|
||||
|
||||
for(n = 0; n < unrolled; n += 4) {
|
||||
t[0] = _mm_loadu_ps(&buffer[w+o]);
|
||||
t[1] = _mm_load_ps(&src[n]);
|
||||
t[2] = _mm_mul_ps(t[0], fb0);
|
||||
t[2] = _mm_add_ps(t[2], t[1]);
|
||||
_mm_storeu_ps(&buffer[w], t[2]);
|
||||
_mm_storeu_ps(&buffer[w+n_buffer], t[2]);
|
||||
t[2] = _mm_mul_ps(t[1], ff0);
|
||||
t[2] = _mm_add_ps(t[2], t[0]);
|
||||
_mm_store_ps(&dst[n], t[2]);
|
||||
w = w + 4 >= n_buffer ? 0 : w + 4;
|
||||
}
|
||||
for(; n < n_samples; n++) {
|
||||
t[0] = _mm_load_ss(&buffer[w+o]);
|
||||
t[1] = _mm_load_ss(&src[n]);
|
||||
t[2] = _mm_mul_ss(t[0], fb0);
|
||||
t[2] = _mm_add_ss(t[2], t[1]);
|
||||
_mm_store_ss(&buffer[w], t[2]);
|
||||
_mm_store_ss(&buffer[w+n_buffer], t[2]);
|
||||
t[2] = _mm_mul_ps(t[1], ff0);
|
||||
t[2] = _mm_add_ps(t[2], t[0]);
|
||||
_mm_store_ss(&dst[n], t[2]);
|
||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||
}
|
||||
}
|
||||
*pos = w;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ struct spa_fga_dsp_methods {
|
|||
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
|
||||
uint32_t n_src, uint32_t n_samples);
|
||||
void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
||||
float *dst, const float *src, uint32_t n_samples);
|
||||
float *dst, const float *src, uint32_t n_samples,
|
||||
float fb, float ff);
|
||||
};
|
||||
|
||||
static inline void spa_fga_dsp_clear(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, uint32_t n_samples)
|
||||
|
|
@ -159,10 +160,11 @@ static inline void spa_fga_dsp_biquad_run(struct spa_fga_dsp *obj,
|
|||
}
|
||||
static inline void spa_fga_dsp_delay(struct spa_fga_dsp *obj,
|
||||
float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
||||
float *dst, const float *src, uint32_t n_samples)
|
||||
float *dst, const float *src, uint32_t n_samples,
|
||||
float fb, float ff)
|
||||
{
|
||||
spa_api_method_v(spa_fga_dsp, &obj->iface, delay, 0,
|
||||
buffer, pos, n_buffer, delay, dst, src, n_samples);
|
||||
buffer, pos, n_buffer, delay, dst, src, n_samples, fb, ff);
|
||||
}
|
||||
|
||||
#endif /* SPA_FGA_DSP_H */
|
||||
|
|
|
|||
|
|
@ -704,16 +704,93 @@ struct convolver_impl {
|
|||
struct convolver *conv;
|
||||
};
|
||||
|
||||
struct finfo {
|
||||
#define TYPE_INVALID 0
|
||||
#define TYPE_SNDFILE 1
|
||||
#define TYPE_HILBERT 2
|
||||
#define TYPE_DIRAC 3
|
||||
#define TYPE_IR 4
|
||||
uint32_t type;
|
||||
|
||||
const char *filename;
|
||||
#ifdef HAVE_SNDFILE
|
||||
static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain, int delay,
|
||||
int offset, int length, int channel, long unsigned *rate, int *n_samples) {
|
||||
float *samples;
|
||||
int i, n;
|
||||
SF_INFO info;
|
||||
SNDFILE *fs;
|
||||
#endif
|
||||
int channels;
|
||||
int def_frames;
|
||||
int max_frames;
|
||||
float latency; /* latency relative to number of samples */
|
||||
uint32_t rate;
|
||||
const char *error;
|
||||
};
|
||||
|
||||
static int finfo_open(const char *filename, struct finfo *info, int rate)
|
||||
{
|
||||
info->filename = filename;
|
||||
if (spa_strstartswith(filename, "/hilbert")) {
|
||||
info->channels = 1;
|
||||
info->rate = rate;
|
||||
info->def_frames = 64;
|
||||
info->max_frames = INT_MAX;
|
||||
info->type = TYPE_HILBERT;
|
||||
info->latency = 0.5f;
|
||||
}
|
||||
else if (spa_strstartswith(filename, "/dirac")) {
|
||||
info->channels = 1;
|
||||
info->def_frames = 1;
|
||||
info->max_frames = 1;
|
||||
info->rate = rate;
|
||||
info->type = TYPE_DIRAC;
|
||||
info->latency = 0.0f;
|
||||
}
|
||||
else if (spa_strstartswith(filename, "/ir:")) {
|
||||
struct spa_json it[1];
|
||||
float v;
|
||||
int rate;
|
||||
info->channels = 1;
|
||||
info->type = TYPE_IR;
|
||||
info->def_frames = 0;
|
||||
if (spa_json_begin_array_relax(&it[0], filename+4, strlen(filename+4)) <= 0)
|
||||
return -EINVAL;
|
||||
if (spa_json_get_int(&it[0], &rate) <= 0)
|
||||
return -EINVAL;
|
||||
info->rate = rate;
|
||||
while (spa_json_get_float(&it[0], &v) > 0)
|
||||
info->def_frames++;
|
||||
info->max_frames = info->def_frames;
|
||||
info->latency = 0.0f;
|
||||
} else {
|
||||
#ifdef HAVE_SNDFILE
|
||||
info->fs = sf_open(filename, SFM_READ, &info->info);
|
||||
if (info->fs == NULL) {
|
||||
info->error = sf_strerror(NULL);
|
||||
return -ENOENT;
|
||||
}
|
||||
info->channels = info->info.channels;
|
||||
info->def_frames = info->info.frames;
|
||||
info->max_frames = info->def_frames;
|
||||
info->rate = info->info.samplerate;
|
||||
info->type = TYPE_SNDFILE;
|
||||
info->latency = 0.0f;
|
||||
#else
|
||||
info->error = "compiled without sndfile support, can't load samples";
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float gain, int delay,
|
||||
int offset, int length, int channel, long unsigned *rate, int *n_samples, int *latency)
|
||||
{
|
||||
float *samples, v;
|
||||
int i, n, h;
|
||||
|
||||
if (length <= 0)
|
||||
length = info->frames;
|
||||
length = info->def_frames;
|
||||
else
|
||||
length = SPA_MIN(length, info->frames);
|
||||
length = SPA_MIN(length, info->max_frames);
|
||||
|
||||
length -= SPA_MIN(offset, length);
|
||||
|
||||
|
|
@ -725,128 +802,107 @@ static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain,
|
|||
if (samples == NULL)
|
||||
return NULL;
|
||||
|
||||
if (offset > 0)
|
||||
sf_seek(f, offset, SEEK_SET);
|
||||
sf_readf_float(f, samples + (delay * info->channels), length);
|
||||
|
||||
channel = channel % info->channels;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
samples[i] = samples[info->channels * i + channel] * gain;
|
||||
|
||||
switch (info->type) {
|
||||
case TYPE_SNDFILE:
|
||||
#ifdef HAVE_SNDFILE
|
||||
if (offset > 0)
|
||||
sf_seek(info->fs, offset, SEEK_SET);
|
||||
sf_readf_float(info->fs, samples + (delay * info->channels), length);
|
||||
for (i = 0; i < n; i++)
|
||||
samples[i] = samples[info->channels * i + channel] * gain;
|
||||
#endif
|
||||
break;
|
||||
case TYPE_HILBERT:
|
||||
gain *= 2 / (float)M_PI;
|
||||
h = length / 2;
|
||||
for (i = 1; i < h; i += 2) {
|
||||
v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
|
||||
samples[delay + h + i] = -v;
|
||||
samples[delay + h - i] = v;
|
||||
}
|
||||
spa_log_info(pl->log, "created hilbert function length %d", length);
|
||||
break;
|
||||
case TYPE_DIRAC:
|
||||
samples[delay] = gain;
|
||||
spa_log_info(pl->log, "created dirac function");
|
||||
break;
|
||||
case TYPE_IR:
|
||||
{
|
||||
struct spa_json it[1];
|
||||
float v;
|
||||
if (spa_json_begin_array_relax(&it[0], info->filename+4, strlen(info->filename+4)) <= 0)
|
||||
return NULL;
|
||||
if (spa_json_get_int(&it[0], &h) <= 0)
|
||||
return NULL;
|
||||
info->rate = h;
|
||||
i = 0;
|
||||
while (spa_json_get_float(&it[0], &v) > 0) {
|
||||
samples[delay + i] = v * gain;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
*n_samples = n;
|
||||
*rate = info->samplerate;
|
||||
*rate = info->rate;
|
||||
*latency = (int) (n * info->latency);
|
||||
return samples;
|
||||
}
|
||||
#endif
|
||||
|
||||
static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset,
|
||||
int length, int channel, long unsigned *rate, int *n_samples)
|
||||
|
||||
static void finfo_close(struct finfo *info)
|
||||
{
|
||||
#ifdef HAVE_SNDFILE
|
||||
SF_INFO infos[MAX_RATES];
|
||||
SNDFILE *fs[MAX_RATES];
|
||||
if (info->type == TYPE_SNDFILE && info->fs != NULL)
|
||||
sf_close(info->fs);
|
||||
#endif
|
||||
}
|
||||
|
||||
spa_zero(infos);
|
||||
spa_zero(fs);
|
||||
|
||||
int diff = INT_MAX;
|
||||
uint32_t best = 0, i;
|
||||
static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset,
|
||||
int length, int channel, long unsigned *rate, int *n_samples, int *latency)
|
||||
{
|
||||
struct finfo finfo[MAX_RATES];
|
||||
int res, diff = INT_MAX;
|
||||
uint32_t best = SPA_ID_INVALID, i;
|
||||
float *samples = NULL;
|
||||
|
||||
spa_zero(finfo);
|
||||
|
||||
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
|
||||
fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]);
|
||||
if (fs[i] == NULL)
|
||||
res = finfo_open(filenames[i], &finfo[i], *rate);
|
||||
if (res < 0)
|
||||
continue;
|
||||
|
||||
if (labs((long)infos[i].samplerate - (long)*rate) < diff) {
|
||||
if (labs((long)finfo[i].rate - (long)*rate) < diff) {
|
||||
best = i;
|
||||
diff = labs((long)infos[i].samplerate - (long)*rate);
|
||||
spa_log_debug(pl->log, "new closest match: %d", infos[i].samplerate);
|
||||
diff = labs((long)finfo[i].rate - (long)*rate);
|
||||
spa_log_debug(pl->log, "new closest match: %d", finfo[i].rate);
|
||||
}
|
||||
}
|
||||
if (fs[best] != NULL) {
|
||||
spa_log_info(pl->log, "loading best rate:%u %s", infos[best].samplerate, filenames[best]);
|
||||
samples = read_samples_from_sf(fs[best], &infos[best], gain,
|
||||
(int) (delay_sec * infos[best].samplerate), offset, length,
|
||||
channel, rate, n_samples);
|
||||
if (best != SPA_ID_INVALID) {
|
||||
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]);
|
||||
samples = finfo_read_samples(pl, &finfo[best], gain,
|
||||
(int) (delay_sec * finfo[best].rate), offset, length,
|
||||
channel, rate, n_samples, latency);
|
||||
} else {
|
||||
char buf[PATH_MAX];
|
||||
spa_log_error(pl->log, "Can't open any sample file (CWD %s):",
|
||||
getcwd(buf, sizeof(buf)));
|
||||
|
||||
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
|
||||
fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]);
|
||||
if (fs[i] == NULL)
|
||||
spa_log_error(pl->log, " failed file %s: %s", filenames[i], sf_strerror(fs[i]));
|
||||
res = finfo_open(filenames[i], &finfo[i], *rate);
|
||||
if (res < 0)
|
||||
spa_log_error(pl->log, " failed file %s: %s", filenames[i], finfo[i].error);
|
||||
else
|
||||
spa_log_warn(pl->log, " unexpectedly opened file %s", filenames[i]);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < MAX_RATES; i++)
|
||||
if (fs[i] != NULL)
|
||||
sf_close(fs[i]);
|
||||
finfo_close(&finfo[i]);
|
||||
|
||||
return samples;
|
||||
#else
|
||||
spa_log_error(pl->log, "compiled without sndfile support, can't load samples: "
|
||||
"using dirac impulse");
|
||||
float *samples = calloc(1, sizeof(float));
|
||||
samples[0] = gain;
|
||||
*n_samples = 1;
|
||||
return samples;
|
||||
#endif
|
||||
}
|
||||
|
||||
static float *create_hilbert(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset,
|
||||
int length, int *n_samples)
|
||||
{
|
||||
float *samples, v;
|
||||
int i, n, h;
|
||||
int delay = (int) (delay_sec * rate);
|
||||
|
||||
if (length <= 0)
|
||||
length = 64;
|
||||
|
||||
length -= SPA_MIN(offset, length);
|
||||
|
||||
n = delay + length;
|
||||
if (n == 0)
|
||||
return NULL;
|
||||
|
||||
samples = calloc(n, sizeof(float));
|
||||
if (samples == NULL)
|
||||
return NULL;
|
||||
|
||||
gain *= 2 / (float)M_PI;
|
||||
h = length / 2;
|
||||
for (i = 1; i < h; i += 2) {
|
||||
v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
|
||||
samples[delay + h + i] = -v;
|
||||
samples[delay + h - i] = v;
|
||||
}
|
||||
*n_samples = n;
|
||||
spa_log_info(pl->log, "created hilbert function");
|
||||
return samples;
|
||||
}
|
||||
|
||||
static float *create_dirac(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset,
|
||||
int length, int *n_samples)
|
||||
{
|
||||
float *samples;
|
||||
int delay = (int) (delay_sec * rate);
|
||||
int n;
|
||||
|
||||
n = delay + 1;
|
||||
|
||||
samples = calloc(n, sizeof(float));
|
||||
if (samples == NULL)
|
||||
return NULL;
|
||||
|
||||
samples[delay] = gain;
|
||||
|
||||
spa_log_info(pl->log, "created dirac function");
|
||||
*n_samples = n;
|
||||
return samples;
|
||||
}
|
||||
|
||||
static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples,
|
||||
|
|
@ -936,10 +992,10 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
|||
uint32_t i = 0;
|
||||
struct spa_json it[2];
|
||||
const char *val;
|
||||
char key[256], v[256];
|
||||
char key[256];
|
||||
char *filenames[MAX_RATES] = { 0 };
|
||||
int blocksize = 0, tailsize = 0;
|
||||
int resample_quality = RESAMPLE_DEFAULT_QUALITY;
|
||||
int resample_quality = RESAMPLE_DEFAULT_QUALITY, def_latency;
|
||||
float gain = 1.0f, delay = 0.0f, latency = -1.0f;
|
||||
unsigned long rate;
|
||||
|
||||
|
|
@ -985,17 +1041,20 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
|||
else if (spa_streq(key, "filename")) {
|
||||
if (spa_json_is_array(val, len)) {
|
||||
spa_json_enter(&it[0], &it[1]);
|
||||
while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 &&
|
||||
while ((len = spa_json_next(&it[1], &val)) > 0 &&
|
||||
i < SPA_N_ELEMENTS(filenames)) {
|
||||
filenames[i] = strdup(v);
|
||||
filenames[i] = malloc(len+1);
|
||||
if (filenames[i] == NULL)
|
||||
return NULL;
|
||||
spa_json_parse_stringn(val, len, filenames[i], len+1);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else if (spa_json_parse_stringn(val, len, v, sizeof(v)) <= 0) {
|
||||
spa_log_error(pl->log, "convolver:filename requires a string or an array");
|
||||
return NULL;
|
||||
} else {
|
||||
filenames[0] = strdup(v);
|
||||
else {
|
||||
filenames[0] = malloc(len+1);
|
||||
if (filenames[0] == NULL)
|
||||
return NULL;
|
||||
spa_json_parse_stringn(val, len, filenames[0], len+1);
|
||||
}
|
||||
}
|
||||
else if (spa_streq(key, "offset")) {
|
||||
|
|
@ -1042,21 +1101,12 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
|||
if (offset < 0)
|
||||
offset = 0;
|
||||
|
||||
if (spa_streq(filenames[0], "/hilbert")) {
|
||||
samples = create_hilbert(pl, filenames[0], gain, SampleRate, delay, offset,
|
||||
length, &n_samples);
|
||||
} else if (spa_streq(filenames[0], "/dirac")) {
|
||||
samples = create_dirac(pl, filenames[0], gain, SampleRate, delay, offset,
|
||||
length, &n_samples);
|
||||
} else {
|
||||
rate = SampleRate;
|
||||
samples = read_closest(pl, filenames, gain, delay, offset,
|
||||
length, channel, &rate, &n_samples);
|
||||
if (samples != NULL && rate != SampleRate) {
|
||||
samples = resample_buffer(pl, samples, &n_samples,
|
||||
rate, SampleRate, resample_quality);
|
||||
}
|
||||
}
|
||||
rate = SampleRate;
|
||||
samples = read_closest(pl, filenames, gain, delay, offset,
|
||||
length, channel, &rate, &n_samples, &def_latency);
|
||||
if (samples != NULL && rate != SampleRate)
|
||||
samples = resample_buffer(pl, samples, &n_samples,
|
||||
rate, SampleRate, resample_quality);
|
||||
|
||||
for (i = 0; i < MAX_RATES; i++)
|
||||
if (filenames[i])
|
||||
|
|
@ -1072,8 +1122,8 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
|||
if (tailsize <= 0)
|
||||
tailsize = SPA_CLAMP(4096, blocksize, 32768);
|
||||
|
||||
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f", n_samples,
|
||||
blocksize, tailsize, delay);
|
||||
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f def-latency:%d", n_samples,
|
||||
blocksize, tailsize, delay, def_latency);
|
||||
|
||||
impl = calloc(1, sizeof(*impl));
|
||||
if (impl == NULL)
|
||||
|
|
@ -1089,7 +1139,7 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
|
|||
goto error;
|
||||
|
||||
if (latency < 0.0f)
|
||||
impl->latency = n_samples;
|
||||
impl->latency = def_latency;
|
||||
else
|
||||
impl->latency = latency * impl->rate;
|
||||
|
||||
|
|
@ -1178,7 +1228,7 @@ struct delay_impl {
|
|||
struct spa_log *log;
|
||||
|
||||
unsigned long rate;
|
||||
float *port[4];
|
||||
float *port[6];
|
||||
|
||||
float delay;
|
||||
uint32_t delay_samples;
|
||||
|
|
@ -1284,7 +1334,8 @@ static void delay_run(void * Instance, unsigned long SampleCount)
|
|||
}
|
||||
if (in != NULL && out != NULL) {
|
||||
spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples,
|
||||
impl->delay_samples, out, in, SampleCount);
|
||||
impl->delay_samples, out, in, SampleCount,
|
||||
impl->port[4][0], impl->port[5][0]);
|
||||
}
|
||||
if (impl->port[3] != NULL)
|
||||
impl->port[3][0] = impl->latency;
|
||||
|
|
@ -1309,6 +1360,16 @@ static struct spa_fga_port delay_ports[] = {
|
|||
.hint = SPA_FGA_HINT_LATENCY,
|
||||
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL,
|
||||
},
|
||||
{ .index = 4,
|
||||
.name = "Feedback",
|
||||
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
|
||||
.def = 0.0f, .min = -10.0f, .max = 10.0f
|
||||
},
|
||||
{ .index = 5,
|
||||
.name = "Feedforward",
|
||||
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
|
||||
.def = 0.0f, .min = -10.0f, .max = 10.0f
|
||||
},
|
||||
};
|
||||
|
||||
static const struct spa_fga_descriptor delay_desc = {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ struct port {
|
|||
struct spa_fraction rate = {};
|
||||
StreamConfiguration streamConfig;
|
||||
|
||||
spa_data_type memtype = SPA_DATA_Invalid;
|
||||
uint32_t buffers_blocks = 1;
|
||||
|
||||
struct buffer buffers[MAX_BUFFERS];
|
||||
|
|
@ -449,8 +448,7 @@ const struct format_info *find_format_info_by_media_type(
|
|||
uint32_t type, uint32_t subtype, uint32_t format)
|
||||
{
|
||||
for (const auto& f : format_info) {
|
||||
if (f.media_type == type && f.media_subtype == subtype
|
||||
&& (f.format == SPA_VIDEO_FORMAT_UNKNOWN || f.format == format))
|
||||
if (f.media_type == type && f.media_subtype == subtype && f.format == format)
|
||||
return &f;
|
||||
}
|
||||
|
||||
|
|
@ -667,7 +665,6 @@ int spa_libcamera_set_format(struct impl *impl, struct port *port,
|
|||
const struct format_info *info = nullptr;
|
||||
uint32_t video_format;
|
||||
struct spa_rectangle *size = nullptr;
|
||||
struct spa_fraction *framerate = nullptr;
|
||||
CameraConfiguration::Status validation;
|
||||
int res;
|
||||
|
||||
|
|
@ -675,18 +672,15 @@ int spa_libcamera_set_format(struct impl *impl, struct port *port,
|
|||
case SPA_MEDIA_SUBTYPE_raw:
|
||||
video_format = format->info.raw.format;
|
||||
size = &format->info.raw.size;
|
||||
framerate = &format->info.raw.framerate;
|
||||
break;
|
||||
case SPA_MEDIA_SUBTYPE_mjpg:
|
||||
case SPA_MEDIA_SUBTYPE_jpeg:
|
||||
video_format = SPA_VIDEO_FORMAT_ENCODED;
|
||||
size = &format->info.mjpg.size;
|
||||
framerate = &format->info.mjpg.framerate;
|
||||
break;
|
||||
case SPA_MEDIA_SUBTYPE_h264:
|
||||
video_format = SPA_VIDEO_FORMAT_ENCODED;
|
||||
size = &format->info.h264.size;
|
||||
framerate = &format->info.h264.framerate;
|
||||
break;
|
||||
default:
|
||||
video_format = SPA_VIDEO_FORMAT_ENCODED;
|
||||
|
|
@ -695,7 +689,7 @@ int spa_libcamera_set_format(struct impl *impl, struct port *port,
|
|||
|
||||
info = find_format_info_by_media_type(format->media_type,
|
||||
format->media_subtype, video_format);
|
||||
if (info == nullptr || size == nullptr || framerate == nullptr) {
|
||||
if (info == nullptr || size == nullptr) {
|
||||
spa_log_error(impl->log, "unknown media type %d %d %d", format->media_type,
|
||||
format->media_subtype, video_format);
|
||||
return -EINVAL;
|
||||
|
|
@ -1210,21 +1204,17 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port,
|
|||
const std::vector<std::unique_ptr<FrameBuffer>> &bufs =
|
||||
impl->allocator.buffers(stream);
|
||||
|
||||
if (n_buffers > 0) {
|
||||
if (bufs.size() != n_buffers)
|
||||
return -EINVAL;
|
||||
if (n_buffers > 0 && bufs.size() != n_buffers)
|
||||
return -EINVAL;
|
||||
|
||||
spa_data *d = buffers[0]->datas;
|
||||
const auto choose_memtype = [](uint32_t t) {
|
||||
if (t != SPA_ID_INVALID && t & (1u << SPA_DATA_DmaBuf))
|
||||
return SPA_DATA_DmaBuf;
|
||||
if (t & (1u << SPA_DATA_MemFd))
|
||||
return SPA_DATA_MemFd;
|
||||
|
||||
if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_DmaBuf)) {
|
||||
port->memtype = SPA_DATA_DmaBuf;
|
||||
} else if (d[0].type & (1u << SPA_DATA_MemFd)) {
|
||||
port->memtype = SPA_DATA_MemFd;
|
||||
} else {
|
||||
spa_log_error(impl->log, "can't use buffers of type %d", d[0].type);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return SPA_DATA_Invalid;
|
||||
};
|
||||
|
||||
for (uint32_t i = 0; i < n_buffers; i++) {
|
||||
struct buffer *b;
|
||||
|
|
@ -1254,9 +1244,17 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port,
|
|||
spa_data *d = buffers[i]->datas;
|
||||
|
||||
for(uint32_t j = 0; j < buffers[i]->n_datas; ++j) {
|
||||
d[j].type = port->memtype;
|
||||
const auto memtype = choose_memtype(d[j].type);
|
||||
if (memtype == SPA_DATA_Invalid) {
|
||||
spa_log_error(impl->log, "can't use buffers of type %" PRIu32, d[j].type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
d[j].type = memtype;
|
||||
d[j].flags = SPA_DATA_FLAG_READABLE;
|
||||
d[j].fd = -1;
|
||||
d[j].mapoffset = 0;
|
||||
d[j].data = nullptr;
|
||||
d[j].chunk->stride = port->streamConfig.stride;
|
||||
d[j].chunk->flags = 0;
|
||||
/* Update parameters according to the plane information */
|
||||
|
|
@ -1286,15 +1284,16 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port,
|
|||
d[j].chunk->size = port->streamConfig.frameSize;
|
||||
}
|
||||
|
||||
if (port->memtype == SPA_DATA_DmaBuf ||
|
||||
port->memtype == SPA_DATA_MemFd) {
|
||||
switch (memtype) {
|
||||
case SPA_DATA_DmaBuf:
|
||||
case SPA_DATA_MemFd:
|
||||
d[j].flags |= SPA_DATA_FLAG_MAPPABLE;
|
||||
d[j].fd = planes[j].fd.get();
|
||||
spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i);
|
||||
d[j].data = nullptr;
|
||||
} else {
|
||||
spa_log_error(impl->log, "invalid buffer type");
|
||||
return -EIO;
|
||||
break;
|
||||
default:
|
||||
spa_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1420,20 +1419,17 @@ int port_get_format(struct impl *impl, struct port *port,
|
|||
spa_pod_builder_add(builder,
|
||||
SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format->info.raw.format),
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.raw.size),
|
||||
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.raw.framerate),
|
||||
0);
|
||||
break;
|
||||
case SPA_MEDIA_SUBTYPE_mjpg:
|
||||
case SPA_MEDIA_SUBTYPE_jpeg:
|
||||
spa_pod_builder_add(builder,
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.mjpg.size),
|
||||
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.mjpg.framerate),
|
||||
0);
|
||||
break;
|
||||
case SPA_MEDIA_SUBTYPE_h264:
|
||||
spa_pod_builder_add(builder,
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.h264.size),
|
||||
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.h264.framerate),
|
||||
0);
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <threads.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <spa/support/loop.h>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <spa/param/video/format-utils.h>
|
||||
#include <spa/param/latency-utils.h>
|
||||
#include <spa/param/tag-utils.h>
|
||||
#include <spa/param/peer-utils.h>
|
||||
#include <spa/debug/format.h>
|
||||
#include <spa/debug/pod.h>
|
||||
#include <spa/debug/log.h>
|
||||
|
|
@ -900,7 +901,6 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
|
|||
switch (id) {
|
||||
case SPA_IO_Position:
|
||||
this->io_position = data;
|
||||
this->recheck_format = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -920,7 +920,7 @@ static int update_param_peer_formats(struct impl *impl)
|
|||
uint8_t buffer[4096];
|
||||
spa_auto(spa_pod_dynamic_builder) b = { 0 };
|
||||
uint32_t state = 0;
|
||||
struct spa_pod *param;
|
||||
struct spa_pod *param, *p, *str;
|
||||
struct spa_pod_frame f;
|
||||
int res;
|
||||
|
||||
|
|
@ -934,7 +934,6 @@ static int update_param_peer_formats(struct impl *impl)
|
|||
|
||||
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
|
||||
spa_pod_builder_push_struct(&b.b, &f);
|
||||
|
||||
while (true) {
|
||||
res = node_port_enum_params_sync(impl, impl->follower,
|
||||
impl->direction, 0,
|
||||
|
|
@ -951,10 +950,24 @@ static int update_param_peer_formats(struct impl *impl)
|
|||
spa_pod_simplify(&b.b, ¶m, param);
|
||||
spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
|
||||
|
||||
str = spa_pod_copy(param);
|
||||
spa_pod_dynamic_builder_clean(&b);
|
||||
|
||||
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
|
||||
spa_peer_param_build_start(&b.b, &f, SPA_PARAM_PeerEnumFormat);
|
||||
SPA_POD_STRUCT_FOREACH(str, p) {
|
||||
spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, p);
|
||||
spa_peer_param_build_add_param(&b.b, 1, p);
|
||||
}
|
||||
param = spa_peer_param_build_end(&b.b, &f);
|
||||
|
||||
spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
|
||||
|
||||
res = spa_node_port_set_param(impl->target,
|
||||
SPA_DIRECTION_REVERSE(impl->direction), 0,
|
||||
SPA_PARAM_PeerFormats, 0, param);
|
||||
SPA_PARAM_PeerEnumFormat, 0, param);
|
||||
|
||||
free(str);
|
||||
impl->recheck_format = false;
|
||||
|
||||
spa_log_debug(impl->log, "done updating peer formats: %d", res);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <spa/param/param.h>
|
||||
#include <spa/param/latency-utils.h>
|
||||
#include <spa/param/tag-utils.h>
|
||||
#include <spa/param/peer-utils.h>
|
||||
#include <spa/pod/filter.h>
|
||||
#include <spa/pod/dynamic.h>
|
||||
#include <spa/debug/types.h>
|
||||
|
|
@ -77,15 +78,16 @@ struct port {
|
|||
uint64_t info_all;
|
||||
struct spa_port_info info;
|
||||
|
||||
#define IDX_EnumFormat 0
|
||||
#define IDX_Meta 1
|
||||
#define IDX_IO 2
|
||||
#define IDX_Format 3
|
||||
#define IDX_Buffers 4
|
||||
#define IDX_Latency 5
|
||||
#define IDX_Tag 6
|
||||
#define IDX_PeerFormats 7
|
||||
#define N_PORT_PARAMS 8
|
||||
#define IDX_EnumFormat 0
|
||||
#define IDX_Meta 1
|
||||
#define IDX_IO 2
|
||||
#define IDX_Format 3
|
||||
#define IDX_Buffers 4
|
||||
#define IDX_Latency 5
|
||||
#define IDX_Tag 6
|
||||
#define IDX_PeerEnumFormat 7
|
||||
#define IDX_PeerCapability 8
|
||||
#define N_PORT_PARAMS 9
|
||||
struct spa_param_info params[N_PORT_PARAMS];
|
||||
|
||||
struct spa_pod *peer_format_pod;
|
||||
|
|
@ -485,7 +487,8 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p
|
|||
port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
|
||||
port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE);
|
||||
port->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE);
|
||||
port->params[IDX_PeerFormats] = SPA_PARAM_INFO(SPA_PARAM_PeerFormats, SPA_PARAM_INFO_WRITE);
|
||||
port->params[IDX_PeerEnumFormat] = SPA_PARAM_INFO(SPA_PARAM_PeerEnumFormat, SPA_PARAM_INFO_WRITE);
|
||||
port->params[IDX_PeerCapability] = SPA_PARAM_INFO(SPA_PARAM_PeerCapability, SPA_PARAM_INFO_WRITE);
|
||||
port->info.params = port->params;
|
||||
port->info.n_params = N_PORT_PARAMS;
|
||||
|
||||
|
|
@ -1599,7 +1602,7 @@ static int port_param_tag(struct impl *this, struct port *port, uint32_t id,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int port_param_peer_formats(struct impl *this, struct port *port, uint32_t index,
|
||||
static int port_param_peer_enum_format(struct impl *this, struct port *port, uint32_t index,
|
||||
const struct spa_pod **param, struct spa_pod_builder *b)
|
||||
{
|
||||
if (index >= port->n_peer_formats)
|
||||
|
|
@ -1664,8 +1667,8 @@ impl_node_port_enum_params(void *object, int seq,
|
|||
case SPA_PARAM_Tag:
|
||||
res = port_param_tag(this, port, id, result.index, ¶m, &b);
|
||||
break;
|
||||
case SPA_PARAM_PeerFormats:
|
||||
res = port_param_peer_formats(this, port, result.index, ¶m, &b);
|
||||
case SPA_PARAM_PeerEnumFormat:
|
||||
res = port_param_peer_enum_format(this, port, result.index, ¶m, &b);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
|
|
@ -1980,7 +1983,7 @@ static int port_set_format(void *object,
|
|||
}
|
||||
|
||||
|
||||
static int port_set_peer_formats(void *object,
|
||||
static int port_set_peer_enum_format(void *object,
|
||||
enum spa_direction direction,
|
||||
uint32_t port_id,
|
||||
uint32_t flags,
|
||||
|
|
@ -1990,14 +1993,15 @@ static int port_set_peer_formats(void *object,
|
|||
struct port *port, *oport;
|
||||
int res = 0;
|
||||
uint32_t i;
|
||||
const struct spa_pod *format;
|
||||
enum spa_direction other = SPA_DIRECTION_REVERSE(direction);
|
||||
static uint32_t subtypes[] = {
|
||||
SPA_MEDIA_SUBTYPE_raw,
|
||||
SPA_MEDIA_SUBTYPE_mjpg,
|
||||
SPA_MEDIA_SUBTYPE_h264 };
|
||||
struct spa_peer_param_info info;
|
||||
void *state = NULL;
|
||||
|
||||
spa_return_val_if_fail(spa_pod_is_struct(formats), -EINVAL);
|
||||
spa_return_val_if_fail(spa_pod_is_object(formats), -EINVAL);
|
||||
|
||||
port = GET_PORT(this, direction, port_id);
|
||||
oport = GET_PORT(this, other, port_id);
|
||||
|
|
@ -2013,9 +2017,10 @@ static int port_set_peer_formats(void *object,
|
|||
port->peer_format_pod = spa_pod_copy(formats);
|
||||
|
||||
for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) {
|
||||
SPA_POD_STRUCT_FOREACH(port->peer_format_pod, format) {
|
||||
state = NULL;
|
||||
while (spa_peer_param_parse(formats, &info, sizeof(info), &state) > 0) {
|
||||
uint32_t media_type, media_subtype;
|
||||
if (!spa_format_parse(format, &media_type, &media_subtype) ||
|
||||
if (!spa_format_parse(info.param, &media_type, &media_subtype) ||
|
||||
media_type != SPA_MEDIA_TYPE_video ||
|
||||
media_subtype != subtypes[i])
|
||||
continue;
|
||||
|
|
@ -2024,13 +2029,14 @@ static int port_set_peer_formats(void *object,
|
|||
}
|
||||
port->peer_formats = calloc(count, sizeof(struct spa_pod *));
|
||||
for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) {
|
||||
SPA_POD_STRUCT_FOREACH(port->peer_format_pod, format) {
|
||||
state = NULL;
|
||||
while (spa_peer_param_parse(port->peer_format_pod, &info, sizeof(info), &state) > 0) {
|
||||
uint32_t media_type, media_subtype;
|
||||
if (!spa_format_parse(format, &media_type, &media_subtype) ||
|
||||
if (!spa_format_parse(info.param, &media_type, &media_subtype) ||
|
||||
media_type != SPA_MEDIA_TYPE_video ||
|
||||
media_subtype != subtypes[i])
|
||||
continue;
|
||||
port->peer_formats[port->n_peer_formats++] = format;
|
||||
port->peer_formats[port->n_peer_formats++] = info.param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2038,7 +2044,7 @@ static int port_set_peer_formats(void *object,
|
|||
oport->params[IDX_EnumFormat].user++;
|
||||
port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
|
||||
port->params[IDX_EnumFormat].user++;
|
||||
port->params[IDX_PeerFormats].user++;
|
||||
port->params[IDX_PeerEnumFormat].user++;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
@ -2068,8 +2074,9 @@ impl_node_port_set_param(void *object,
|
|||
case SPA_PARAM_Format:
|
||||
res = port_set_format(this, direction, port_id, flags, param);
|
||||
break;
|
||||
case SPA_PARAM_PeerFormats:
|
||||
res = port_set_peer_formats(this, direction, port_id, flags, param);
|
||||
case SPA_PARAM_PeerEnumFormat:
|
||||
res = port_set_peer_enum_format(this, direction, port_id, flags, param);
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ avb.properties = {
|
|||
# the addresses this server listens on
|
||||
#ifname = "eth0.2"
|
||||
ifname = "enp3s0"
|
||||
milan = false
|
||||
}
|
||||
|
||||
avb.properties.rules = [
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Requires=pipewire-pulse.socket
|
|||
ConditionUser=!root
|
||||
Wants=pipewire.service pipewire-session-manager.service
|
||||
After=pipewire.service pipewire-session-manager.service
|
||||
BindsTo=pipewire.service
|
||||
Conflicts=pulseaudio.service
|
||||
|
||||
[Service]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
#include <spa/utils/result.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <spa/param/tag-utils.h>
|
||||
#include <spa/param/dict-utils.h>
|
||||
#include <spa/param/peer-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
#include <spa/param/latency-utils.h>
|
||||
#include <spa/debug/format.h>
|
||||
|
|
@ -270,6 +272,34 @@ on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size)
|
|||
}
|
||||
}
|
||||
|
||||
static void parse_peer_capability(struct data *data, const struct spa_pod *param)
|
||||
{
|
||||
struct spa_peer_param_info info;
|
||||
void *state = NULL;
|
||||
|
||||
fprintf(stderr, "peer capability\n");
|
||||
while (spa_peer_param_parse(param, &info, sizeof(info), &state) == 1) {
|
||||
struct spa_param_dict_info di;
|
||||
|
||||
if (spa_param_dict_parse(info.param, &di, sizeof(di)) > 0) {
|
||||
struct spa_dict dict;
|
||||
struct spa_dict_item *items;
|
||||
const struct spa_dict_item *it;
|
||||
|
||||
if (spa_param_dict_info_parse(&di, sizeof(di), &dict, NULL) < 0)
|
||||
return;
|
||||
|
||||
items = alloca(sizeof(struct spa_dict_item) * dict.n_items);
|
||||
if (spa_param_dict_info_parse(&di, sizeof(di), &dict, items) < 0)
|
||||
return;
|
||||
|
||||
spa_dict_for_each(it, &dict)
|
||||
fprintf(stderr, "peer:%u %s: %s\n", info.peer_id, it->key, it->value);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Be notified when the stream param changes. We're only looking at the
|
||||
* format changes.
|
||||
*
|
||||
|
|
@ -294,8 +324,11 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
|
|||
void *d;
|
||||
int32_t mult, size, blocks;
|
||||
|
||||
if (param != NULL && id == SPA_PARAM_Tag) {
|
||||
spa_debug_pod(0, NULL, param);
|
||||
if (param != NULL && (id == SPA_PARAM_Tag || id == SPA_PARAM_PeerCapability)) {
|
||||
if (id == SPA_PARAM_PeerCapability)
|
||||
parse_peer_capability(data, param);
|
||||
else
|
||||
spa_debug_pod(0, NULL, param);
|
||||
return;
|
||||
}
|
||||
if (param != NULL && id == SPA_PARAM_Latency) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <spa/param/tag-utils.h>
|
||||
#include <spa/param/dict-utils.h>
|
||||
#include <spa/debug/pod.h>
|
||||
#include <spa/debug/format.h>
|
||||
|
||||
|
|
@ -219,7 +220,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
|
|||
const struct spa_pod *params[5];
|
||||
uint32_t n_params = 0;
|
||||
|
||||
if (param != NULL && id == SPA_PARAM_Tag) {
|
||||
if (param != NULL && (id == SPA_PARAM_Tag || id == SPA_PARAM_PeerCapability)) {
|
||||
spa_debug_pod(0, NULL, param);
|
||||
return;
|
||||
}
|
||||
|
|
@ -290,7 +291,8 @@ static void do_quit(void *userdata, int signal_number)
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct data data = { 0, };
|
||||
const struct spa_pod *params[2];
|
||||
const struct spa_pod *params[3];
|
||||
uint32_t n_params = 0;
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
|
||||
|
|
@ -318,7 +320,7 @@ int main(int argc, char *argv[])
|
|||
PW_KEY_NODE_SUPPORTS_REQUEST, "1",
|
||||
NULL));
|
||||
|
||||
params[0] = spa_pod_builder_add_object(&b,
|
||||
params[n_params++] = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
|
||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
|
||||
|
|
@ -336,7 +338,13 @@ int main(int argc, char *argv[])
|
|||
spa_tag_build_add_dict(&b,
|
||||
&SPA_DICT_ITEMS(
|
||||
SPA_DICT_ITEM("my-tag-key", "my-special-tag-value")));
|
||||
params[1] = spa_tag_build_end(&b, &f);
|
||||
params[n_params++] = spa_tag_build_end(&b, &f);
|
||||
}
|
||||
{
|
||||
params[n_params++] = spa_param_dict_build_dict(&b, SPA_PARAM_Capability,
|
||||
&SPA_DICT_ITEMS(
|
||||
SPA_DICT_ITEM("my-capability-key", "my-capability-value")));
|
||||
|
||||
}
|
||||
|
||||
pw_stream_add_listener(data.stream,
|
||||
|
|
@ -349,7 +357,7 @@ int main(int argc, char *argv[])
|
|||
PW_ID_ANY,
|
||||
PW_STREAM_FLAG_DRIVER |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS,
|
||||
params, 2);
|
||||
params, n_params);
|
||||
|
||||
pw_main_loop_run(data.loop);
|
||||
|
||||
|
|
|
|||
|
|
@ -1056,6 +1056,44 @@ wait_started (GstPipeWireSrc *this)
|
|||
return state;
|
||||
}
|
||||
|
||||
|
||||
static enum pw_stream_state
|
||||
wait_negotiated (GstPipeWireSrc *this)
|
||||
{
|
||||
enum pw_stream_state state;
|
||||
const char *error = NULL;
|
||||
struct timespec abstime;
|
||||
|
||||
pw_thread_loop_get_time (this->stream->core->loop, &abstime,
|
||||
GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
|
||||
|
||||
while (TRUE) {
|
||||
state = pw_stream_get_state (this->stream->pwstream, &error);
|
||||
|
||||
GST_DEBUG_OBJECT (this, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state));
|
||||
if (state == PW_STREAM_STATE_ERROR)
|
||||
break;
|
||||
if (this->flushing) {
|
||||
state = PW_STREAM_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->negotiated)
|
||||
break;
|
||||
|
||||
if (this->autoconnect) {
|
||||
if (pw_thread_loop_timed_wait_full (this->stream->core->loop, &abstime) < 0) {
|
||||
state = PW_STREAM_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pw_thread_loop_wait (this->stream->core->loop);
|
||||
}
|
||||
}
|
||||
GST_DEBUG_OBJECT (this, state != PW_STREAM_STATE_ERROR ? "got negotiated signal" : "error during negotiation");
|
||||
return state;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
|
||||
{
|
||||
|
|
@ -1067,7 +1105,6 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
|
|||
g_autoptr (GPtrArray) possible = NULL;
|
||||
gboolean result = FALSE;
|
||||
const char *error = NULL;
|
||||
struct timespec abstime;
|
||||
uint32_t target_id;
|
||||
|
||||
/* first see what is possible on our source pad */
|
||||
|
|
@ -1177,26 +1214,8 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
|
|||
(const struct spa_pod **)possible->pdata,
|
||||
possible->len);
|
||||
|
||||
pw_thread_loop_get_time (pwsrc->stream->core->loop, &abstime,
|
||||
GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
|
||||
|
||||
while (TRUE) {
|
||||
enum pw_stream_state state = pw_stream_get_state (pwsrc->stream->pwstream, &error);
|
||||
|
||||
GST_DEBUG_OBJECT (basesrc, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state));
|
||||
if (state == PW_STREAM_STATE_ERROR || pwsrc->flushing)
|
||||
goto connect_error;
|
||||
|
||||
if (pwsrc->negotiated)
|
||||
break;
|
||||
|
||||
if (pwsrc->autoconnect) {
|
||||
if (pw_thread_loop_timed_wait_full (pwsrc->stream->core->loop, &abstime) < 0)
|
||||
goto connect_error;
|
||||
} else {
|
||||
pw_thread_loop_wait (pwsrc->stream->core->loop);
|
||||
}
|
||||
}
|
||||
if (wait_negotiated(pwsrc) == PW_STREAM_STATE_ERROR)
|
||||
goto connect_error;
|
||||
|
||||
negotiated_caps = g_steal_pointer (&pwsrc->caps);
|
||||
pw_thread_loop_unlock (pwsrc->stream->core->loop);
|
||||
|
|
@ -1724,12 +1743,21 @@ gst_pipewire_src_change_state (GstElement * element, GstStateChange transition)
|
|||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||
/* uncork and start recording */
|
||||
GST_DEBUG_OBJECT (this, "activating stream");
|
||||
|
||||
pw_thread_loop_lock (this->stream->core->loop);
|
||||
pw_stream_set_active (this->stream->pwstream, true);
|
||||
/* if state have been paused for longer time, the underlying node might
|
||||
* be moved from idle to suspended, which would mean format cleared via
|
||||
* handle_format_change. Wait for new format to avoid basesrc calling
|
||||
* create() and get not-negotiated error as response. */
|
||||
if (wait_negotiated(this) == PW_STREAM_STATE_ERROR)
|
||||
goto open_failed;
|
||||
pw_thread_loop_unlock (this->stream->core->loop);
|
||||
break;
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
/* stop recording ASAP by corking */
|
||||
GST_DEBUG_OBJECT (this, "in-activating stream");
|
||||
pw_thread_loop_lock (this->stream->core->loop);
|
||||
pw_stream_set_active (this->stream->pwstream, false);
|
||||
pw_thread_loop_unlock (this->stream->core->loop);
|
||||
|
|
|
|||
|
|
@ -736,7 +736,21 @@ if build_module_avb
|
|||
'module-avb/acmp.c',
|
||||
'module-avb/aecp.c',
|
||||
'module-avb/aecp-aem.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-available.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-get-set-clock-source.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-get-set-stream-format.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-get-set-configuration.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c',
|
||||
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
|
||||
'module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c',
|
||||
'module-avb/es-builder.c',
|
||||
'module-avb/avdecc.c',
|
||||
'module-avb/descriptors.c',
|
||||
'module-avb/maap.c',
|
||||
'module-avb/mmrp.c',
|
||||
'module-avb/mrp.c',
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include "msrp.h"
|
||||
#include "internal.h"
|
||||
#include "stream.h"
|
||||
#include "aecp-aem-descriptors.h"
|
||||
#include "aecp-aem-state.h"
|
||||
|
||||
static const uint8_t mac[6] = AVB_BROADCAST_MAC;
|
||||
|
||||
|
|
@ -77,12 +79,64 @@ static void pending_free(struct acmp *acmp, struct pending *p)
|
|||
free(p);
|
||||
}
|
||||
|
||||
static void pending_destroy(struct acmp *acmp)
|
||||
{
|
||||
struct pending *p, *t;
|
||||
for (uint32_t list_id = 0; list_id < PENDING_CONTROLLER; list_id++) {
|
||||
spa_list_for_each_safe(p, t, &acmp->pending[list_id], link) {
|
||||
pending_free(acmp, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct msg_info {
|
||||
uint16_t type;
|
||||
const char *name;
|
||||
int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len);
|
||||
};
|
||||
|
||||
static struct stream *find_stream(struct server *server, enum spa_direction direction,
|
||||
uint16_t index)
|
||||
{
|
||||
uint16_t type;
|
||||
struct descriptor *desc;
|
||||
struct stream *stream;
|
||||
|
||||
switch (direction) {
|
||||
case SPA_DIRECTION_INPUT:
|
||||
type = AVB_AEM_DESC_STREAM_INPUT;
|
||||
break;
|
||||
case SPA_DIRECTION_OUTPUT:
|
||||
type = AVB_AEM_DESC_STREAM_OUTPUT;
|
||||
break;
|
||||
default:
|
||||
pw_log_error("Unkown direction\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
desc = server_find_descriptor(server, type, index);
|
||||
if (!desc) {
|
||||
pw_log_error("Could not find stream type %u index %u\n",
|
||||
type, index);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case SPA_DIRECTION_INPUT:
|
||||
struct aecp_aem_stream_input_state *stream_in;
|
||||
stream_in = desc->ptr;
|
||||
stream = &stream_in->stream;
|
||||
break;
|
||||
case SPA_DIRECTION_OUTPUT:
|
||||
struct aecp_aem_stream_output_state *stream_out;
|
||||
stream_out = desc->ptr;
|
||||
stream = &stream_out->stream;
|
||||
break;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len)
|
||||
{
|
||||
struct server *server = acmp->server;
|
||||
|
|
@ -120,8 +174,7 @@ static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void
|
|||
return 0;
|
||||
|
||||
memcpy(buf, m, len);
|
||||
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
|
||||
reply->talker_unique_id);
|
||||
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
|
||||
if (stream == NULL) {
|
||||
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
|
||||
goto done;
|
||||
|
|
@ -130,7 +183,7 @@ static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void
|
|||
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
|
||||
reply->stream_id = htobe64(stream->id);
|
||||
|
||||
stream_activate(stream, now);
|
||||
stream_activate(stream, ntohs(reply->talker_unique_id), now);
|
||||
|
||||
memcpy(reply->stream_dest_mac, stream->addr, 6);
|
||||
reply->connection_count = htons(1);
|
||||
|
|
@ -169,14 +222,13 @@ static int handle_connect_tx_response(struct acmp *acmp, uint64_t now, const voi
|
|||
reply->sequence_id = htons(pending->old_sequence_id);
|
||||
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE);
|
||||
|
||||
stream = server_find_stream(server, SPA_DIRECTION_INPUT,
|
||||
ntohs(reply->listener_unique_id));
|
||||
stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
|
||||
if (stream == NULL)
|
||||
return 0;
|
||||
|
||||
stream->peer_id = be64toh(reply->stream_id);
|
||||
memcpy(stream->addr, reply->stream_dest_mac, 6);
|
||||
stream_activate(stream, now);
|
||||
stream_activate(stream, ntohs(reply->listener_unique_id), now);
|
||||
|
||||
res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size);
|
||||
|
||||
|
|
@ -199,8 +251,7 @@ static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const v
|
|||
return 0;
|
||||
|
||||
memcpy(buf, m, len);
|
||||
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
|
||||
reply->talker_unique_id);
|
||||
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
|
||||
if (stream == NULL) {
|
||||
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
|
||||
goto done;
|
||||
|
|
@ -243,8 +294,7 @@ static int handle_disconnect_tx_response(struct acmp *acmp, uint64_t now, const
|
|||
reply->sequence_id = htons(pending->old_sequence_id);
|
||||
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE);
|
||||
|
||||
stream = server_find_stream(server, SPA_DIRECTION_INPUT,
|
||||
reply->listener_unique_id);
|
||||
stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
|
||||
if (stream == NULL)
|
||||
return 0;
|
||||
|
||||
|
|
@ -369,6 +419,7 @@ static void acmp_destroy(void *data)
|
|||
{
|
||||
struct acmp *acmp = data;
|
||||
spa_hook_remove(&acmp->server_listener);
|
||||
pending_destroy(acmp);
|
||||
free(acmp);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,7 +156,14 @@ static int adp_message(void *data, uint64_t now, const void *message, int len)
|
|||
static void adp_destroy(void *data)
|
||||
{
|
||||
struct adp *adp = data;
|
||||
struct entity *e, *t;
|
||||
|
||||
spa_hook_remove(&adp->server_listener);
|
||||
|
||||
spa_list_for_each_safe(e, t, &adp->entities, link) {
|
||||
entity_free(e);
|
||||
}
|
||||
|
||||
free(adp);
|
||||
}
|
||||
|
||||
|
|
|
|||
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__
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
/* AVB support */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#ifndef AVB_AECP_AEM_DESCRIPTORS_H
|
||||
#define AVB_AECP_AEM_DESCRIPTORS_H
|
||||
|
||||
#include "internal.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* IEEE 1722.1-2021, Table 7-1 - Descriptor Types
|
||||
*/
|
||||
#define AVB_AEM_DESC_ENTITY 0x0000
|
||||
#define AVB_AEM_DESC_CONFIGURATION 0x0001
|
||||
#define AVB_AEM_DESC_AUDIO_UNIT 0x0002
|
||||
|
|
@ -45,8 +49,17 @@
|
|||
#define AVB_AEM_DESC_SIGNAL_TRANSCODER 0x0023
|
||||
#define AVB_AEM_DESC_CLOCK_DOMAIN 0x0024
|
||||
#define AVB_AEM_DESC_CONTROL_BLOCK 0x0025
|
||||
/** IEEE 1722.1-2021 Table-7 has up to descriptor 0x0029, reserved for future */
|
||||
#define AVB_AEM_DESC_LAST_RESERVED_17221 0x0029
|
||||
#define AVB_AEM_DESC_INVALID 0xffff
|
||||
|
||||
/* IEEE 1722.1-2021, Table 7-24 - Port Flags */
|
||||
// No flag is not defined in table
|
||||
#define AVB_AEM_PORT_FLAG_NO_FLAG 0x0000
|
||||
#define AVB_AEM_PORT_FLAG_CLOCK_SYNC_SOURCE 0x0001
|
||||
#define AVB_AEM_PORT_FLAG_ASYNC_SAMPLE_RATE_CONV 0x0002
|
||||
#define AVB_AEM_PORT_FLAG_SYNC_SAMPLE_RATE_CONV 0x0004
|
||||
|
||||
struct avb_aem_desc_entity {
|
||||
uint64_t entity_id;
|
||||
uint64_t entity_model_id;
|
||||
|
|
@ -127,6 +140,42 @@ struct avb_aem_desc_audio_unit {
|
|||
struct avb_aem_desc_sampling_rate sampling_rates[0];
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
/* IEEE 1722.1-2021, Table 7-28 - AUDIO_CLUSTER format values */
|
||||
#define AVB_AEM_AUDIO_CLUSTER_TYPE_IEC60958 0x00
|
||||
#define AVB_AEM_AUDIO_CLUSTER_TYPE_MBLA 0x40
|
||||
#define AVB_AEM_AUDIO_CLUSTER_TYPE_MIDI 0x80
|
||||
#define AVB_AEM_AUDIO_CLUSTER_TYPE_SMPTE 0x88
|
||||
|
||||
struct avb_aem_desc_audio_cluster {
|
||||
char object_name[64];
|
||||
uint16_t localized_description;
|
||||
|
||||
uint16_t signal_type;
|
||||
uint16_t signal_index;
|
||||
uint16_t signal_output;
|
||||
uint32_t path_latency;
|
||||
uint32_t block_latency;
|
||||
uint16_t channel_count;
|
||||
uint8_t format;
|
||||
uint8_t aes3_data_type_ref;
|
||||
uint16_t aes3_data_type;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
#define AVB_AEM_AUDIO_MAPPING_FORMAT_OFFSET (8)
|
||||
|
||||
struct avb_aem_audio_mapping_format {
|
||||
uint16_t mapping_stream_index;
|
||||
uint16_t mapping_stream_channel;
|
||||
uint16_t mapping_cluster_offset;
|
||||
uint16_t mapping_cluster_channel;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct avb_aem_desc_audio_map {
|
||||
uint16_t mapping_offset;
|
||||
uint16_t number_of_mappings;
|
||||
struct avb_aem_audio_mapping_format mappings[0];
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
#define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0)
|
||||
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1)
|
||||
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2)
|
||||
|
|
@ -197,6 +246,15 @@ struct avb_aem_desc_clock_source {
|
|||
uint16_t clock_source_location_index;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct avb_aem_desc_clock_domain {
|
||||
char object_name[64];
|
||||
uint16_t localized_description;
|
||||
uint16_t clock_source_index;
|
||||
uint16_t descriptor_counts_offset;
|
||||
uint16_t clock_sources_count;
|
||||
uint16_t clock_sources[0];
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct avb_aem_desc_locale {
|
||||
char locale_identifier[64];
|
||||
uint16_t number_of_strings;
|
||||
|
|
@ -224,4 +282,34 @@ struct avb_aem_desc_stream_port {
|
|||
uint16_t base_map;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct avb_aem_desc_value_format {
|
||||
uint8_t minimum;
|
||||
uint8_t maximum;
|
||||
uint8_t step;
|
||||
uint8_t default_value;
|
||||
uint8_t current_value;
|
||||
uint16_t unit;
|
||||
uint16_t localized_description;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct avb_aem_desc_control {
|
||||
char object_name[64];
|
||||
uint16_t localized_description;
|
||||
|
||||
uint32_t block_latency;
|
||||
uint32_t control_latency;
|
||||
uint16_t control_domain;
|
||||
uint16_t control_value_type;
|
||||
uint64_t control_type;
|
||||
uint32_t reset_time;
|
||||
|
||||
uint16_t descriptor_counts_offset;
|
||||
|
||||
uint16_t number_of_values;
|
||||
uint16_t signal_type;
|
||||
uint16_t signal_index;
|
||||
uint16_t signal_output;
|
||||
struct avb_aem_desc_value_format value_format[0];
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
#endif /* AVB_AECP_AEM_DESCRIPTORS_H */
|
||||
|
|
|
|||
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 */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "aecp-aem.h"
|
||||
#include "aecp-aem-descriptors.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-resp-helpers.h"
|
||||
#include "utils.h"
|
||||
|
||||
static int reply_status(struct aecp *aecp, int status, const void *m, int len)
|
||||
{
|
||||
struct server *server = aecp->server;
|
||||
uint8_t buf[len];
|
||||
struct avb_ethernet_header *h = (void*)buf;
|
||||
struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void);
|
||||
/* The headers including the command and response of the system */
|
||||
#include "aecp-aem-cmds-resps/cmd-available.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-get-set-configuration.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-lock-entity.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-get-set-name.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-get-set-stream-format.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-get-set-clock-source.h"
|
||||
#include "aecp-aem-cmds-resps/cmd-lock-entity.h"
|
||||
|
||||
memcpy(buf, m, len);
|
||||
AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
|
||||
AVB_PACKET_AECP_SET_STATUS(reply, status);
|
||||
|
||||
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
|
||||
}
|
||||
|
||||
static int reply_not_implemented(struct aecp *aecp, const void *m, int len)
|
||||
{
|
||||
return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len);
|
||||
}
|
||||
|
||||
static int reply_success(struct aecp *aecp, const void *m, int len)
|
||||
{
|
||||
return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len);
|
||||
}
|
||||
|
||||
/* ACQUIRE_ENTITY */
|
||||
static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
|
||||
static int handle_acquire_entity_avb_legacy(struct aecp *aecp, int64_t now,
|
||||
const void *m, int len)
|
||||
{
|
||||
struct server *server = aecp->server;
|
||||
const struct avb_packet_aecp_aem *p = m;
|
||||
|
|
@ -45,7 +37,8 @@ static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
|
|||
|
||||
desc = server_find_descriptor(server, desc_type, desc_id);
|
||||
if (desc == NULL)
|
||||
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||
return reply_status(aecp,
|
||||
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||
|
||||
if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0)
|
||||
return reply_not_implemented(aecp, m, len);
|
||||
|
|
@ -54,7 +47,8 @@ static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
|
|||
}
|
||||
|
||||
/* LOCK_ENTITY */
|
||||
static int handle_lock_entity(struct aecp *aecp, const void *m, int len)
|
||||
static int handle_lock_entity_avb_legacy(struct aecp *aecp, int64_t now,
|
||||
const void *m, int len)
|
||||
{
|
||||
struct server *server = aecp->server;
|
||||
const struct avb_packet_aecp_aem *p = m;
|
||||
|
|
@ -78,7 +72,7 @@ static int handle_lock_entity(struct aecp *aecp, const void *m, int len)
|
|||
}
|
||||
|
||||
/* READ_DESCRIPTOR */
|
||||
static int handle_read_descriptor(struct aecp *aecp, const void *m, int len)
|
||||
static int handle_read_descriptor_common(struct aecp *aecp, int64_t now, const void *m, int len)
|
||||
{
|
||||
struct server *server = aecp->server;
|
||||
const struct avb_ethernet_header *h = m;
|
||||
|
|
@ -120,7 +114,8 @@ static int handle_read_descriptor(struct aecp *aecp, const void *m, int len)
|
|||
}
|
||||
|
||||
/* GET_AVB_INFO */
|
||||
static int handle_get_avb_info(struct aecp *aecp, const void *m, int len)
|
||||
static int handle_get_avb_info_common(struct aecp *aecp, int64_t now,
|
||||
const void *m, int len)
|
||||
{
|
||||
struct server *server = aecp->server;
|
||||
const struct avb_ethernet_header *h = m;
|
||||
|
|
@ -168,95 +163,227 @@ static int handle_get_avb_info(struct aecp *aecp, const void *m, int len)
|
|||
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size);
|
||||
}
|
||||
|
||||
/* AEM_COMMAND */
|
||||
/* TODO in the case the AVB mode allows you to modifiy a Milan readonly
|
||||
descriptor, then create a array of is_readonly depending on the mode used */
|
||||
static const char * const cmd_names[] = {
|
||||
[AVB_AECP_AEM_CMD_ACQUIRE_ENTITY] = "acquire-entity",
|
||||
[AVB_AECP_AEM_CMD_LOCK_ENTITY] = "lock-entity",
|
||||
[AVB_AECP_AEM_CMD_ENTITY_AVAILABLE] = "entity-available",
|
||||
[AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE] = "controller-available",
|
||||
[AVB_AECP_AEM_CMD_READ_DESCRIPTOR] = "read-descriptor",
|
||||
[AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR] = "write-descriptor",
|
||||
[AVB_AECP_AEM_CMD_SET_CONFIGURATION] = "set-configuration",
|
||||
[AVB_AECP_AEM_CMD_GET_CONFIGURATION] = "get-configuration",
|
||||
[AVB_AECP_AEM_CMD_SET_STREAM_FORMAT] = "set-stream-format",
|
||||
[AVB_AECP_AEM_CMD_GET_STREAM_FORMAT] = "get-stream-format",
|
||||
[AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT] = "set-video-format",
|
||||
[AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT] = "get-video-format",
|
||||
[AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT] = "set-sensor-format",
|
||||
[AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT] = "get-sensor-format",
|
||||
[AVB_AECP_AEM_CMD_SET_STREAM_INFO] = "set-stream-info",
|
||||
[AVB_AECP_AEM_CMD_GET_STREAM_INFO] = "get-stream-info",
|
||||
[AVB_AECP_AEM_CMD_SET_NAME] = "set-name",
|
||||
[AVB_AECP_AEM_CMD_GET_NAME] = "get-name",
|
||||
[AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID] = "set-association-id",
|
||||
[AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID] = "get-association-id",
|
||||
[AVB_AECP_AEM_CMD_SET_SAMPLING_RATE] = "set-sampling-rate",
|
||||
[AVB_AECP_AEM_CMD_GET_SAMPLING_RATE] = "get-sampling-rate",
|
||||
[AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE] = "set-clock-source",
|
||||
[AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE] = "get-clock-source",
|
||||
[AVB_AECP_AEM_CMD_SET_CONTROL] = "set-control",
|
||||
[AVB_AECP_AEM_CMD_GET_CONTROL] = "get-control",
|
||||
[AVB_AECP_AEM_CMD_INCREMENT_CONTROL] = "increment-control",
|
||||
[AVB_AECP_AEM_CMD_DECREMENT_CONTROL] = "decrement-control",
|
||||
[AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR] = "set-signal-selector",
|
||||
[AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR] = "get-signal-selector",
|
||||
[AVB_AECP_AEM_CMD_SET_MIXER] = "set-mixer",
|
||||
[AVB_AECP_AEM_CMD_GET_MIXER] = "get-mixer",
|
||||
[AVB_AECP_AEM_CMD_SET_MATRIX] = "set-matrix",
|
||||
[AVB_AECP_AEM_CMD_GET_MATRIX] = "get-matrix",
|
||||
[AVB_AECP_AEM_CMD_START_STREAMING] = "start-streaming",
|
||||
[AVB_AECP_AEM_CMD_STOP_STREAMING] = "stop-streaming",
|
||||
[AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION] = "register-unsolicited-notification",
|
||||
[AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION] = "deregister-unsolicited-notification",
|
||||
[AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION] = "identify-notification",
|
||||
[AVB_AECP_AEM_CMD_GET_AVB_INFO] = "get-avb-info",
|
||||
[AVB_AECP_AEM_CMD_GET_AS_PATH] = "get-as-path",
|
||||
[AVB_AECP_AEM_CMD_GET_COUNTERS] = "get-counters",
|
||||
[AVB_AECP_AEM_CMD_REBOOT] = "reboot",
|
||||
[AVB_AECP_AEM_CMD_GET_AUDIO_MAP] = "get-audio-map",
|
||||
[AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS] = "add-audio-mappings",
|
||||
[AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS] = "remove-audio-mappings",
|
||||
[AVB_AECP_AEM_CMD_GET_VIDEO_MAP] = "get-video-map",
|
||||
[AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS] = "add-video-mappings",
|
||||
[AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS] = "remove-video-mappings",
|
||||
[AVB_AECP_AEM_CMD_GET_SENSOR_MAP] = "get-sensor-map"
|
||||
};
|
||||
|
||||
/* AEM_COMMAND */
|
||||
struct cmd_info {
|
||||
uint16_t type;
|
||||
const char *name;
|
||||
int (*handle) (struct aecp *aecp, const void *p, int len);
|
||||
/**
|
||||
* \brief Is Readonly is a hint used to decide whether or not the
|
||||
* unsollocited notifications is to be sent for this descriptor or not
|
||||
*/
|
||||
const bool is_readonly;
|
||||
|
||||
/**
|
||||
* \brief handle a command for a specific descriptor
|
||||
*/
|
||||
int (*handle_command) (struct aecp *aecp, int64_t now, const void *p,
|
||||
int len);
|
||||
|
||||
/**
|
||||
* \brief Response are sent upon changes that occure internally
|
||||
* and that are then propagated to the network and are not
|
||||
* unsollicited notifications
|
||||
*/
|
||||
int (*handle_response) (struct aecp *aecp, int64_t now, const void *p,
|
||||
int len);
|
||||
|
||||
/**
|
||||
* \brief Handling of the unsolicited notification that are used
|
||||
* to inform subscribed controller about the change of status of
|
||||
* a specific descriptor or the counter associted with it
|
||||
*/
|
||||
int (*handle_unsol_timer) (struct aecp *aecp, int64_t now);
|
||||
};
|
||||
|
||||
static const struct cmd_info cmd_info[] = {
|
||||
{ AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, },
|
||||
{ AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, },
|
||||
{ AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE, "controller-available", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_READ_DESCRIPTOR, "read-descriptor", handle_read_descriptor, },
|
||||
{ AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR, "write-descriptor", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_CONFIGURATION, "set-configuration", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT, "get-video-format", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT, "set-sensor-format", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT, "get-sensor-format", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_SAMPLING_RATE, "set-sampling-rate", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, "get-sampling-rate", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, "set-clock-source", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION, "deregister-unsolicited-notification", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION, "identify-notification", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_AVB_INFO, "get-avb-info", handle_get_avb_info, },
|
||||
{ AVB_AECP_AEM_CMD_GET_AS_PATH, "get-as-path", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_COUNTERS, "get-counters", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_REBOOT, "reboot", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_AUDIO_MAP, "get-audio-map", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS, "add-audio-mappings", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS, "remove-audio-mappings", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_VIDEO_MAP, "get-video-map", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS, "add-video-mappings", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS, "remove-video-mappings", NULL, },
|
||||
{ AVB_AECP_AEM_CMD_GET_SENSOR_MAP, "get-sensor-map", NULL, }
|
||||
};
|
||||
|
||||
static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name)
|
||||
{
|
||||
SPA_FOR_EACH_ELEMENT_VAR(cmd_info, i) {
|
||||
if ((name == NULL && type == i->type) ||
|
||||
(name != NULL && spa_streq(name, i->name)))
|
||||
return i;
|
||||
#define AECP_AEM_HANDLE_CMD(cmd, readonly_desc, handle_exec) \
|
||||
[cmd] = { \
|
||||
.is_readonly = readonly_desc, \
|
||||
.handle_command = handle_exec \
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#define AECP_AEM_HANDLE_RESP(cmd, handle_cmd, handle_exec_unsol) \
|
||||
[cmd] = { \
|
||||
.name = name_str, \
|
||||
.is_readonly = false, \
|
||||
.handle_response = handle_cmd \
|
||||
}
|
||||
|
||||
#define AECP_AEM_CMD_RESP_AND_UNSOL(cmd, readonly_desc, handle_exec, \
|
||||
handle_exec_unsol) \
|
||||
[cmd] = { \
|
||||
.name = name_str, \
|
||||
.is_readonly = readonly_desc, \
|
||||
.handle = handle_exec, \
|
||||
.handle_unsol = handle_exec_unsol \
|
||||
}
|
||||
|
||||
static const struct cmd_info cmd_info_avb_legacy[] = {
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
|
||||
handle_acquire_entity_avb_legacy),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, true,
|
||||
handle_lock_entity_avb_legacy),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
|
||||
handle_cmd_get_configuration_common),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
|
||||
handle_read_descriptor_common),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
|
||||
handle_get_avb_info_common),
|
||||
};
|
||||
|
||||
static const struct cmd_info cmd_info_milan_v12[] = {
|
||||
/** Milan V1.2 should not implement acquire */
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
|
||||
direct_reply_not_supported),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, false,
|
||||
handle_cmd_lock_entity_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, true,
|
||||
handle_cmd_entity_available_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, false,
|
||||
handle_cmd_set_stream_format_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, true,
|
||||
handle_cmd_get_stream_format_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CONFIGURATION, false,
|
||||
handle_cmd_set_configuration_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
|
||||
handle_cmd_get_configuration_common),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
|
||||
handle_read_descriptor_common),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION,
|
||||
false, handle_cmd_register_unsol_notif_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION,
|
||||
false, handle_cmd_deregister_unsol_notif_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
|
||||
handle_get_avb_info_common),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_NAME, false,
|
||||
handle_cmd_set_name_common),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_NAME, true,
|
||||
handle_cmd_get_name_common),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, false,
|
||||
handle_cmd_set_clock_source_milan_v12),
|
||||
|
||||
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, true,
|
||||
handle_cmd_get_clock_source_milan_v12),
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const struct cmd_info *cmd_info;
|
||||
size_t count;
|
||||
} cmd_info_modes[AVB_MODE_MAX] = {
|
||||
[AVB_MODE_LEGACY] = {
|
||||
.cmd_info = cmd_info_avb_legacy,
|
||||
.count = SPA_N_ELEMENTS(cmd_info_avb_legacy),
|
||||
},
|
||||
[AVB_MODE_MILAN_V12] = {
|
||||
.cmd_info = cmd_info_milan_v12,
|
||||
.count = SPA_N_ELEMENTS(cmd_info_milan_v12),
|
||||
},
|
||||
};
|
||||
|
||||
int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len)
|
||||
{
|
||||
const struct avb_ethernet_header *h = m;
|
||||
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||
uint16_t cmd_type;
|
||||
struct server *server = aecp->server;
|
||||
const struct cmd_info *info;
|
||||
struct timespec ts_now = {0};
|
||||
int64_t now;
|
||||
|
||||
cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p);
|
||||
|
||||
info = find_cmd_info(cmd_type, NULL);
|
||||
if (info == NULL)
|
||||
pw_log_info("mode: %s aem command %s",
|
||||
get_avb_mode_str(server->avb_mode), cmd_names[cmd_type]);
|
||||
|
||||
if (cmd_info_modes[server->avb_mode].count <= cmd_type) {
|
||||
pw_log_warn("Too many %d vs exp. %zu\n", cmd_type,
|
||||
cmd_info_modes[server->avb_mode].count);
|
||||
return reply_not_implemented(aecp, m, len);
|
||||
}
|
||||
|
||||
info = &cmd_info_modes[server->avb_mode].cmd_info[cmd_type];
|
||||
if (!info || !info->handle_command )
|
||||
return reply_not_implemented(aecp, m, len);
|
||||
|
||||
pw_log_info("aem command %s", info->name);
|
||||
|
||||
if (info->handle == NULL)
|
||||
return reply_not_implemented(aecp, m, len);
|
||||
if (clock_gettime(CLOCK_TAI, &ts_now)) {
|
||||
pw_log_warn("clock_gettime(CLOCK_TAI): %m\n");
|
||||
}
|
||||
|
||||
return info->handle(aecp, m, len);
|
||||
now = SPA_TIMESPEC_TO_NSEC(&ts_now);
|
||||
|
||||
return info->handle_command(aecp, now, m, len);
|
||||
}
|
||||
|
||||
int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len)
|
||||
|
|
|
|||
|
|
@ -6,99 +6,7 @@
|
|||
#define AVB_AEM_H
|
||||
|
||||
#include "aecp.h"
|
||||
|
||||
#define AVB_AECP_AEM_STATUS_SUCCESS 0
|
||||
#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1
|
||||
#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2
|
||||
#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3
|
||||
#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4
|
||||
#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5
|
||||
#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6
|
||||
#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7
|
||||
#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8
|
||||
#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9
|
||||
#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10
|
||||
#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11
|
||||
#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12
|
||||
|
||||
#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000
|
||||
#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001
|
||||
#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002
|
||||
#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003
|
||||
#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004
|
||||
#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005
|
||||
#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006
|
||||
#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007
|
||||
#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008
|
||||
#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009
|
||||
#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a
|
||||
#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b
|
||||
#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c
|
||||
#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d
|
||||
#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e
|
||||
#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f
|
||||
#define AVB_AECP_AEM_CMD_SET_NAME 0x0010
|
||||
#define AVB_AECP_AEM_CMD_GET_NAME 0x0011
|
||||
#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012
|
||||
#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013
|
||||
#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014
|
||||
#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015
|
||||
#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016
|
||||
#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017
|
||||
#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018
|
||||
#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019
|
||||
#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a
|
||||
#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b
|
||||
#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c
|
||||
#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d
|
||||
#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e
|
||||
#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f
|
||||
#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020
|
||||
#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021
|
||||
#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022
|
||||
#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023
|
||||
#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024
|
||||
#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025
|
||||
#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026
|
||||
#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027
|
||||
#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028
|
||||
#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029
|
||||
#define AVB_AECP_AEM_CMD_REBOOT 0x002a
|
||||
#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b
|
||||
#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c
|
||||
#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d
|
||||
#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e
|
||||
#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f
|
||||
#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030
|
||||
#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031
|
||||
#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032
|
||||
#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033
|
||||
#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034
|
||||
#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035
|
||||
#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036
|
||||
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037
|
||||
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038
|
||||
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039
|
||||
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a
|
||||
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b
|
||||
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c
|
||||
#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d
|
||||
#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e
|
||||
#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f
|
||||
#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040
|
||||
#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041
|
||||
#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042
|
||||
#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043
|
||||
#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044
|
||||
#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045
|
||||
#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046
|
||||
#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047
|
||||
#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048
|
||||
#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049
|
||||
#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a
|
||||
#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff
|
||||
|
||||
#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0)
|
||||
#include "aecp-aem-types.h"
|
||||
|
||||
struct avb_packet_aecp_aem_acquire {
|
||||
uint32_t flags;
|
||||
|
|
@ -114,6 +22,12 @@ struct avb_packet_aecp_aem_lock {
|
|||
uint16_t descriptor_id;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct avb_packet_aecp_aem_available {
|
||||
uint32_t flags;
|
||||
uint64_t acquired_controller_guid;
|
||||
uint64_t lock_controller_guid;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct avb_packet_aecp_aem_read_descriptor {
|
||||
uint16_t configuration;
|
||||
uint8_t reserved[2];
|
||||
|
|
@ -315,9 +229,11 @@ struct avb_packet_aecp_aem {
|
|||
uint8_t payload[0];
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v))
|
||||
#define AVB_PACKET_CONTROL_DATA_OFFSET (12U)
|
||||
|
||||
#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)
|
||||
#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v))
|
||||
|
||||
#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)
|
||||
|
||||
int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len);
|
||||
int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue