Compare commits

...

98 commits

Author SHA1 Message Date
Wim Taymans
3f79bcae5d 1.4.4 2025-05-29 09:17:47 +02:00
Jonas Holmberg
fc9aa51619 pod: declare offset as unused in spa_pod_builder_bytes_end()
Fixes compiler warning:

/usr/include/spa-0.2/spa/pod/builder.h:357:69: error: unused parameter 'offset'
[-Werror=unused-parameter]
2025-05-29 09:08:40 +02:00
Wim Taymans
62a719d71a adapter:handle -ENOENT when enumerating buffers
When the follower has no buffer suggestion, it can return -ENOENT, which
should not generate an error but simply use the converter buffer
suggestion instead.
2025-05-27 16:14:15 +02:00
Robert Mader
eda42ef2fc gst: src: Change DEFAULT_MIN_BUFFERS back to 1
The change from 1 to 8 was done without justification in the commit
message and possibly for debug purposes. Unfortunately it breaks
negotiation with the libcamera virtual pipeline, which defaults to
4 buffers.

Set the the value to 1 again as successful negotiation - even with an
unusually low number of buffers - is usually more desirable than an
error.

Fixes: 98b7dc7c0 ("gst: don't do set_caps from the pipewire callback")
(cherry picked from commit e81fb77322)
2025-05-26 19:28:32 +02:00
Wim Taymans
c507c4b0ff adapter: negotiate from target to follower
Since 3abda54d80 we prefer the format
of the filter. This reverses the selection of the default value when
negotiating buffers from the target to the follower.

If the follower does not select a reasonable value for the buffer size,
for example, this then results in wrongly sized buffers.

Fix this by reversing the order of allocation from target to follower
where we let the target (converter) select a default value, which is
more likely to be correct.

See #4713, #4619
2025-05-26 15:44:51 +02:00
Wim Taymans
06941f7315 alsa: remove UMP flag from control format
Don't set the UMP type flag on the format. Use the negotiated types flag
to decide what format to output. Add support for output to old style
midi.

Set the UMP type flag only on the new mixer and JACK when UMP is
enabled.

This ensures that only new (or explicitly requesting) apps get UMP and
old apps receive old midi.

This makes JACK running on 1.2 in flatpaks work with midi again.
2025-05-23 17:00:16 +02:00
Wim Taymans
76db05a0f8 Use "8 bit raw midi" for control ports again
There is no need to encode the potential format in the format.dsp of
control ports, this is just for legacy compatibility with JACK apps. The
actual format can be negotiated with the types field.

Fixes midi port visibility with apps compiled against 1.2, such as JACK
apps in flatpaks.
2025-05-23 17:00:05 +02:00
Wim Taymans
483b59a9d9 pod: add bytes start/append/end functions
Add functions to dynamically start and build a bytes pod.
2025-05-23 16:59:54 +02:00
Wim Taymans
331d5e0351 1.4.3 2025-05-22 10:59:37 +02:00
Wim Taymans
b24ceda8b2 filter-graph: lv2 features need a NULL terminator 2025-05-21 15:37:02 +02:00
Wim Taymans
6115a240d1 filter-chain: manage graph from source only
Only react to the capture stream state change and format changes. The
playback and capture streams change state somewhat concurrently and so
the graph state might not be consistent.

Because only the capture stream will trigger the playback stream and
start the processing, we can safely react to capture stream changes
only.
2025-05-21 15:34:01 +02:00
Wim Taymans
2a3f92e67f client-node: let all events go to the node
Just pass all events to the node and only error out when they return an
error value.

Make sure we pass the result values of the command around.
2025-05-21 15:28:08 +02:00
Wim Taymans
61168adb92 adapter: log command errors when no converter 2025-05-20 10:54:48 +02:00
Christian Glombek
603aae2dc8 daemon/pipewire.conf.avail: Add snippet enabling module-raop 2025-05-20 10:54:43 +02:00
Wim Taymans
a968027bdc adapter: handle -ENOTSUP for commands
When using custom commands, the converter might return -ENOTSUP and
we should ignore this.
2025-05-20 10:54:38 +02:00
Wim Taymans
9207fea992 libcemara: take care of index offset when enumerating controls
Add an index offset when enumerating controls. We insert 2 properties
before enumerating the controls so the index of the first control needs
to have an offset of 2.
2025-05-20 10:53:55 +02:00
Wim Taymans
a66377cf42 alsa: only use default rate and channels when valid
Check the user provided rate and channels and only use them to limit the
rate and channels when they are valid.
2025-05-19 12:12:36 +02:00
Wim Taymans
e7610de305 alsa: clamp audio.channels to MAX_CHANNELS
So that we don't end up trying to use too many channels later on.
2025-05-19 12:09:21 +02:00
Robert Mader
2a8d5c1f40 v4l2: Re-enable first buffer skip for jpeg types
The workaround is typically needed with usb-cameras using jpeg streams.
Re-enabling the skip shouldn't affect what the commit was trying to do,
i.e. fix encoded streams like h264 etc.

Fixes: bcf0c0cf8 (v4l2: only skip buffer for raw formats)
(cherry picked from commit e387772dc2)
2025-05-17 15:08:45 +02:00
Wim Taymans
6b9f340219 alsa-seq: remove leftover debug line 2025-05-13 11:20:55 +02:00
Wim Taymans
f5c9944e61 netjack2: reverse send/recv roles in driver/manager
The params contain the send/recv streams from the point of view of the
manager (and not the driver as was assumed before). This means we need
to swap send/recv in the driver, not the manager.

This makes things interoperate with JACK/netjack2.

See #4666
2025-05-13 10:57:04 +02:00
Wim Taymans
835d8b7801 netjack2: add driver.mode again
This configures the default number of audio and midi ports per stream
to 0 depending on the mode.

Improve docs a little.

See #4666
2025-05-13 10:56:12 +02:00
Wim Taymans
f7f2e3e52a netjack2: use strncpy to copy the header
It pads the remaining bytes in the header with 0 bytes so that the
memory doesn't contain uninitialized data.
2025-05-13 10:55:31 +02:00
Wim Taymans
e040430967 netjack2: improve shutdown
Destroy the sources from the io handler immediately when there is an
error so that we don't end up in endless error wakeups.

Schedule the free from the main loop and make sure only one can ever
run.
2025-05-13 10:55:25 +02:00
Wim Taymans
4e0137696f netjack2: fix trace_fp compilation 2025-05-13 10:54:58 +02:00
Wim Taymans
c3e08ad9c9 netjack2: handle connection errors in more cases 2025-05-13 10:54:51 +02:00
Wim Taymans
515de13b8a netjack2: set timeout to sane value again 2025-05-13 10:54:44 +02:00
Wim Taymans
0ea1bc3841 netjack2: implement driver and manager roles correctly
The manager is actually not supposed to decide much about the number of
audio and midi ports. It should just suggest a default when connecting
driver doesn't know.

Add a audio.ports parameters to manager and driver to suggest/ask for
the amount of audio ports. Let the audio.position/audio.channels be a
specification of the channel mask in case it matches the requested
channels, otherwise use AUX channels for the ports.

This means that we must derive the mode (sink/source/audio/midi) from
the ports that are negotiated in the manager and the driver, so delay
this until after negotiation.

Make sure all the possible modes work. For midi only streams, we can't
wait for the session manager to perform a PortConfig so do that
ourselves. Make sure we only use a source trigger when we have a sink.

Fixes #4666
2025-05-13 10:54:32 +02:00
Wim Taymans
b9bad88eed netjack2: make function to clear events
Make a separate function to clear events instead of passing NULL as the
midi buffer and segfaulting.
2025-05-13 10:54:25 +02:00
Wim Taymans
85479cf203 netjack2: copy the node.group to streams
Just in case we want them to be scheduled in the same group.
2025-05-13 10:54:19 +02:00
Wim Taymans
c2c255b5f9 netjack2: keep per stream io_position
And handle the trigger failure with a warning and fallback.
2025-05-13 10:54:06 +02:00
Wim Taymans
dc1f73ceaf netjack2: warn when the trigger fails 2025-05-13 10:53:50 +02:00
Wim Taymans
f3cb35dad3 netjack2: keep position io per stream
Keep a position info for the stream it was set and then use the position
info for the stream that is driving the graph. Otherwise we might use a
destroyed position info.
2025-05-13 10:53:41 +02:00
Wim Taymans
5a00232cdf netjack2: implement ifname in the driver
See #4666
2025-05-13 10:53:34 +02:00
Wim Taymans
3a231a807d netjack2: Improve docs
Remove unused option.
Clarify that channels and midi port are chosen by the server.

See #4666
2025-05-13 10:53:25 +02:00
Wim Taymans
3aaf91d9c6 netjack2: fix the large midi events offset
The midi events have their large data offsets relative to the start of
the buffer and the large data is at the end of the buffer. Because we
copied it down, right after the events, but we didn't adjust the
offsets, calculate a correction offset when unpacking the events.
2025-05-13 10:50:01 +02:00
Wim Taymans
d28d195745 netjack2: set correct max midi buffer size
It depends on the negotiated period size, not the graph quantum.
2025-05-13 10:49:48 +02:00
Wim Taymans
28109587fd netjack2: copy large midi events to the end of the buffer
There is no need to keep an extra free byte at the end and it will cause
us to lose a byte when we copy the large midi events down.
2025-05-13 10:49:37 +02:00
Wim Taymans
9b52c07871 jack: write midi events to end of buffer
There is no need to keep one extra byte at the end of the buffer,
we can write the event up to the last byte.
2025-05-13 10:49:18 +02:00
Wim Taymans
3d33acce1d netjack: handle overflow in midi buffer append 2025-05-13 10:48:55 +02:00
Wim Taymans
1cbe4e1782 ump: handle sysex from UMP to MIDI1 better
SysEx in UMP can span multiple packets. In MIDI1 we can't split them up
into multiple events so we need to collect the complete sysex and then
write out the event.

Fixes SysEx writes to ALSA seq by running the event encoder until a
valid packet is completed.

Also fixes split MIDI1 packets in the JACK API when going through the
tunnel or via netjack.
2025-05-13 10:48:39 +02:00
Wim Taymans
750b88c3ca control: only convert midi/UMP, pass other control types
We should only try to convert MIDI/UMP when the port doesn't support it
but let all the other control types pass through.

Fixes #4692
2025-05-09 12:01:24 +02:00
Wim Taymans
37dbf93cc4 filter-graph: remove useless check 2025-05-09 11:59:31 +02:00
Guido Günther
2d4af3ced5 spa: Add default: statements
This allows to use the library in projects that use `-Wswitch-default`
without any

 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wswitch-default"
 #pragma GCC diagnostic pop

This is useful as as the header is being pulled in via
pipewire/wireplumber headers into projects that might have this warning
enabled and would otherwise fail to build with -Werror.

Signed-off-by: Guido Günther <agx@sigxcpu.org>
2025-05-09 11:58:55 +02:00
Wim Taymans
5d741a2b3c filter-chain: add props only once
Only add the properties offset in the builder after we actually got a
property or else we end up with the same property twice.
2025-05-09 11:54:34 +02:00
Wim Taymans
7d27668fb2 ebur128: fix port name
Fixes #4667
2025-05-09 11:53:37 +02:00
Wim Taymans
0135baa60f alsa-seq: add the source only on success
Otherwise if we have an error with the timerfd, we have the source fd
added and we close the device.
2025-05-09 11:53:07 +02:00
Wim Taymans
49c4c44dce videoconvert: consume input buffer in all cases
Return the input buffer when we get some error so that the other side
can send new data.
2025-05-09 11:52:04 +02:00
Wim Taymans
957bde02cd v4l2: only skip buffer for raw formats
We don't want to skip the first buffer for encoded formats because it
can contain the encoding parameters, in the h264 case, for example.
2025-05-09 11:51:45 +02:00
Wim Taymans
f857a50734 ump: fix program change 2.0 to 1.0 conversion
The program change byte should not be shifted an extra bit, unlike all
the other messages.

Fixes #4664
2025-05-09 11:51:20 +02:00
Wim Taymans
8c1e1ea17f midifile: unpack the UMP SysRT bytes correctly
They are packed in a native endian uint32_t so read it like that and
then use shifts to get the right bytes.
2025-05-09 11:50:43 +02:00
Simon Ruderich
63bf21d7b8 midifile: decode UMP SysRT messages 2025-05-09 11:50:36 +02:00
Wim Taymans
3abda54d80 pod: use default value of filter
When using a filter, it makes more sense to use the default value
of the filter as a first attempt.

One case is in adapter when we try to find a passthrough format first. The
audioconverter suggests a default rate of the graph rate but the follower
filters this out for another unrelated default value and passthrough is not
possible (altough it would be because the default value of the filter is
in the supported follower range).

Fixes #4619
2025-05-09 11:49:08 +02:00
Wim Taymans
d20a1523b6 1.4.2 2025-04-14 11:46:02 +02:00
Wim Taymans
ac42f55916 ebur128: work around libebur128 bug
Versions 1.2.5 and 1.2.6 don't scale the window in ms correctly, leading
to excessive memory allocation, so work around this here.
See https://github.com/jiixyj/libebur128/pull/132

Fixes #4646
2025-04-14 11:19:44 +02:00
Kenny Levinsen
a0be431c7f audioconvert: Increase param length limit to 4096
Parameter values read into a 512 byte long buffer, which is insufficient
for medium to long filter-graph parameters.

Increase the buffer to 4096 bytes to give some wiggle-room.
2025-04-14 09:41:47 +02:00
fossdd
35591922ce doc: fix typo in pw-link(1) 2025-04-14 09:40:10 +02:00
Pauli Virtanen
f120b595f0 pw-loopback: add missing --channel-map long option 2025-04-14 09:40:02 +02:00
Wim Taymans
07f49a7559 raop: fix byte array initialization
Initialize the byte array with bytes instead of a string because the 0
byte at the end of the string does not fit in the array and causes a
compiler warning.
2025-04-14 09:39:18 +02:00
Arun Raghavan
cf5db17aa6 gst: sink: Only add VideoCrop meta for video 2025-04-03 06:15:11 -04:00
Wim Taymans
f0a432118a gst: require a buffer size of at least 1
Setting the default size to 0 and outside of the min/max range now means
that there is no suggestion for the size and it should use the
suggestion of the peer.
2025-04-03 06:14:26 -04:00
Sanchayan Maity
f571253ff3 gst: pipewiresrc: Fixate caps if intersect did not return fixated caps
We might end up in a situation where depending on the pipeline,
intersect might not give us fixated caps.

Possible example of such a pipeline can be below.

gst-launch-1.0 -e pipewiresrc target-object=<path> ! audioconvert !
audio/x-raw,format=S16LE,rate=48000,channels=2 ! lamemp3enc !
filesink location=test.mp3

This results in non-fixated caps like below when intersecting caps from
format param and possible_caps which depends on what we have downstream
in the pipeline.

audio/x-raw, layout=(string)interleaved, format=(string)S16LE, rate=(int)48000, channels=(int)2, channel-mask=(bitmask)0x0000000000000003;
audio/x-raw, layout=(string)interleaved, format=(string)S16LE, rate=(int)48000, channels=(int)2

To fix this, fixate the caps explicitly.
2025-04-03 06:14:02 -04:00
Philippe Normand
99b94015a7 gst: src: Fix buffer pool handling in case of caps renegotiation
In case negotiation is first attempted with unfixed caps, bufferpool support was
unconditionally disabled. Then at a second caps negotiation attempt it wasn't
restored according to the property value.
2025-04-03 06:13:24 -04:00
Sanchayan Maity
48a959bf2e gst: pipewireformat: Do not use RANGE if values are equal
This fixes assertion from the underlying gst_value_collect_int_range
when using gst_caps_set_simple with range types when the values are
equal.
2025-04-03 06:13:13 -04:00
Wim Taymans
1388f16c47 adapter: handle builder overflow 2025-04-02 15:33:54 +02:00
Wim Taymans
ce6d2fce9a audioadapter: init the builder for each param
Or else we keep on adding items until we overflow.
2025-04-02 15:33:54 +02:00
Wim Taymans
98e7ae39c6 impl-link: improve debug log
Log the format after we patch it up and log some context lines.

Move some info log to debug.
2025-04-02 15:33:54 +02:00
Wim Taymans
a9eba98e8e pod: handle builder overflows
When the builder is overflowed, we might get a NULL pod. This is a valid
situation that we need to handle because it can be used to get the
required builder buffer size.
2025-04-02 15:33:54 +02:00
Wim Taymans
2d26fbcddf v4l2: handle nearest set_format
The set_format function can return 1 when the format was adjusted to the
nearest supported format so return this from the port_set_param
function.

This instructs the adapter to recheck the configured format so that it
can store the adjuted format on the converter.
2025-04-02 15:33:54 +02:00
Wim Taymans
8d7175a1e7 adapter: add Header metadata by default
Firefox needs this but we should eventually check the Meta Params to
decide on this.
2025-04-02 15:33:54 +02:00
Philippe Normand
81408597f4 gst: deviceprovider: Fix a leak and a heap-use-after-free
The device passed to gst_device_provider_device_add() is transfer:floating, so
we need increase its ref, otherwise the pointer we keep internally will be a
dangling ref.

Also gst_device_provider_device_remove() doesn't actually release the device, so
we have to do it ourselves.

Fixes #4616
2025-04-02 15:33:54 +02:00
Wim Taymans
58c412c9dc pw-cat: improve sndfile file format debug info
Print the endianness, container name and the sample format nicely
instead of dumping the hex values.
2025-04-02 15:33:54 +02:00
Wim Taymans
081cb6848e pw-cat: prefer AU format when using stdin/stdout
WAV is actually not usable for streaming output by sndfile.

See #4629
2025-04-02 15:33:54 +02:00
Pauli Virtanen
4176caca30 alsa: seq: double-check midi client version
Check midi client version after setting it, to see if it was really
successfully set.  Old kernels without UMP don't know about the midi
version fields, so snd_seq_set_client_midi_version() appears to fail
silently there.
2025-04-02 15:33:54 +02:00
Robert Mader
b87764bd07 systemd: Depend on dbus.service
Solution suggested by Xi Ruoyao.

The dbus user service is required for various features - the summary says:
'dbus (Bluetooth, rt, portal, pw-reserve)'

On session logout the dbus service gets shut down while the Pipewire one
relies on a timeout. If a user logs in again before PW timed out, the
later stays alive but doesn't handle re-connecting to the dbus service
of the new session, breaking the camera portal and potentially other
features.

Thus hard-depend on the dbus service (if enabled at build time) and thus
shut down together with it.

(cherry picked from commit 2625983a23)
2025-03-20 14:05:28 +01:00
Wim Taymans
2eb8cf5dc7 1.4.1 2025-03-14 11:07:06 +01:00
Wim Taymans
fb43eb8ee2 audioconvert: fix the filter-graph samplerate
The filter graph runs before or after the resampler depending on the
direction of the conversion.
2025-03-14 10:16:15 +01:00
Pauli Virtanen
d58d2a2375 spa: acp: indicate ALSA UCM profile errors in UIs
Add "[ALSA UCM error]" to the end of card description, to indicate
something is wrong.
2025-03-14 10:16:08 +01:00
Pauli Virtanen
ba3a36b3d1 spa: acp: allow also too few channels in SplitPCM configs
GoXLR Mini has different numbers of channels actually available (21, 23,
or 25) depending on its firmware/etc, but its UCM profile specifies
always 23. The count can then be bigger or smaller than what is actually
available.

Fail a bit more gracefully in the case of too few channels: create all
the split devices specified by the profile. The channels that aren't
actually available in HW just won't get routed anywhere.

ALSA upstream IIUC is saying that the channel counts should be fixed, so
spew warnings that say the UCM profiles are wrong if they look wrong.
2025-03-14 10:16:05 +01:00
sunyuechi
e16c184228 Add x86 CPU check in meson.build for clang build fix
have_avx = cc.has_argument(avx_args)

gcc generates an error when AVX is unsupported, whereas clang only
produces a warning.

Thus, using Clang on the RISC-V platform incorrectly enables AVX file
compilation, leading to build failures.
2025-03-14 10:15:56 +01:00
Wim Taymans
9696e2c62b loop: remove return from function returning void
Fixes #4603
2025-03-14 10:15:42 +01:00
Wim Taymans
b4d88143da alsa-seq: fix UMP output
We need to set the UMP flag, which is done with
snd_seq_ev_set_ump_data() to make it output the midi data.
2025-03-11 13:30:37 +01:00
Wim Taymans
15e200846a alsa-seq: avoid uninitialized warning 2025-03-11 12:32:28 +01:00
Wim Taymans
f4417f4e2c audioconvert: remove some unused fields 2025-03-11 12:27:36 +01:00
Wim Taymans
edffc87ebf audioconvert: configure resample channels correctly
Depending on the direction of the conversion, we run the resampler
before or after the channelmix. This means we need to use the channel
count before or after the channelmixer instead of always using the
channels after channelmixing.

Fixes #4595
2025-03-11 12:27:33 +01:00
Pauli Virtanen
f7b1ba2a40 spa: avoid casting between snd_seq_event_t & snd_seq_ump_event_t
Although the two structs have same initial sequence, it's not really
correct to cast between their pointers. Alsa-lib also does this only
internally, but not in API.
2025-03-11 12:27:29 +01:00
Pauli Virtanen
4f077e1f27 spa: fix ALSA UMP support detection in meson 2025-03-11 12:27:25 +01:00
Pauli Virtanen
2167945eff spa: alsa: support also MIDI-1.0 IO for ALSA seq
Support also non-UMP IO with ALSA seq, in case either alsa-lib or the
kernel does not have UMP enabled.

Add configuration option "api.alsa.seq.ump" for optionally turning UMP
I/O off, for easier debugging.
2025-03-11 12:27:21 +01:00
Nikolay Borodin
2397a984f7 spa: alsa: allow building without UMP in libalsa
Allow building with older libalsa releases that don't have UMP.
2025-03-11 12:27:18 +01:00
msizanoen
17eaf83fe8 systemd: Disable pipewire user services for root
The `access(2)` based multi-user mediation mechanism doesn't quite work
for the root user, which may cause it to conflict with a running
foreground user session. Prevent this by not running the user service at
all for the root user, which nobody should be doing anyway.
2025-03-11 12:27:14 +01:00
Jan Grulich
8d55213288 stream: don't emit process when disconnecting
Commit b160a72018 introduced this change
before, but it was omitted in e1e0a886d5.

This makes again sure we don't call process callback while disconnecting
stream.

Fixes #3314
2025-03-11 12:27:05 +01:00
Pauli Virtanen
06438cbc9a spa: acp: make spa-acp-tool debug output easier to read
Include log level, file/line numbers, and indent messages for debug
levels.
2025-03-10 09:24:29 +01:00
Pauli Virtanen
ae16463c3c spa: acp: be more noisy when UCM profiles fail to be supported
Generally ALSA UCM profiles should all work as they're supposed to be
device-specific, so be more noisy when the profile fails to be supported
due to the PCM device failing to open.

Some logging on the probe outcome in failure case also makes
spa-acp-tool etc. log output easier to read.
2025-03-10 09:24:26 +01:00
Pauli Virtanen
78981a8d9b spa: acp: in SplitPCM probe channel count and allow excess
In SplitPCM mode, Focusrite Scarlett Gen 4 (USB 1235:8218) UCM profile
specifies "CaptureChannels 2" for the Mic1/2 inputs, but
snd_pcm_hw_params_set_channels(2) fails for the HW device.

Fix by not requiring the channel count to be exact for SplitPCM, but
also allow larger numbers of channels than what UCM profile specifies.
2025-03-10 09:24:23 +01:00
Jan Palus
901401e6f1 module-roc: require roc >= 0.4.0
3270bd4 introduced changes reyling on features from roc 0.4.0
(upstream commit: d18d342)
2025-03-10 09:24:20 +01:00
Frédéric Danis
5c1be35018 bluez5: backend-native: Fix 3way active call hangup
HFP/HF/TWC/BV-03-C test, which setup an active and a held calls,
expects to receive AT+CHLD=1 (release and swap calls) instead of
AT+CHUP on active call hang up request.

As this changes the active call to disconnected and held call to
being active, the call states should be managed in hfp_hf_hangup
instead of waiting for +CIEV (callheld=0) event which will drop
the previously held call before AT+CLCC reply can inform this call
is now active.
2025-03-10 09:24:13 +01:00
Frédéric Danis
79d232c1f2 bluez5: backend-native: Fix incoming call crash
HFP/HF/TWC/BV-01-C test creates an incoming call as soon as the SLC is
completed, i.e. a +CIEV: <callsetup>,1 event just after AT+CHLD=? reply
has been received. This try to parse the rfcomm->telephony_ag->call_list
which has not yet been created.

This commit move the telephony_ag creation to the SLC completed event.
2025-03-10 09:24:10 +01:00
Frédéric Danis
277b80c903 bluez5: backend-native: Fix ECNR support in HFP HF SDP record
Sending AT+ECNR is supported by the native backend
2025-03-10 09:24:07 +01:00
61 changed files with 1575 additions and 574 deletions

136
NEWS
View file

@ -1,3 +1,136 @@
# PipeWire 1.4.4 (2025-05-29)
This is a quick bugfix release that is API and ABI compatible with
previous 1.x releases.
## Highlights
- Provide better compatibility with 1.2 for MIDI.
- Fix mpv buffer negotiation regression.
- Improve GStreamer compatibility with libcamera.
## SPA
- Provide conversions to old style midi in the ALSA sequencer.
- Negotiate only to UMP when using a newer library.
- Fix negotiation direction for buffers, prefer the converter
suggestion instead of the application until we can be sure
applications make good suggestions.
## GStreamer
- Allow a minimum of 1 buffers again instead of 8. libcamera will
allocate only 4 buffers so we need to support this.
Older versions:
# PipeWire 1.4.3 (2025-05-22)
This is a bugfix release that is API and ABI compatible with
previous 1.x releases.
## Highlights
- Many netjack2 improvements. The driver/manager roles were fixed,
MIDI is written correctly and errors are handled better.
- Improvements to UMP sysex handling.
- More small bug fixes and improvements.
## PipeWire
- Let all commands go to the node. This makes it possible to send
custom commands.
## Modules
- Many netjack2 improvements. The driver/manager roles were fixed,
MIDI is written correctly and errors are handled better.
- Improve the filter-graph state management in filter-chain.
## SPA
- Use default value of filter. (#4619)
- Fix UMP program change conversion to MIDI 1.0. (#4664)
- Skip only the first buffer for raw formats in v4l2 to avoid
dropping important headers when dealing with encoded formats.
- Fix ebur128 port name. (#4667)
- Only convert UMP/MIDI, pass other controls. Fixes OSC and other
control types in the mixer. (#4692)
- Improve UMP sysex handling in alsa seq.
- Improve ALSA audio.channels support. Only use this when the value
is within the valid range.
## Tools
- debug UMP SysRT messages correctly in pw-mididump.
## JACK
- Handle sysex in UMP better by appending the converted midi1
sysex.
# PipeWire 1.4.2 (2025-04-14)
This is a bugfix release that is API and ABI compatible with
previous 1.x releases.
## Highlights
- Do extra checks for MIDI to avoid 100% CPU usage on older kernels.
- Fix some potential crashes in POD builder.
- pw-cat streaming improvements on stdout/stdin.
- Small fixes and improvements.
## PipeWire
- Make the service files depend on DBus to avoid startup races.
## SPA
- Do extra checks for MIDI to avoid 100% CPU usage on older kernels.
- Use Header metadata by default in videoadapter.
- Handle set_format result from v4l2 better.
- Handle crash when POD builder overflows in the filter.
- Work around a libebur128 bug. (#4646)
## Tools
- pw-cat prefers AU format when streaming on stdout/stdin. (#4629)
- Improve pw-cat verbose sndfile format debug.
- Add the missing --channel-map long option to pw-loopback.
## GStreamer
- Fix a leak in the deviceprovider. (#4616)
- Fix negotiation and make renegotiation better.
# PipeWire 1.4.1 (2025-03-14)
This is a quick bugfix release that is API and ABI compatible with
previous 1.x releases.
## Highlights
- Handle SplitPCM wrong channels specifications. This fixes some
problems with disappearing devices.
- Add backwards compatibility support for when the kernel does not
support UMP. Also fix UMP output. This restores MIDI support for
older kernels/ALSA.
- Fix a crash in audioconvert because the resampler was not using the
right number of channels.
- Some compilation fixes and small improvements.
## PipeWire
- Don't emit events when disconnecting a stream. (#3314)
- Fix some compilation problems. (#4603)
## Modules
- Bump the ROC requirement to version 0.4.0
## SPA
- Handle SplitPCM too few or too many channels. Add an error string
to the device names when the UCM config has an error.
- Add backwards compatibility support for when the kernel does not
support UMP.
- Configure the channels in the resampler correctly in all
cases. (#4595)
- Fix UMP output.
- Use the right samplerate of the filter-graph in audioconvert in
all cases.
## Bluetooth
- Fix a crash with an incomming call.
# PipeWire 1.4.0 (2025-03-06)
This is the 1.4 release that is API and ABI compatible with previous
@ -99,9 +232,6 @@ the 1.2 release last year, including:
## JACK
- Add an option to disable the MIDI2 port flags. (#4584)
Older versions:
# PipeWire 1.3.83 (2025-02-20)
This is the third and hopefully last 1.4 release candidate that

View file

@ -37,7 +37,7 @@ output ports and their links.
List output ports
\par -i | \--input
List output ports
List input ports
\par -l | \--links
List links

View file

@ -1,5 +1,5 @@
project('pipewire', ['c' ],
version : '1.4.0',
version : '1.4.4',
license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ],
meson_version : '>= 0.61.1',
default_options : [ 'warning_level=3',
@ -127,21 +127,30 @@ if have_cpp
add_project_arguments(cxx.get_supported_arguments(cxx_flags), language: 'cpp')
endif
sse_args = '-msse'
sse2_args = '-msse2'
ssse3_args = '-mssse3'
sse41_args = '-msse4.1'
fma_args = '-mfma'
avx_args = '-mavx'
avx2_args = '-mavx2'
have_sse = false
have_sse2 = false
have_ssse3 = false
have_sse41 = false
have_fma = false
have_avx = false
have_avx2 = false
if host_machine.cpu_family() in ['x86', 'x86_64']
sse_args = '-msse'
sse2_args = '-msse2'
ssse3_args = '-mssse3'
sse41_args = '-msse4.1'
fma_args = '-mfma'
avx_args = '-mavx'
avx2_args = '-mavx2'
have_sse = cc.has_argument(sse_args)
have_sse2 = cc.has_argument(sse2_args)
have_ssse3 = cc.has_argument(ssse3_args)
have_sse41 = cc.has_argument(sse41_args)
have_fma = cc.has_argument(fma_args)
have_avx = cc.has_argument(avx_args)
have_avx2 = cc.has_argument(avx2_args)
have_sse = cc.has_argument(sse_args)
have_sse2 = cc.has_argument(sse2_args)
have_ssse3 = cc.has_argument(ssse3_args)
have_sse41 = cc.has_argument(sse41_args)
have_fma = cc.has_argument(fma_args)
have_avx = cc.has_argument(avx_args)
have_avx2 = cc.has_argument(avx2_args)
endif
have_neon = false
if host_machine.cpu_family() == 'aarch64'
@ -481,7 +490,7 @@ endif
summary({'intl support': libintl_dep.found()}, bool_yn: true)
need_alsa = get_option('pipewire-alsa').enabled() or 'media-session' in get_option('session-managers')
alsa_dep = dependency('alsa', version : '>=1.2.10', required: need_alsa)
alsa_dep = dependency('alsa', version : '>=1.2.6', required: need_alsa)
summary({'pipewire-alsa': alsa_dep.found()}, bool_yn: true)
if host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd'

View file

@ -1538,7 +1538,7 @@ static inline jack_midi_data_t* midi_event_reserve(void *port_buffer,
res = ev->inline_data;
} else {
mb->write_pos += data_size;
ev->byte_offset = mb->buffer_size - 1 - mb->write_pos;
ev->byte_offset = mb->buffer_size - mb->write_pos;
res = SPA_PTROFF(mb, ev->byte_offset, uint8_t);
}
mb->event_count += 1;
@ -1546,14 +1546,37 @@ static inline jack_midi_data_t* midi_event_reserve(void *port_buffer,
return res;
}
static inline int midi_event_append(void *port_buffer, const jack_midi_data_t *data, size_t data_size)
{
struct midi_buffer *mb = port_buffer;
struct midi_event *events = SPA_PTROFF(mb, sizeof(*mb), struct midi_event);
struct midi_event *ev;
size_t old_size;
uint8_t *old, *buf;
ev = &events[--mb->event_count];
mb->write_pos -= ev->size;
old_size = ev->size;
if (old_size <= MIDI_INLINE_MAX)
old = ev->inline_data;
else
old = SPA_PTROFF(mb, ev->byte_offset, uint8_t);
buf = midi_event_reserve(port_buffer, ev->time, old_size + data_size);
if (SPA_UNLIKELY(buf == NULL))
return -ENOBUFS;
memmove(buf, old, old_size);
memcpy(buf+old_size, data, data_size);
return 0;
}
static inline int midi_event_write(void *port_buffer,
jack_nframes_t time,
const jack_midi_data_t *data,
size_t data_size, bool fix)
{
jack_midi_data_t *retbuf = midi_event_reserve (port_buffer, time, data_size);
if (SPA_UNLIKELY(retbuf == NULL))
return -ENOBUFS;
if (SPA_UNLIKELY(retbuf == NULL))
return -ENOBUFS;
memcpy (retbuf, data, data_size);
if (fix)
fix_midi_event(retbuf, data_size);
@ -1566,6 +1589,7 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void
uint64_t state = 0;
uint32_t i;
int res = 0;
bool in_sysex = false;
for (i = 0; i < n_seq; i++)
c[i] = spa_pod_control_first(&seq[i]->body);
@ -1620,17 +1644,30 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void
void *data = SPA_POD_BODY(&next->value);
size_t size = SPA_POD_BODY_SIZE(&next->value);
uint8_t ev[32];
bool was_sysex = in_sysex;
if (type == TYPE_ID_MIDI) {
int ev_size = spa_ump_to_midi(data, size, ev, sizeof(ev));
if (ev_size <= 0)
break;
size = ev_size;
data = ev;
if (!in_sysex && ev[0] == 0xf0)
in_sysex = true;
if (in_sysex && ev[ev_size-1] == 0xf7)
in_sysex = false;
} else if (type != TYPE_ID_UMP)
break;
if ((res = midi_event_write(midi, next->offset, data, size, fix)) < 0)
if (was_sysex)
res = midi_event_append(midi, data, size);
else
res = midi_event_write(midi, next->offset, data, size, fix);
if (res < 0)
pw_log_warn("midi %p: can't write event: %s", midi,
spa_strerror(res));
}
@ -2512,11 +2549,28 @@ static int param_enum_format(struct client *c, struct port *p,
case TYPE_ID_UMP:
case TYPE_ID_OSC:
case TYPE_ID_MIDI:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
{
struct spa_pod_frame f;
int32_t types = 0;
spa_pod_builder_push_object(b, &f,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
0);
if (p->object->port.type_id == TYPE_ID_UMP)
types |= 1u<<SPA_CONTROL_UMP;
if (p->object->port.type_id == TYPE_ID_OSC)
types |= 1u<<SPA_CONTROL_OSC;
if (types != 0)
spa_pod_builder_add(b,
SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(types),
0);
*param = spa_pod_builder_pop(b, &f);
break;
}
case TYPE_ID_VIDEO:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
@ -3429,24 +3483,6 @@ static const char* type_to_string(jack_port_type_id_t type_id)
}
}
static const char* type_to_format_dsp(jack_port_type_id_t type_id)
{
switch(type_id) {
case TYPE_ID_AUDIO:
return JACK_DEFAULT_AUDIO_TYPE;
case TYPE_ID_VIDEO:
return JACK_DEFAULT_VIDEO_TYPE;
case TYPE_ID_OSC:
return JACK_DEFAULT_OSC_TYPE;
case TYPE_ID_MIDI:
return JACK_DEFAULT_MIDI_TYPE;
case TYPE_ID_UMP:
return JACK_DEFAULT_UMP_TYPE;
default:
return NULL;
}
}
static bool type_is_dsp(jack_port_type_id_t type_id)
{
switch(type_id) {
@ -5507,7 +5543,7 @@ jack_port_t * jack_port_register (jack_client_t *client,
spa_list_init(&p->mix);
pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_format_dsp(type_id));
pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_string(type_id));
pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name);
if (flags > 0x1f) {
pw_properties_setf(p->props, PW_KEY_PORT_EXTRA,
@ -5754,8 +5790,8 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames)
convert_to_event(seq, n_seq, mb, p->client->fix_midi_events, p->object->port.type_id);
memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count
* sizeof(struct midi_event)));
if (mb->write_pos) {
size_t offs = mb->buffer_size - 1 - mb->write_pos;
if (mb->write_pos > 0) {
size_t offs = mb->buffer_size - mb->write_pos;
memcpy(SPA_PTROFF(ptr, offs, void), SPA_PTROFF(mb, offs, void), mb->write_pos);
}
return ptr;

View file

@ -96,9 +96,17 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(uint32_t *ump, size_t ump_size,
if (ump_size < 8)
return 0;
midi[size++] = (ump[0] >> 16) | 0x80;
if (midi[0] < 0xc0 || midi[0] > 0xdf)
switch (midi[0] & 0xf0) {
case 0xc0:
midi[size++] = (ump[1] >> 24);
break;
default:
midi[size++] = (ump[0] >> 8) & 0x7f;
midi[size++] = (ump[1] >> 25);
SPA_FALLTHROUGH;
case 0xd0:
midi[size++] = (ump[1] >> 25);
break;
}
break;
case 0x0: /* Utility Messages */

View file

@ -326,6 +326,31 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len)
return SPA_POD_BODY(spa_pod_builder_deref(builder, offset));
}
SPA_API_POD_BUILDER uint32_t
spa_pod_builder_bytes_start(struct spa_pod_builder *builder)
{
uint32_t offset = builder->state.offset;
const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(0);
spa_pod_builder_raw(builder, &p, sizeof(p));
return offset;
}
SPA_API_POD_BUILDER int
spa_pod_builder_bytes_append(struct spa_pod_builder *builder, uint32_t offset,
const void *data, uint32_t size)
{
int res = spa_pod_builder_raw(builder, data, size);
struct spa_pod *pod = spa_pod_builder_deref(builder, offset);
if (pod)
pod->size += size;
return res;
}
SPA_API_POD_BUILDER int
spa_pod_builder_bytes_end(struct spa_pod_builder *builder, uint32_t offset SPA_UNUSED)
{
return spa_pod_builder_pad(builder, builder->state.offset);
}
#define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } })
SPA_API_POD_BUILDER int

View file

@ -139,7 +139,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b,
const struct spa_pod_prop *p2)
{
const struct spa_pod *v1, *v2;
struct spa_pod_choice *nc;
struct spa_pod_choice *nc, dummy;
uint32_t j, k, nalt1, nalt2;
void *alt1, *alt2, *a1, *a2;
uint32_t type, size, p1c, p2c;
@ -175,9 +175,13 @@ spa_pod_filter_prop(struct spa_pod_builder *b,
spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags);
spa_pod_builder_push_choice(b, &f, 0, 0);
nc = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f);
/* write to dummy value when builder overflows. We don't want to error
* because overflowing is a way to determine the required buffer size. */
if (nc == NULL)
nc = &dummy;
/* default value */
spa_pod_builder_primitive(b, v1);
spa_pod_builder_primitive(b, v2);
if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_None) ||
(p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) ||
@ -185,10 +189,10 @@ spa_pod_filter_prop(struct spa_pod_builder *b,
(p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Enum)) {
int n_copied = 0;
/* copy all equal values but don't copy the default value again */
for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1, size, void)) {
for (k = 0, a2 = alt2; k < nalt2; k++, a2 = SPA_PTROFF(a2,size,void)) {
for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2, size, void)) {
for (k = 0, a1 = alt1; k < nalt1; k++, a1 = SPA_PTROFF(a1,size,void)) {
if (spa_pod_compare_value(type, a1, a2, size) == 0) {
if (p1c == SPA_CHOICE_Enum || j > 0)
if (p2c == SPA_CHOICE_Enum || j > 0)
spa_pod_builder_raw(b, a1, size);
n_copied++;
}

View file

@ -268,6 +268,8 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value)
if (--utf8_remain == 0)
iter->state = __STRING | flag;
continue;
default:
break;
}
_SPA_ERROR(CHARACTERS_NOT_ALLOWED);
case __ESC:
@ -276,12 +278,17 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value)
case 'n': case 'r': case 't': case 'u':
iter->state = __STRING | flag;
continue;
default:
break;
}
_SPA_ERROR(INVALID_ESCAPE);
case __COMMENT:
switch (cur) {
case '\n': case '\r':
iter->state = __STRUCT | flag;
break;
default:
break;
}
break;
default:
@ -299,6 +306,8 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value)
case __COMMENT:
/* trailing comment */
return 0;
default:
break;
}
if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) {

View file

@ -43,9 +43,13 @@ if get_option('spa-plugins').allowed()
endif
# plugin-specific dependencies
alsa_dep = dependency('alsa', version : '>=1.2.10', required: get_option('alsa'))
alsa_dep = dependency('alsa', version : '>=1.2.6', required: get_option('alsa'))
summary({'ALSA': alsa_dep.found()}, bool_yn: true, section: 'Backend')
if alsa_dep.version().version_compare('>=1.2.10')
cdata.set('HAVE_ALSA_UMP', true)
endif
bluez_dep = dependency('bluez', version : '>= 4.101', required: get_option('bluez5'))
bluez_gio_dep = dependency('gio-2.0', required : get_option('bluez5'))
bluez_gio_unix_dep = dependency('gio-unix-2.0', required : get_option('bluez5'))

View file

@ -132,6 +132,18 @@ static ACP_PRINTF_FUNC(6,0) void log_func(void *data,
int level, const char *file, int line, const char *func,
const char *fmt, va_list arg)
{
static const char * const levels[] = { "E", "W", "N", "I", "D", "T" };
const char *level_str = levels[SPA_CLAMP(level, 0, (int)SPA_N_ELEMENTS(levels) - 1)];
if (file) {
const char *p = strrchr(file, '/');
if (p)
file = p + 1;
}
fprintf(stderr, "%s %16s:%-5d ", level_str, file ? file : "", line);
while (level-- > 1)
fprintf(stderr, " ");
vfprintf(stderr, fmt, arg);
fprintf(stderr, "\n");
}

View file

@ -498,6 +498,7 @@ static void add_profiles(pa_card *impl)
int n_profiles, n_ports, n_devices;
uint32_t idx;
const char *arr;
bool broken_ucm = false;
n_devices = 0;
pa_dynarray_init(&impl->out.devices, device_free);
@ -541,6 +542,9 @@ static void add_profiles(pa_card *impl)
dev->ports, NULL);
pa_dynarray_append(&ap->out.devices, dev);
if (m->split && m->split->broken)
broken_ucm = true;
}
}
@ -564,6 +568,9 @@ static void add_profiles(pa_card *impl)
dev->ports, NULL);
pa_dynarray_append(&ap->out.devices, dev);
if (m->split && m->split->broken)
broken_ucm = true;
}
}
cp->n_devices = pa_dynarray_size(&ap->out.devices);
@ -571,6 +578,22 @@ static void add_profiles(pa_card *impl)
pa_hashmap_put(impl->profiles, ap->name, cp);
}
/* Add a conspicuous notice if there are errors in the UCM profile */
if (broken_ucm) {
const char *desc;
char *new_desc = NULL;
desc = pa_proplist_gets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION);
if (!desc)
desc = "";
new_desc = spa_aprintf(_("%s [ALSA UCM error]"), desc);
pa_log_notice("Errors in ALSA UCM profile for card %s", desc);
if (new_desc)
pa_proplist_sets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION, new_desc);
free(new_desc);
}
pa_dynarray_init(&impl->out.ports, NULL);
n_ports = 0;
PA_HASHMAP_FOREACH(dp, impl->ports, state) {

View file

@ -353,6 +353,15 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd
const char *device_name;
int i;
uint32_t hw_channels;
const char *pcm_name;
const char *rule_name;
if (spa_streq(prefix, "Playback"))
pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK);
else
pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (!pcm_name)
pcm_name = "";
device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
if (!device_name)
@ -372,16 +381,23 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd
if (pa_atou(value, &idx) < 0)
break;
if (idx >= hw_channels)
goto fail;
if (idx >= hw_channels) {
pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannel%d=%d >= %sChannels=%d",
pcm_name, device_name, prefix, i, idx, prefix, hw_channels);
split->broken = true;
}
value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name);
if (!value)
if (!value) {
rule_name = "ChannelPos";
goto fail;
}
map = snd_pcm_chmap_parse_string(value);
if (!map)
if (!map) {
rule_name = "ChannelPos value";
goto fail;
}
if (map->channels == 1) {
pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)",
@ -391,6 +407,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd
free(map);
} else {
free(map);
rule_name = "channel map parsing";
goto fail;
}
}
@ -405,7 +422,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd
return split;
fail:
pa_log_warn("Invalid SplitPCM ALSA UCM rule for device %s", device_name);
pa_log_warn("Invalid SplitPCM ALSA UCM %s for device %s (%s)", rule_name, pcm_name, device_name);
pa_xfree(split);
return NULL;
}
@ -2383,7 +2400,7 @@ static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm)
dev->eld_device = pcm_device;
}
static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) {
static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode, bool max_channels) {
snd_pcm_t* pcm;
pa_sample_spec try_ss = ucm->default_sample_spec;
pa_channel_map try_map;
@ -2391,6 +2408,11 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m,
bool exact_channels = m->channel_map.channels > 0;
if (!m->split) {
if (max_channels) {
errno = EINVAL;
return NULL;
}
if (exact_channels) {
try_map = m->channel_map;
try_ss.channels = try_map.channels;
@ -2402,8 +2424,8 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m,
return NULL;
}
exact_channels = true;
try_ss.channels = m->split->hw_channels;
exact_channels = false;
try_ss.channels = max_channels ? PA_CHANNELS_MAX : m->split->hw_channels;
pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_AUX);
}
@ -2416,15 +2438,40 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m,
&try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, exact_channels);
if (pcm) {
if (!exact_channels)
if (m->split) {
const char *mode_name = mode == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture";
if (try_map.channels < m->split->hw_channels) {
pa_logl((max_channels ? PA_LOG_NOTICE : PA_LOG_DEBUG),
"Error in ALSA UCM profile for %s (%s): %sChannels=%d > avail %d",
m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels);
/* Retry with max channel count, in case ALSA rounded down */
if (!max_channels) {
pa_alsa_close(&pcm);
return mapping_open_pcm(ucm, m, mode, true);
}
/* Just accept whatever we got... Some of the routings won't get connected
* anywhere */
m->split->hw_channels = try_map.channels;
m->split->broken = true;
} else if (try_map.channels > m->split->hw_channels) {
pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannels=%d < avail %d",
m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels);
m->split->hw_channels = try_map.channels;
m->split->broken = true;
}
} else if (!exact_channels) {
m->channel_map = try_map;
}
mapping_init_eld(m, pcm);
}
return pcm;
}
static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction)
static void pa_alsa_init_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction)
{
pa_proplist *props = pa_proplist_new();
uint32_t idx;
@ -2445,6 +2492,9 @@ static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping
pa_proplist_update(m->output_proplist, PA_UPDATE_REPLACE, props);
else
pa_proplist_update(m->input_proplist, PA_UPDATE_REPLACE, props);
/* Update HW channel count to match probed one */
m->split->hw_channels = leader->split->hw_channels;
}
pa_proplist_free(props);
@ -2464,7 +2514,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) {
if (!m->split)
pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm);
else
pa_alsa_init_proplist_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT);
pa_alsa_init_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT);
pa_alsa_close(&m->output_pcm);
}
@ -2479,7 +2529,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) {
if (!m->split)
pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm);
else
pa_alsa_init_proplist_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT);
pa_alsa_init_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT);
pa_alsa_close(&m->input_pcm);
}
@ -2521,7 +2571,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *
pa_log_info("Set ucm verb to %s", verb_name);
if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) {
pa_log("Failed to set verb %s", verb_name);
pa_log("Profile '%s': failed to set verb %s", p->name, verb_name);
p->supported = false;
continue;
}
@ -2536,8 +2586,10 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *
if (m->split && !m->split->leader)
continue;
m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK, false);
if (!m->output_pcm) {
pa_log_info("Profile '%s' mapping '%s': output PCM open failed",
p->name, m->name);
p->supported = false;
break;
}
@ -2554,8 +2606,10 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *
if (m->split && !m->split->leader)
continue;
m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE, false);
if (!m->input_pcm) {
pa_log_info("Profile '%s' mapping '%s': input PCM open failed",
p->name, m->name);
p->supported = false;
break;
}
@ -2564,6 +2618,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *
if (!p->supported) {
profile_finalize_probing(p);
pa_log_info("Profile %s not supported", p->name);
continue;
}

View file

@ -185,6 +185,7 @@ struct pa_alsa_ucm_split {
int channels;
int idx[PA_CHANNELS_MAX];
enum snd_pcm_chmap_position pos[PA_CHANNELS_MAX];
bool broken;
};
struct pa_alsa_ucm_device {

View file

@ -162,6 +162,11 @@ static int alsa_set_param(struct state *state, const char *k, const char *s)
int fmt_change = 0;
if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) {
state->default_channels = atoi(s);
if (state->default_channels > SPA_AUDIO_MAX_CHANNELS) {
spa_log_warn(state->log, "%p: %s: %s > %d, clamping",
state, k, s, SPA_AUDIO_MAX_CHANNELS);
state->default_channels = SPA_AUDIO_MAX_CHANNELS;
}
fmt_change++;
} else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) {
state->default_rate = atoi(s);
@ -1563,15 +1568,18 @@ static int add_channels(struct state *state, bool all, uint32_t index, uint32_t
spa_log_debug(state->log, "channels (%d %d) default:%d all:%d",
min, max, state->default_channels, all);
if (state->default_channels != 0 && !all) {
if (min < state->default_channels)
min = state->default_channels;
if (max > state->default_channels)
max = state->default_channels;
}
min = SPA_MIN(min, SPA_AUDIO_MAX_CHANNELS);
max = SPA_MIN(max, SPA_AUDIO_MAX_CHANNELS);
if (state->default_channels != 0 && !all) {
if (min > state->default_channels ||
max < state->default_channels)
spa_log_warn(state->log, "given audio.channels %d out of range:%d-%d",
state->default_channels, min, max);
else
min = max = state->default_channels;
}
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_channels, 0);
if (state->props.use_chmap && (maps = snd_pcm_query_chmaps(hndl)) != NULL) {
@ -1842,10 +1850,12 @@ static int enum_iec958_formats(struct state *state, uint32_t index, uint32_t *ne
spa_log_debug(state->log, "rate (%d %d)", rmin, rmax);
if (state->default_rate != 0) {
if (rmin < state->default_rate)
rmin = state->default_rate;
if (rmax > state->default_rate)
rmax = state->default_rate;
if (rmin > state->default_rate ||
rmax < state->default_rate)
spa_log_warn(state->log, "given audio.rate %d out of range:%d-%d",
state->default_rate, rmin, rmax);
else
rmin = rmax = state->default_rate;
}
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_iec958Codec, 0);

View file

@ -275,7 +275,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f
snprintf(alias, sizeof(alias), "%s:%s", client_name, port_name);
clean_name(alias);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, name);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, alias);
@ -529,8 +529,7 @@ impl_node_port_enum_params(void *object, int seq,
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(1u<<SPA_CONTROL_UMP));
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
break;
case SPA_PARAM_Format:
@ -541,8 +540,7 @@ impl_node_port_enum_params(void *object, int seq,
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_Format,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
SPA_FORMAT_CONTROL_types, SPA_POD_Int(1u<<SPA_CONTROL_UMP));
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
break;
case SPA_PARAM_Buffers:
@ -635,7 +633,7 @@ static int port_set_format(void *object, struct seq_port *port,
port->have_format = false;
} else {
struct spa_audio_info info = { 0 };
uint32_t types;
uint32_t types = 0;
if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0)
return err;
@ -646,13 +644,12 @@ static int port_set_format(void *object, struct seq_port *port,
if ((err = spa_pod_parse_object(format,
SPA_TYPE_OBJECT_Format, NULL,
SPA_FORMAT_CONTROL_types, SPA_POD_Int(&types))) < 0)
SPA_FORMAT_CONTROL_types, SPA_POD_OPT_Int(&types))) < 0)
return err;
if (types != 1u << SPA_CONTROL_UMP)
return -EINVAL;
port->current_format = info;
port->have_format = true;
port->control_types = types;
}
port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE;
@ -931,6 +928,7 @@ impl_init(const struct spa_handle_factory *factory,
this->quantum_limit = 8192;
this->min_pool_size = 500;
this->max_pool_size = 2000;
this->ump = true;
for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key;
@ -949,6 +947,8 @@ impl_init(const struct spa_handle_factory *factory,
spa_atou32(s, &this->min_pool_size, 0);
} else if (spa_streq(k, "api.alsa.seq.max-pool")) {
spa_atou32(s, &this->max_pool_size, 0);
} else if (spa_streq(k, "api.alsa.seq.ump")) {
this->ump = spa_atob(s);
}
}
@ -992,6 +992,7 @@ static const struct spa_dict_item info_items[] = {
"["SPA_KEY_API_ALSA_DISABLE_LONGNAME"=<bool, default false>] "
"[ api.alsa.seq.min-pool=<min-pool, default 500>] "
"[ api.alsa.seq.max-pool=<max-pool, default 2000>]"
"[ api.alsa.seq.ump = <boolean> ]"
},
};

View file

@ -24,7 +24,7 @@
#define CHECK(s,msg,...) if ((res = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(res)); return res; }
static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue)
static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue, bool probe_ump)
{
struct props *props = &state->props;
int res;
@ -37,13 +37,47 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu
0)) < 0)
return res;
if ((res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0)) < 0) {
snd_seq_close(conn->hndl);
spa_log_info(state->log, "%p: ALSA failed to enable UMP MIDI: %s",
state, snd_strerror(res));
return res;
if (!state->ump) {
spa_log_info(state->log, "%p: ALSA UMP MIDI disabled", state);
return 0;
}
#ifdef HAVE_ALSA_UMP
res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0);
if (!res) {
snd_seq_client_info_t *info = NULL;
/* Double check client version */
res = snd_seq_client_info_malloc(&info);
if (!res)
res = snd_seq_get_client_info(conn->hndl, info);
if (!res) {
res = snd_seq_client_info_get_midi_version(info);
if (res == SND_SEQ_CLIENT_UMP_MIDI_2_0)
res = 0;
else
res = -EIO;
}
if (info)
snd_seq_client_info_free(info);
}
#else
res = -EOPNOTSUPP;
#endif
if (res < 0) {
spa_log_lev(state->log, (probe_ump ? SPA_LOG_LEVEL_INFO : SPA_LOG_LEVEL_ERROR),
"%p: ALSA failed to enable UMP MIDI: %s", state, snd_strerror(res));
if (!probe_ump) {
snd_seq_close(conn->hndl);
return res; /* either all are UMP or none are UMP */
}
state->ump = false;
} else {
spa_log_debug(state->log, "%p: ALSA UMP MIDI enabled", state);
state->ump = true;
}
return 0;
}
@ -172,7 +206,7 @@ static void init_ports(struct seq_state *state)
}
}
static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev)
static void debug_event(struct seq_state *state, snd_seq_event_t *ev)
{
if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE)))
return;
@ -196,21 +230,74 @@ static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev)
ev->queue);
}
#ifdef HAVE_ALSA_UMP
static void debug_ump_event(struct seq_state *state, snd_seq_ump_event_t *ev)
{
if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE)))
return;
spa_log_trace(state->log, "event type:%d flags:0x%x", ev->type, ev->flags);
switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) {
case SND_SEQ_TIME_STAMP_TICK:
spa_log_trace(state->log, " time: %d ticks", ev->time.tick);
break;
case SND_SEQ_TIME_STAMP_REAL:
spa_log_trace(state->log, " time = %d.%09d",
(int)ev->time.time.tv_sec,
(int)ev->time.time.tv_nsec);
break;
}
spa_log_trace(state->log, " source:%d.%d dest:%d.%d queue:%d",
ev->source.client,
ev->source.port,
ev->dest.client,
ev->dest.port,
ev->queue);
}
#endif
static void alsa_seq_on_sys(struct spa_source *source)
{
struct seq_state *state = source->data;
snd_seq_ump_event_t *ev;
const bool ump = state->ump;
int res;
while (snd_seq_ump_event_input(state->sys.hndl, &ev) > 0) {
const snd_seq_addr_t *addr = &ev->data.addr;
while (1) {
const snd_seq_addr_t *addr;
snd_seq_event_type_t type;
if (ump) {
#ifdef HAVE_ALSA_UMP
snd_seq_ump_event_t *ev;
res = snd_seq_ump_event_input(state->sys.hndl, &ev);
if (res <= 0)
break;
debug_ump_event(state, ev);
addr = &ev->data.addr;
type = ev->type;
#else
spa_assert_not_reached();
#endif
} else {
snd_seq_event_t *ev;
res = snd_seq_event_input(state->sys.hndl, &ev);
if (res <= 0)
break;
debug_event(state, ev);
addr = &ev->data.addr;
type = ev->type;
}
if (addr->client == state->event.addr.client)
continue;
debug_event(state, ev);
switch (ev->type) {
switch (type) {
case SND_SEQ_EVENT_CLIENT_START:
case SND_SEQ_EVENT_CLIENT_CHANGE:
spa_log_info(state->log, "client add/change %d", addr->client);
@ -244,7 +331,7 @@ static void alsa_seq_on_sys(struct spa_source *source)
break;
default:
spa_log_info(state->log, "unhandled event %d: %d:%d",
ev->type, addr->client, addr->port);
type, addr->client, addr->port);
break;
}
@ -269,8 +356,8 @@ int spa_alsa_seq_open(struct seq_state *state)
spa_zero(reserve);
for (i = 0; i < 16; i++) {
spa_log_debug(state->log, "close %d", i);
if ((res = seq_open(state, &reserve[i], false)) < 0)
spa_log_debug(state->log, "open %d", i);
if ((res = seq_open(state, &reserve[i], false, (i == 0))) < 0)
break;
}
if (i >= 2) {
@ -316,7 +403,6 @@ int spa_alsa_seq_open(struct seq_state *state)
state->sys.source.func = alsa_seq_on_sys;
state->sys.source.data = state;
spa_loop_add_source(state->main_loop, &state->sys.source);
/* increase event queue timer resolution */
snd_seq_queue_timer_alloca(&timer);
@ -361,6 +447,8 @@ int spa_alsa_seq_open(struct seq_state *state)
state->timerfd = res;
spa_loop_add_source(state->main_loop, &state->sys.source);
state->opened = true;
return 0;
@ -498,7 +586,8 @@ static int prepare_buffer(struct seq_state *state, struct seq_port *port)
spa_pod_builder_init(&port->builder,
port->buffer->buf->datas[0].data,
port->buffer->buf->datas[0].maxsize);
spa_pod_builder_push_sequence(&port->builder, &port->frame, 0);
spa_pod_builder_push_sequence(&port->builder, &port->frame, 0);
port->ev_offset = SPA_IDX_INVALID;
return 0;
}
@ -529,21 +618,57 @@ static int process_recycle(struct seq_state *state)
static int process_read(struct seq_state *state)
{
snd_seq_ump_event_t *ev;
struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT];
const bool ump = state->ump;
uint32_t i;
uint32_t *data;
long size;
int res;
uint8_t midi1_data[MAX_EVENT_SIZE];
uint32_t ump_data[MAX_EVENT_SIZE];
int res = -1;
/* copy all new midi events into their port buffers */
while ((res = snd_seq_ump_event_input(state->event.hndl, &ev)) > 0) {
const snd_seq_addr_t *addr = &ev->source;
while (1) {
const snd_seq_addr_t *addr;
struct seq_port *port;
uint64_t ev_time, diff;
uint32_t offset;
uint32_t offset, ev_type;
void *event;
uint8_t *data_ptr;
size_t data_size = 0;
long size;
uint64_t ump_state = 0;
snd_seq_event_type_t SPA_UNUSED type;
debug_event(state, ev);
if (ump) {
#ifdef HAVE_ALSA_UMP
snd_seq_ump_event_t *ev;
res = snd_seq_ump_event_input(state->event.hndl, &ev);
if (res <= 0)
break;
debug_ump_event(state, ev);
event = ev;
addr = &ev->source;
ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time);
type = ev->type;
#else
spa_assert_not_reached();
#endif
} else {
snd_seq_event_t *ev;
res = snd_seq_event_input(state->event.hndl, &ev);
if (res <= 0)
break;
debug_event(state, ev);
event = ev;
addr = &ev->source;
ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time);
type = ev->type;
}
if ((port = find_port(state, stream, addr)) == NULL) {
spa_log_debug(state->log, "unknown port %d.%d",
@ -554,17 +679,13 @@ static int process_read(struct seq_state *state)
continue;
if ((res = prepare_buffer(state, port)) < 0) {
spa_log_debug(state->log, "can't prepare buffer port:%p %d.%d: %s",
spa_log_warn(state->log, "can't prepare buffer port:%p %d.%d: %s",
port, addr->client, addr->port, spa_strerror(res));
continue;
}
data = (uint32_t*)&ev->ump[0];
size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4;
/* queue_time is the estimated current time of the queue as calculated by
* the DLL. Calculate the age of the event. */
ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time);
if (state->queue_time > ev_time)
diff = state->queue_time - ev_time;
else
@ -577,17 +698,81 @@ static int process_read(struct seq_state *state)
else
offset = 0;
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
ev->type, ev_time, offset, size, addr->client, addr->port);
if (ump) {
#ifdef HAVE_ALSA_UMP
snd_seq_ump_event_t *ev = event;
spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP);
spa_pod_builder_bytes(&port->builder, data, size);
data_ptr = (uint8_t*)&ev->ump[0];
data_size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4;
#else
spa_assert_not_reached();
#endif
} else {
snd_seq_event_t *ev = event;
snd_midi_event_reset_decode(stream->codec);
if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) {
spa_log_warn(state->log, "decode failed: %s", snd_strerror(data_size));
continue;
}
data_ptr = midi1_data;
data_size = size;
}
ev_type = (port->control_types & (1u << SPA_CONTROL_UMP)) ?
SPA_CONTROL_UMP : SPA_CONTROL_Midi;
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
type, ev_time, offset, data_size, addr->client, addr->port);
if ((ump && ev_type == SPA_CONTROL_UMP) ||
(!ump && ev_type == SPA_CONTROL_Midi)) {
/* no conversion needed */
spa_pod_builder_control(&port->builder, offset, ev_type);
spa_pod_builder_bytes(&port->builder, data_ptr, data_size);
}
else if (ump) {
bool continued = port->ev_offset != SPA_IDX_INVALID;
/* UMP -> MIDI */
size = spa_ump_to_midi((uint32_t*)data_ptr, data_size,
midi1_data, sizeof(midi1_data));
if (size < 0)
continue;
if (!continued) {
spa_pod_builder_control(&port->builder, offset, ev_type);
port->ev_offset = spa_pod_builder_bytes_start(&port->builder);
if (midi1_data[0] == 0xf0)
continued = true;
} else {
if (midi1_data[size-1] == 0xf7)
continued = false;
}
spa_pod_builder_bytes_append(&port->builder, port->ev_offset, midi1_data, size);
if (!continued) {
spa_pod_builder_bytes_end(&port->builder, port->ev_offset);
port->ev_offset = SPA_IDX_INVALID;
}
} else {
/* MIDI -> UMP */
while (data_size > 0) {
size = spa_ump_from_midi(&data_ptr, &data_size,
ump_data, sizeof(ump_data), 0, &ump_state);
if (size <= 0)
break;
spa_pod_builder_control(&port->builder, offset, ev_type);
spa_pod_builder_bytes(&port->builder, ump_data, size);
}
}
/* make sure we can fit at least one control event of max size otherwise
* we keep the event in the queue and try to copy it in the next cycle */
if (port->builder.state.offset +
sizeof(struct spa_pod_control) +
MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize)
sizeof(struct spa_pod_control) +
MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize)
break;
}
if (res < 0 && res != -EAGAIN)
@ -604,6 +789,8 @@ static int process_read(struct seq_state *state)
continue;
if (prepare_buffer(state, port) >= 0) {
if (port->ev_offset != SPA_IDX_INVALID)
spa_pod_builder_bytes_end(&port->builder, port->ev_offset);
spa_pod_builder_pop(&port->builder, &port->frame);
port->buffer->buf->datas[0].chunk->offset = 0;
@ -651,6 +838,7 @@ static int process_read(struct seq_state *state)
static int process_write(struct seq_state *state)
{
struct seq_stream *stream = &state->streams[SPA_DIRECTION_INPUT];
const bool ump = state->ump;
uint32_t i;
int err, res = 0;
@ -661,9 +849,9 @@ static int process_write(struct seq_state *state)
struct spa_pod_sequence *pod;
struct spa_data *d;
struct spa_pod_control *c;
snd_seq_ump_event_t ev;
uint64_t out_time;
snd_seq_real_time_t out_rt;
bool first = true;
if (!port->valid || io == NULL)
continue;
@ -689,31 +877,101 @@ static int process_write(struct seq_state *state)
SPA_POD_SEQUENCE_FOREACH(pod, c) {
size_t body_size;
uint8_t *body;
if (c->type != SPA_CONTROL_UMP)
continue;
int size;
body = SPA_POD_BODY(&c->value);
body_size = SPA_POD_BODY_SIZE(&c->value);
spa_zero(ev);
memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size));
snd_seq_ev_set_source(&ev, state->event.addr.port);
snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port);
out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset);
out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC;
out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC;
snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%zd port:%d.%d",
ev.type, out_time, c->offset, body_size, port->addr.client, port->addr.port);
spa_log_trace_fp(state->log, "event time:%"PRIu64" offset:%d size:%zd port:%d.%d",
out_time, c->offset, body_size, port->addr.client, port->addr.port);
if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
if (ump) {
#ifdef HAVE_ALSA_UMP
uint8_t *ump_data;
uint32_t data[MAX_EVENT_SIZE];
snd_seq_ump_event_t ev;
do {
switch (c->type) {
case SPA_CONTROL_UMP:
ump_data = body;
size = body_size;
body_size = 0;
break;
case SPA_CONTROL_Midi:
size = spa_ump_from_midi(&body, &body_size,
data, sizeof(data), 0, &port->ump_state);
ump_data = (uint8_t*)data;
break;
default:
size = 0;
body_size = 0;
continue;
}
if (size <= 0)
break;
snd_seq_ump_ev_clear(&ev);
snd_seq_ev_set_ump_data(&ev, ump_data, SPA_MIN(sizeof(ev.ump), (size_t)size));
snd_seq_ev_set_source(&ev, state->event.addr.port);
snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port);
snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
} while (body_size > 0);
#else
spa_assert_not_reached();
#endif
} else {
snd_seq_event_t ev;
uint8_t data[MAX_EVENT_SIZE];
uint8_t *midi_data;
switch (c->type) {
case SPA_CONTROL_UMP:
if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0)
continue;
midi_data = data;
break;
case SPA_CONTROL_Midi:
midi_data = body;
size = body_size;
break;
default:
continue;
}
if (first)
snd_seq_ev_clear(&ev);
if ((size = snd_midi_event_encode(stream->codec, midi_data, size, &ev)) < 0) {
spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size));
snd_midi_event_reset_encode(stream->codec);
first = true;
continue;
}
first = false;
if (ev.type == SND_SEQ_EVENT_NONE)
/* this can happen when the event is not complete yet, like
* a sysex message and we need to encode some more data. */
continue;
snd_seq_ev_set_source(&ev, state->event.addr.port);
snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port);
snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
first = true;
}
}
}

View file

@ -12,8 +12,12 @@ extern "C" {
#include <stddef.h>
#include <math.h>
#include "config.h"
#include <alsa/asoundlib.h>
#ifdef HAVE_ALSA_UMP
#include <alsa/ump_msg.h>
#endif
#include <spa/support/plugin.h>
#include <spa/support/loop.h>
@ -76,7 +80,10 @@ struct seq_port {
struct buffer *buffer;
struct spa_pod_builder builder;
struct spa_pod_frame frame;
uint32_t ev_offset;
uint64_t ump_state;
uint32_t control_types;
struct spa_audio_info current_format;
unsigned int have_format:1;
unsigned int valid:1;
@ -153,6 +160,7 @@ struct seq_state {
unsigned int opened:1;
unsigned int started:1;
unsigned int following:1;
unsigned int ump:1;
struct seq_stream streams[2];

View file

@ -442,28 +442,32 @@ static int negotiate_buffers(struct impl *this)
state = 0;
param = NULL;
if ((res = node_port_enum_params_sync(this, this->follower,
this->direction, 0,
if ((res = node_port_enum_params_sync(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_Buffers, &state,
param, &param, &b)) < 0) {
if (res == -ENOENT)
param = NULL;
else {
debug_params(this, this->follower, this->direction, 0,
SPA_PARAM_Buffers, param, "follower buffers", res);
debug_params(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_Buffers, param, "target buffers", res);
return res;
}
}
state = 0;
if ((res = node_port_enum_params_sync(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
if ((res = node_port_enum_params_sync(this, this->follower,
this->direction, 0,
SPA_PARAM_Buffers, &state,
param, &param, &b)) != 1) {
debug_params(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_Buffers, param, "convert buffers", res);
return -ENOTSUP;
if (res == -ENOENT)
res = 0;
else {
debug_params(this, this->follower, this->direction, 0,
SPA_PARAM_Buffers, param, "follower buffers", res);
return res < 0 ? res : -ENOTSUP;
}
}
if (param == NULL)
return -ENOTSUP;
@ -497,7 +501,7 @@ static int negotiate_buffers(struct impl *this)
if (this->async)
buffers = SPA_MAX(2u, buffers);
spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d",
spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d",
this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc);
align = SPA_MAX(align, this->max_align);
@ -941,27 +945,13 @@ static int negotiate_format(struct impl *this)
spa_node_send_command(this->follower,
&SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin));
/* first try the ideal converter format, which is likely passthrough */
tstate = 0;
fres = node_port_enum_params_sync(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_EnumFormat, &tstate,
NULL, &format, &b);
if (fres == 1) {
fstate = 0;
res = node_port_enum_params_sync(this, this->follower,
this->direction, 0,
SPA_PARAM_EnumFormat, &fstate,
format, &format, &b);
if (res == 1)
goto found;
}
/* then try something the follower can accept */
/* The target has been negotiated on its other ports and so it can propose
* a passthrough format or an ideal conversion. We use the suggestions of the
* target to find the best follower format */
for (fstate = 0;;) {
format = NULL;
res = node_port_enum_params_sync(this, this->follower,
this->direction, 0,
res = node_port_enum_params_sync(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_EnumFormat, &fstate,
NULL, &format, &b);
@ -971,8 +961,8 @@ static int negotiate_format(struct impl *this)
break;
tstate = 0;
fres = node_port_enum_params_sync(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
fres = node_port_enum_params_sync(this, this->follower,
this->direction, 0,
SPA_PARAM_EnumFormat, &tstate,
format, &format, &b);
if (fres == 0 && res == 1)
@ -981,7 +971,6 @@ static int negotiate_format(struct impl *this)
res = fres;
break;
}
found:
if (format == NULL) {
debug_params(this, this->follower, this->direction, 0,
SPA_PARAM_EnumFormat, format, "follower format", res);
@ -997,6 +986,8 @@ found:
format = merge_objects(this, &b, SPA_PARAM_Format,
(struct spa_pod_object*)format,
(struct spa_pod_object*)def);
if (format == NULL)
return -ENOSPC;
spa_pod_fixate(format);
@ -1043,7 +1034,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
break;
}
if ((res = spa_node_send_command(this->target, command)) < 0) {
res = spa_node_send_command(this->target, command);
if (res == -ENOTSUP && this->target != this->follower)
res = 0;
if (res < 0) {
spa_log_error(this->log, "%p: can't send command %d: %s",
this, SPA_NODE_COMMAND_ID(command),
spa_strerror(res));
@ -1612,13 +1606,13 @@ port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction dir
uint32_t count = 0;
struct spa_result_node_params result;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
result.id = id;
result.next = start;
next:
result.index = result.next;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
if (result.next < 0x100000) {
/* Enumerate follower formats first, until we have enough or we run out */
if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id,

View file

@ -210,7 +210,6 @@ struct stage_context {
uint32_t src_idx;
uint32_t dst_idx;
uint32_t final_idx;
uint32_t n_datas;
struct port *ctrlport;
};
@ -219,8 +218,6 @@ struct stage {
bool passthrough;
uint32_t in_idx;
uint32_t out_idx;
uint32_t n_in;
uint32_t n_out;
void *data;
void (*run) (struct stage *stage, struct stage_context *c);
};
@ -365,7 +362,7 @@ static void emit_port_info(struct impl *this, struct port *port, bool full)
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true");
} else if (PORT_IS_CONTROL(this, port->direction, port->id)) {
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi");
}
if (this->group_name[0] != '\0')
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name);
@ -1003,13 +1000,13 @@ static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph)
{
int res;
char rate_str[64];
struct dir *in;
struct dir *dir;
if (graph == NULL)
return 0;
in = &this->dir[SPA_DIRECTION_INPUT];
snprintf(rate_str, sizeof(rate_str), "%d", in->format.info.raw.rate);
dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)];
snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate);
spa_filter_graph_deactivate(graph);
res = spa_filter_graph_activate(graph,
@ -1217,7 +1214,7 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params)
while (true) {
const char *name;
struct spa_pod *pod;
char value[512];
char value[4096];
if (spa_pod_parser_get_string(&prs, &name) < 0)
break;
@ -1971,13 +1968,19 @@ static int setup_resample(struct impl *this)
struct dir *in = &this->dir[SPA_DIRECTION_INPUT];
struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT];
int res;
uint32_t channels;
if (this->direction == SPA_DIRECTION_INPUT)
channels = in->format.info.raw.channels;
else
channels = out->format.info.raw.channels;
spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this,
spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32),
out->format.info.raw.channels,
channels,
in->format.info.raw.rate,
spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32),
out->format.info.raw.channels,
channels,
out->format.info.raw.rate);
if (this->props.resample_disabled && !this->resample_peaks &&
@ -1987,7 +1990,7 @@ static int setup_resample(struct impl *this)
if (this->resample.free)
resample_free(&this->resample);
this->resample.channels = out->format.info.raw.channels;
this->resample.channels = channels;
this->resample.i_rate = in->format.info.raw.rate;
this->resample.o_rate = out->format.info.raw.rate;
this->resample.log = this->log;
@ -3217,8 +3220,6 @@ static void add_wav_stage(struct impl *impl, struct stage_context *ctx)
s->passthrough = false;
s->in_idx = ctx->src_idx;
s->out_idx = ctx->src_idx;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = NULL;
s->run = run_wav_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3230,7 +3231,7 @@ static void run_dst_remap_stage(struct stage *s, struct stage_context *c)
struct impl *impl = s->impl;
struct dir *dir = &impl->dir[SPA_DIRECTION_OUTPUT];
uint32_t i;
for (i = 0; i < s->n_in; i++) {
for (i = 0; i < dir->conv.n_channels; i++) {
c->datas[s->out_idx][i] = c->datas[s->in_idx][dir->remap[i]];
spa_log_trace_fp(impl->log, "%p: output remap %d -> %d", impl, i, dir->remap[i]);
}
@ -3242,8 +3243,6 @@ static void add_dst_remap_stage(struct impl *impl, struct stage_context *ctx)
s->passthrough = false;
s->in_idx = ctx->dst_idx;
s->out_idx = CTX_DATA_REMAP_DST;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = NULL;
s->run = run_dst_remap_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3269,8 +3268,6 @@ static void add_src_remap_stage(struct impl *impl, struct stage_context *ctx)
s->passthrough = false;
s->in_idx = ctx->src_idx;
s->out_idx = CTX_DATA_REMAP_SRC;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = NULL;
s->run = run_src_remap_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3304,8 +3301,6 @@ static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx)
s->passthrough = false;
s->in_idx = ctx->src_idx;
s->out_idx = ctx->dst_idx;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = NULL;
s->run = run_src_convert_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3334,8 +3329,6 @@ static void add_resample_stage(struct impl *impl, struct stage_context *ctx)
s->passthrough = false;
s->in_idx = ctx->src_idx;
s->out_idx = ctx->dst_idx;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = NULL;
s->run = run_resample_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3383,8 +3376,6 @@ static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph
s->passthrough = false;
s->in_idx = ctx->src_idx;
s->out_idx = ctx->dst_idx;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = fg;
s->run = run_filter_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3399,8 +3390,6 @@ static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx)
s->passthrough = false;
s->in_idx = ctx->src_idx;
s->out_idx = ctx->dst_idx;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = NULL;
s->run = run_channelmix_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3434,8 +3423,6 @@ static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx)
s->passthrough = false;
s->in_idx = ctx->src_idx;
s->out_idx = ctx->final_idx;
s->n_in = ctx->n_datas;
s->n_out = ctx->n_datas;
s->data = NULL;
s->run = run_dst_convert_stage;
spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages);
@ -3828,14 +3815,14 @@ static int impl_node_process(void *object)
ctx.in_samples = n_samples;
ctx.n_samples = n_samples;
ctx.n_out = n_out;
ctx.src_idx = CTX_DATA_SRC;
ctx.dst_idx = CTX_DATA_DST;
ctx.final_idx = CTX_DATA_DST;
ctx.n_datas = dir->conv.n_channels;
ctx.ctrlport = ctrlport;
if (this->recalc)
if (SPA_UNLIKELY(this->recalc)) {
ctx.src_idx = CTX_DATA_SRC;
ctx.dst_idx = CTX_DATA_DST;
ctx.final_idx = CTX_DATA_DST;
recalc_stages(this, &ctx);
}
for (i = 0; i < this->n_stages; i++) {
struct stage *s = &this->stages[i];

View file

@ -1406,18 +1406,44 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t
struct rfcomm_call_data *call_data = data;
struct rfcomm *rfcomm = call_data->rfcomm;
struct impl *backend = rfcomm->backend;
struct spa_bt_telephony_call *call, *tcall;
bool found_held = false;
bool hfp_hf_in_progress = false;
char reply[20];
bool res;
spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) {
if (call->state == CALL_STATE_HELD)
found_held = true;
}
switch (call_data->call->state) {
case CALL_STATE_ACTIVE:
case CALL_STATE_DIALING:
case CALL_STATE_ALERTING:
case CALL_STATE_INCOMING:
rfcomm_send_cmd(rfcomm, "AT+CHUP");
if (found_held) {
if (!rfcomm->chld_supported) {
*err = BT_TELEPHONY_ERROR_NOT_SUPPORTED;
return;
} else if (rfcomm->hfp_hf_in_progress) {
*err = BT_TELEPHONY_ERROR_IN_PROGRESS;
return;
}
rfcomm_send_cmd(rfcomm, "AT+CHLD=1");
hfp_hf_in_progress = true;
} else {
rfcomm_send_cmd(rfcomm, "AT+CHUP");
}
break;
case CALL_STATE_WAITING:
if (rfcomm->hfp_hf_in_progress) {
*err = BT_TELEPHONY_ERROR_IN_PROGRESS;
return;
}
rfcomm_send_cmd(rfcomm, "AT+CHLD=0");
hfp_hf_in_progress = true;
break;
default:
spa_log_info(backend->log, "Call not incoming, waiting or active: skip hangup");
@ -1435,6 +1461,24 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t
return;
}
if (hfp_hf_in_progress) {
if (call_data->call->state != CALL_STATE_WAITING) {
spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) {
if (call->state == CALL_STATE_ACTIVE) {
call->state = CALL_STATE_DISCONNECTED;
telephony_call_notify_updated_props(call);
telephony_call_destroy(call);
}
}
spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) {
if (call->state == CALL_STATE_HELD) {
call->state = CALL_STATE_ACTIVE;
telephony_call_notify_updated_props(call);
}
}
}
rfcomm->hfp_hf_in_progress = true;
}
*err = BT_TELEPHONY_ERROR_NONE;
}
@ -2286,6 +2330,26 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
}
SPA_FALLTHROUGH;
case hfp_hf_chld:
rfcomm->slc_configured = true;
if (!rfcomm->codec_negotiation_supported) {
if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) {
// TODO: We should manage the missing transport
} else {
spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
}
}
rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0);
rfcomm->telephony_ag->address = strdup(rfcomm->device->address);
telephony_ag_set_callbacks(rfcomm->telephony_ag,
&telephony_ag_callbacks, rfcomm);
if (rfcomm->transport) {
rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec;
rfcomm->telephony_ag->transport.state = rfcomm->transport->state;
}
telephony_ag_register(rfcomm->telephony_ag);
rfcomm_send_cmd(rfcomm, "AT+CLIP=1");
rfcomm->hf_state = hfp_hf_clip;
break;
@ -2312,25 +2376,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
SPA_FALLTHROUGH;
case hfp_hf_nrec:
rfcomm->hf_state = hfp_hf_slc1;
rfcomm->slc_configured = true;
if (!rfcomm->codec_negotiation_supported) {
if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) {
// TODO: We should manage the missing transport
} else {
spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
}
}
rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0);
rfcomm->telephony_ag->address = strdup(rfcomm->device->address);
telephony_ag_set_callbacks(rfcomm->telephony_ag,
&telephony_ag_callbacks, rfcomm);
if (rfcomm->transport) {
rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec;
rfcomm->telephony_ag->transport.state = rfcomm->transport->state;
}
telephony_ag_register(rfcomm->telephony_ag);
if (rfcomm->hfp_hf_clcc) {
rfcomm_send_cmd(rfcomm, "AT+CLCC");
@ -3318,6 +3363,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
} else if (profile == SPA_BT_PROFILE_HFP_AG) {
/* Start SLC connection */
unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_CLIP | SPA_BT_HFP_HF_FEATURE_3WAY |
SPA_BT_HFP_HF_FEATURE_ECNR |
SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_STATUS |
SPA_BT_HFP_HF_FEATURE_ESCO_S4;
bool has_msbc = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC);

View file

@ -2024,13 +2024,13 @@ impl_init(const struct spa_handle_factory *factory,
for (i = 0; i < N_PORTS; ++i) {
struct port *port = &this->ports[i];
static const struct spa_dict_item in_port_items[] = {
SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"),
SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"),
SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "in"),
SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "in"),
SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"),
};
static const struct spa_dict_item out_port_items[] = {
SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"),
SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"),
SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "out"),
SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "out"),
SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"),

View file

@ -298,7 +298,8 @@ static int port_enum_formats(void *object, struct port *port,
*param = spa_pod_builder_add_object(builder,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(SPA_ID_INVALID));
break;
default:
return 0;
@ -671,6 +672,14 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *
}
}
static inline bool control_needs_conversion(struct port *port, uint32_t type)
{
/* we only converter between midi and UMP and only when the port
* does not support the current type */
return (type == SPA_CONTROL_Midi || type == SPA_CONTROL_UMP) &&
port->types && (port->types & (1u << type)) == 0;
}
static int impl_node_process(void *object)
{
struct impl *this = object;
@ -782,7 +791,7 @@ static int impl_node_process(void *object)
if (next == NULL)
break;
if (outport->types && (outport->types & (1u << next->type)) == 0) {
if (control_needs_conversion(outport, next->type)) {
uint8_t *data = SPA_POD_BODY(&next->value);
size_t size = SPA_POD_BODY_SIZE(&next->value);

View file

@ -235,6 +235,8 @@ static void ebur128_cleanup(void * Instance)
static void ebur128_activate(void * Instance)
{
struct ebur128_impl *impl = Instance;
unsigned long max_window;
int major, minor, patch;
int mode = 0, i;
int modes[] = {
EBUR128_MODE_M,
@ -264,12 +266,17 @@ static void ebur128_activate(void * Instance)
mode |= modes[i];
}
ebur128_get_version(&major, &minor, &patch);
max_window = impl->max_window;
if (major == 1 && minor == 2 && (patch == 5 || patch == 6))
max_window = (max_window + 999) / 1000;
for (i = 0; i < 7; i++) {
impl->st[i] = ebur128_init(1, impl->rate, mode);
if (impl->st[i]) {
ebur128_set_channel(impl->st[i], i, channels[i]);
ebur128_set_max_history(impl->st[i], impl->max_history);
ebur128_set_max_window(impl->st[i], impl->max_window);
ebur128_set_max_window(impl->st[i], max_window);
}
}
}
@ -349,7 +356,7 @@ static struct spa_fga_port ebur128_ports[] = {
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL,
},
{ .index = PORT_OUT_SHORTTERM,
.name = "Shorttem LUFS",
.name = "Shortterm LUFS",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL,
},
{ .index = PORT_OUT_GLOBAL,

View file

@ -256,9 +256,8 @@ static int impl_process(void *object,
if (out[i] == NULL)
continue;
port = i < graph->n_output ? &graph->output[i] : NULL;
if (port && port->desc)
port = &graph->output[i];
if (port->desc)
port->desc->connect_port(*port->hndl, port->port, out[i]);
else
memset(out[i], 0, n_samples * sizeof(float));

View file

@ -234,7 +234,7 @@ struct instance {
LV2_Options_Option options[6];
LV2_Feature options_feature;
const LV2_Feature *features[7];
const LV2_Feature *features[8];
const LV2_Worker_Interface *work_iface;
@ -328,9 +328,11 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s
c->atom_Float, &fsample_rate };
i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL };
i->options_feature.URI = LV2_OPTIONS__options;
i->options_feature.data = i->options;
i->features[n_features++] = &i->options_feature;
i->options_feature.URI = LV2_OPTIONS__options;
i->options_feature.data = i->options;
i->features[n_features++] = &i->options_feature;
i->features[n_features++] = NULL;
spa_assert(n_features <= SPA_N_ELEMENTS(i->features));
i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features);
if (i->instance == NULL) {

View file

@ -273,7 +273,7 @@ next:
default:
return spa_libcamera_enum_controls(impl,
GET_OUT_PORT(impl, 0),
seq, result.index - 2, num, filter);
seq, result.index, 2, num, filter);
}
break;
}
@ -535,7 +535,7 @@ next:
switch (id) {
case SPA_PARAM_PropInfo:
return spa_libcamera_enum_controls(impl, port, seq, start, num, filter);
return spa_libcamera_enum_controls(impl, port, seq, start, 0, num, filter);
case SPA_PARAM_EnumFormat:
return spa_libcamera_enum_format(impl, port, seq, start, num, filter);

View file

@ -495,7 +495,7 @@ static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id)
static int
spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq,
uint32_t start, uint32_t num,
uint32_t start, uint32_t offset, uint32_t num,
const struct spa_pod *filter)
{
const ControlInfoMap &info = impl->camera->controls();
@ -513,7 +513,7 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq,
result.next = start;
auto it = info.begin();
for (skip = result.next; skip; skip--)
for (skip = result.next - offset; skip; skip--)
it++;
if (false) {

View file

@ -675,7 +675,7 @@ static int port_set_format(struct impl *this, struct port *port,
const struct spa_pod *format)
{
struct spa_video_info info;
int res;
int res = 0;
spa_zero(info);
@ -755,7 +755,7 @@ static int port_set_format(struct impl *this, struct port *port,
emit_port_info(this, port, false);
emit_node_info(this, false);
return 0;
return res;
}
static int impl_node_port_set_param(void *object,

View file

@ -1873,7 +1873,12 @@ static int spa_v4l2_stream_on(struct impl *this)
spa_log_debug(this->log, "starting");
port->first_buffer = true;
if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw ||
port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_mjpg ||
port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_jpeg)
port->first_buffer = true;
else
port->first_buffer = false;
mmap_read(this);
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

View file

@ -403,6 +403,7 @@ static int negotiate_buffers(struct impl *this)
uint32_t i, size, buffers, blocks, align, flags, stride = 0;
uint32_t *aligns;
struct spa_data *datas;
struct spa_meta metas[1];
uint64_t follower_flags, conv_flags;
spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers);
@ -415,28 +416,32 @@ static int negotiate_buffers(struct impl *this)
state = 0;
param = NULL;
if ((res = spa_node_port_enum_params_sync(this->follower,
this->direction, 0,
if ((res = spa_node_port_enum_params_sync(this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_Buffers, &state,
param, &param, &b)) < 0) {
if (res == -ENOENT)
param = NULL;
else {
debug_params(this, this->follower, this->direction, 0,
SPA_PARAM_Buffers, param, "follower buffers", res);
debug_params(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_Buffers, param, "target buffers", res);
return res;
}
}
state = 0;
if ((res = spa_node_port_enum_params_sync(this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
if ((res = spa_node_port_enum_params_sync(this->follower,
this->direction, 0,
SPA_PARAM_Buffers, &state,
param, &param, &b)) != 1) {
debug_params(this, this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_Buffers, param, "convert buffers", res);
return -ENOTSUP;
if (res == -ENOENT)
res = 0;
else {
debug_params(this, this->follower, this->direction, 0,
SPA_PARAM_Buffers, param, "follower buffers", res);
return res < 0 ? res : -ENOTSUP;
}
}
if (param == NULL)
return -ENOTSUP;
@ -470,7 +475,7 @@ static int negotiate_buffers(struct impl *this)
if (this->async)
buffers = SPA_MAX(2u, buffers);
spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d",
spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d",
this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc);
align = SPA_MAX(align, this->max_align);
@ -484,9 +489,11 @@ static int negotiate_buffers(struct impl *this)
datas[i].maxsize = size;
aligns[i] = align;
}
metas[0].type = SPA_META_Header;
metas[0].size = sizeof(struct spa_meta_header);
free(this->buffers);
this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns);
this->buffers = spa_buffer_alloc_array(buffers, flags, 1, metas, blocks, datas, aligns);
if (this->buffers == NULL)
return -errno;
this->n_buffers = buffers;
@ -905,27 +912,13 @@ static int negotiate_format(struct impl *this)
spa_node_send_command(this->follower,
&SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin));
/* first try the ideal converter format, which is likely passthrough */
tstate = 0;
fres = spa_node_port_enum_params_sync(this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_EnumFormat, &tstate,
NULL, &format, &b);
if (fres == 1) {
fstate = 0;
res = spa_node_port_enum_params_sync(this->follower,
this->direction, 0,
SPA_PARAM_EnumFormat, &fstate,
format, &format, &b);
if (res == 1)
goto found;
}
/* then try something the follower can accept */
/* The target has been negotiated on its other ports and so it can propose
* a passthrough format or an ideal conversion. We use the suggestions of the
* target to find the best follower format */
for (fstate = 0;;) {
format = NULL;
res = spa_node_port_enum_params_sync(this->follower,
this->direction, 0,
res = spa_node_port_enum_params_sync(this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
SPA_PARAM_EnumFormat, &fstate,
NULL, &format, &b);
@ -935,8 +928,8 @@ static int negotiate_format(struct impl *this)
break;
tstate = 0;
fres = spa_node_port_enum_params_sync(this->target,
SPA_DIRECTION_REVERSE(this->direction), 0,
fres = spa_node_port_enum_params_sync(this->follower,
this->direction, 0,
SPA_PARAM_EnumFormat, &tstate,
format, &format, &b);
if (fres == 0 && res == 1)
@ -945,7 +938,6 @@ static int negotiate_format(struct impl *this)
res = fres;
break;
}
found:
if (format == NULL) {
debug_params(this, this->follower, this->direction, 0,
SPA_PARAM_EnumFormat, format, "follower format", res);
@ -961,6 +953,8 @@ found:
format = merge_objects(this, &b, SPA_PARAM_Format,
(struct spa_pod_object*)format,
(struct spa_pod_object*)def);
if (format == NULL)
return -ENOSPC;
spa_pod_fixate(format);
@ -1007,7 +1001,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
break;
}
if ((res = spa_node_send_command(this->target, command)) < 0) {
res = spa_node_send_command(this->target, command);
if (res == -ENOTSUP && this->target != this->follower)
res = 0;
if (res < 0) {
spa_log_error(this->log, "%p: can't send command %d: %s",
this, SPA_NODE_COMMAND_ID(command),
spa_strerror(res));

View file

@ -228,7 +228,7 @@ static void emit_port_info(struct impl *this, struct port *port, bool full)
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true");
} else if (PORT_IS_CONTROL(this, port->direction, port->id)) {
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi");
}
if (this->group_name[0] != '\0')
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name);
@ -1812,6 +1812,7 @@ static int impl_node_process(void *object)
}
sbuf = &in_port->buffers[input->buffer_id];
input->status = SPA_STATUS_NEED_DATA;
if ((dbuf = peek_buffer(this, out_port)) == NULL) {
spa_log_error(this->log, "%p: out of buffers", this);
@ -1902,8 +1903,6 @@ static int impl_node_process(void *object)
output->buffer_id = dbuf->id;
output->status = SPA_STATUS_HAVE_DATA;
input->status = SPA_STATUS_NEED_DATA;
return SPA_STATUS_HAVE_DATA;
}

View file

@ -0,0 +1,4 @@
context.modules = [
# Use mDNS to detect and load module-raop-sink
{ name = libpipewire-module-raop-discover }
]

View file

@ -1,6 +1,7 @@
conf_files = [
'10-rates.conf',
'20-upmix.conf',
'50-raop.conf',
]
foreach c : conf_files

View file

@ -11,6 +11,12 @@ systemd_config = configuration_data()
systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire')
systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse')
pw_service_reqs = ''
if get_option('dbus').enabled()
pw_service_reqs += 'dbus.service '
endif
systemd_config.set('PW_SERVICE_REQS', pw_service_reqs)
configure_file(input : 'pipewire.service.in',
output : 'pipewire.service',
configuration : systemd_config,

View file

@ -13,7 +13,8 @@ Description=PipeWire Multimedia Service
#
# After=pipewire.socket is not needed, as it is already implicit in the
# socket-service relationship, see systemd.socket(5).
Requires=pipewire.socket
Requires=pipewire.socket @PW_SERVICE_REQS@
ConditionUser=!root
[Service]
LockPersonality=yes

View file

@ -1,5 +1,6 @@
[Unit]
Description=PipeWire Multimedia System Sockets
ConditionUser=!root
[Socket]
Priority=6

View file

@ -213,7 +213,7 @@ int main(int argc, char *argv[])
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(struct port),
pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit raw UMP",
PW_KEY_FORMAT_DSP, "8 bit raw midi",
PW_KEY_PORT_NAME, "output",
NULL),
NULL, 0);

View file

@ -327,6 +327,7 @@ static void do_add_nodes(GstPipeWireDeviceProvider *self)
gst_object_ref_sink (device),
compare_device_session_priority);
} else {
gst_object_ref (device);
gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device);
}
}
@ -484,7 +485,8 @@ destroy_node (void *data)
}
if (nd->dev != NULL) {
gst_device_provider_device_remove (provider, GST_DEVICE (nd->dev));
gst_device_provider_device_remove (provider, nd->dev);
gst_clear_object (&nd->dev);
}
if (nd->caps)
gst_caps_unref(nd->caps);

View file

@ -1065,8 +1065,19 @@ handle_rect_prop (const struct spa_pod_prop *prop, const char *width, const char
{
if (n_items < 3)
return;
gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width,
height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL);
if (rect[1].width == rect[2].width &&
rect[1].height == rect[2].height) {
gst_caps_set_simple (res,
width, G_TYPE_INT, rect[1].width,
height, G_TYPE_INT, rect[1].height,
NULL);
} else {
gst_caps_set_simple (res,
width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width,
height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height,
NULL);
}
break;
}
case SPA_CHOICE_Enum:
@ -1117,8 +1128,17 @@ handle_fraction_prop (const struct spa_pod_prop *prop, const char *key, GstCaps
{
if (n_items < 3)
return;
gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom,
fract[2].num, fract[2].denom, NULL);
if (fract[1].num == fract[2].num &&
fract[1].denom == fract[2].denom) {
gst_caps_set_simple (res, key, GST_TYPE_FRACTION,
fract[1].num, fract[1].denom, NULL);
} else {
gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE,
fract[1].num, fract[1].denom,
fract[2].num, fract[2].denom,
NULL);
}
break;
}
case SPA_CHOICE_Enum:

View file

@ -305,6 +305,7 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink)
struct spa_pod_builder b = { NULL };
uint8_t buffer[1024];
struct spa_pod_frame f;
guint n_params = 0;
config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool));
gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers);
@ -325,20 +326,22 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink)
(1<<SPA_DATA_MemFd) |
(1<<SPA_DATA_MemPtr)),
0);
port_params[0] = spa_pod_builder_pop (&b, &f);
port_params[n_params++] = spa_pod_builder_pop (&b, &f);
port_params[1] = spa_pod_builder_add_object (&b,
port_params[n_params++] = spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header)));
port_params[2] = spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region)));
if (sink->is_video) {
port_params[n_params++] = spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region)));
}
pw_thread_loop_lock (sink->stream->core->loop);
pw_stream_update_params (sink->stream->pwstream, port_params, 3);
pw_stream_update_params (sink->stream->pwstream, port_params, n_params);
pw_thread_loop_unlock (sink->stream->core->loop);
}

View file

@ -41,7 +41,7 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug);
#define GST_CAT_DEFAULT pipewire_src_debug
#define DEFAULT_ALWAYS_COPY false
#define DEFAULT_MIN_BUFFERS 8
#define DEFAULT_MIN_BUFFERS 1
#define DEFAULT_MAX_BUFFERS INT32_MAX
#define DEFAULT_RESEND_LAST false
#define DEFAULT_KEEPALIVE_TIME 0
@ -1091,14 +1091,34 @@ handle_format_change (GstPipeWireSrc *pwsrc,
}
pw_peer_caps = gst_caps_from_format (param);
if (pw_peer_caps && pwsrc->possible_caps) {
GST_DEBUG_OBJECT (pwsrc, "peer caps %" GST_PTR_FORMAT, pw_peer_caps);
GST_DEBUG_OBJECT (pwsrc, "possible caps %" GST_PTR_FORMAT, pwsrc->possible_caps);
pwsrc->caps = gst_caps_intersect_full (pw_peer_caps,
pwsrc->possible_caps,
GST_CAPS_INTERSECT_FIRST);
/*
* We expect pw_peer_caps to be fixed caps as we receive that from
* PipeWire. See pw_context_find_format() and SPA_PARAM_Format.
* possible_caps can be non-fixated caps based on what is downstream
* in the pipeline.
*
* The intersection result above might give us non-fixated caps. A
* possible scenario for this is the below pipeline.
* pipewiresrc ! audioconvert ! audio/x-raw,rate=44100,channels=2 ! ..
*
* So we fixate the caps explicitly here.
*/
pwsrc->caps = gst_caps_fixate (pwsrc->caps);
gst_caps_maybe_fixate_dma_format (pwsrc->caps);
}
if (pwsrc->caps && gst_caps_is_fixed (pwsrc->caps)) {
if (pwsrc->caps) {
g_return_if_fail (gst_caps_is_fixed (pwsrc->caps));
pwsrc->negotiated = TRUE;
structure = gst_caps_get_structure (pwsrc->caps, 0);
@ -1127,14 +1147,15 @@ handle_format_change (GstPipeWireSrc *pwsrc,
#ifdef HAVE_GSTREAMER_DMA_DRM
}
#endif
} else {
/* Don't provide bufferpool for audio if not requested by the
* application/user */
if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES)
pwsrc->use_bufferpool = USE_BUFFERPOOL_NO;
}
} else {
pwsrc->negotiated = FALSE;
pwsrc->is_video = FALSE;
/* Don't provide bufferpool for audio if not requested by the application/user */
if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES)
pwsrc->use_bufferpool = USE_BUFFERPOOL_NO;
}
if (pwsrc->caps) {
@ -1158,7 +1179,7 @@ handle_format_change (GstPipeWireSrc *pwsrc,
pwsrc->min_buffers,
pwsrc->max_buffers),
SPA_PARAM_BUFFERS_blocks, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX),
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX),
SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffertypes));

View file

@ -620,7 +620,7 @@ if build_module_raop
endif
summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules')
roc_dep = dependency('roc', version: '>= 0.3.0', required: get_option('roc'))
roc_dep = dependency('roc', version: '>= 0.4.0', required: get_option('roc'))
summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons')
pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source',

View file

@ -481,15 +481,16 @@ static int client_node_command(void *_data, const struct spa_command *command)
pw_proxy_error(proxy, res, "suspend failed");
}
break;
case SPA_NODE_COMMAND_RequestProcess:
res = pw_impl_node_send_command(node, command);
break;
default:
pw_log_warn("unhandled node command %d (%s)", id,
spa_debug_type_find_name(spa_type_node_command_id, id));
res = -ENOTSUP;
pw_proxy_errorf(proxy, res, "command %d (%s) not supported", id,
spa_debug_type_find_name(spa_type_node_command_id, id));
res = pw_impl_node_send_command(node, command);
if (res < 0) {
pw_log_warn("node command %d (%s) error: %s (%d)", id,
spa_debug_type_find_name(spa_type_node_command_id, id),
spa_strerror(res), res);
pw_proxy_errorf(proxy, res, "command %d (%s) error: %s (%d)", id,
spa_debug_type_find_name(spa_type_node_command_id, id),
spa_strerror(res), res);
}
}
return res;
}

View file

@ -772,7 +772,7 @@ static int make_stream_ports(struct stream *s)
break;
case ffado_stream_type_midi:
props = pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit raw UMP",
PW_KEY_FORMAT_DSP, "8 bit raw midi",
PW_KEY_PORT_NAME, port->name,
PW_KEY_PORT_PHYSICAL, "true",
PW_KEY_PORT_TERMINAL, "true",

View file

@ -972,7 +972,7 @@ static void param_tag_changed(struct impl *impl, const struct spa_pod *param)
pw_stream_update_params(impl->playback, params, 1);
}
static void state_changed(void *data, enum pw_stream_state old,
static void capture_state_changed(void *data, enum pw_stream_state old,
enum pw_stream_state state, const char *error)
{
struct impl *impl = data;
@ -1048,7 +1048,9 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param,
struct spa_audio_info_raw info;
spa_zero(info);
if (param == NULL) {
spa_filter_graph_deactivate(graph);
pw_log_info("module %p: filter deactivate", impl);
if (capture)
spa_filter_graph_deactivate(graph);
impl->rate = 0;
} else {
if ((res = spa_format_audio_raw_parse(param, &info)) < 0)
@ -1087,7 +1089,7 @@ static const struct pw_stream_events in_stream_events = {
.destroy = capture_destroy,
.process = capture_process,
.io_changed = io_changed,
.state_changed = state_changed,
.state_changed = capture_state_changed,
.param_changed = capture_param_changed
};
@ -1108,7 +1110,6 @@ static const struct pw_stream_events out_stream_events = {
.destroy = playback_destroy,
.process = playback_process,
.io_changed = io_changed,
.state_changed = state_changed,
.param_changed = playback_param_changed,
};
@ -1153,10 +1154,11 @@ static int setup_streams(struct impl *impl)
SPA_PARAM_EnumFormat, &impl->capture_info);
for (i = 0;; i++) {
if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL)
*offs = b.b.state.offset;
uint32_t save = b.b.state.offset;
if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1)
break;
if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL)
*offs = save;
}
if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL)

View file

@ -247,6 +247,8 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s
struct spa_pod_sequence *seq;
struct spa_pod_control *c;
int res;
uint8_t tmp[n_samples * 4];
size_t tmp_size = 0;
jack.midi_clear_buffer(dst);
if (src == NULL)
@ -260,23 +262,32 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s
seq = (struct spa_pod_sequence*)pod;
SPA_POD_SEQUENCE_FOREACH(seq, c) {
uint8_t data[16];
int size;
if (c->type != SPA_CONTROL_UMP)
continue;
switch (c->type) {
case SPA_CONTROL_UMP:
size = spa_ump_to_midi(SPA_POD_BODY(&c->value),
SPA_POD_BODY_SIZE(&c->value), &tmp[tmp_size], sizeof(tmp) - tmp_size);
if (size <= 0)
continue;
tmp_size += size;
break;
case SPA_CONTROL_Midi:
tmp_size = SPA_POD_BODY_SIZE(&c->value);
memcpy(tmp, SPA_POD_BODY(&c->value), SPA_MIN(sizeof(tmp), tmp_size));
break;
}
size = spa_ump_to_midi(SPA_POD_BODY(&c->value),
SPA_POD_BODY_SIZE(&c->value), data, sizeof(data));
if (size <= 0)
continue;
if (impl->fix_midi)
fix_midi_event(data, size);
if ((res = jack.midi_event_write(dst, c->offset, data, size)) < 0)
pw_log_warn("midi %p: can't write event: %s", dst,
spa_strerror(res));
if (tmp[0] != 0xf0 || tmp[tmp_size-1] == 0xf7) {
if (impl->fix_midi)
fix_midi_event(tmp, tmp_size);
if ((res = jack.midi_event_write(dst, c->offset, tmp, tmp_size)) < 0)
pw_log_warn("midi %p: can't write event: %s", dst,
spa_strerror(res));
tmp_size = 0;
}
}
}
@ -503,7 +514,7 @@ static void make_stream_ports(struct stream *s)
} else {
snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels);
props = pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit raw UMP",
PW_KEY_FORMAT_DSP, "8 bit raw midi",
PW_KEY_PORT_NAME, name,
PW_KEY_PORT_PHYSICAL, "true",
NULL);

View file

@ -45,7 +45,26 @@
/** \page page_module_netjack2_driver Netjack2 driver
*
* The netjack2-driver module provides a source or sink that is following a
* netjack2 manager.
* netjack2 manager. It is meant to be used over stable (ethernet) network
* connections with minimal latency and jitter.
*
* The driver normally decides how many ports it will send and receive from the
* manager. By default however, these values are set to -1 so that the manager
* decides on the number of ports.
*
* With the global or per stream audio.port and midi.ports properties this
* behaviour can be adjusted.
*
* The driver will send out UDP messages on a (typically) multicast address to
* inform the manager of the available driver. This will then instruct the manager
* to configure and start the driver.
*
* On the driver side, a sink and/or source with the specified numner of audio and
* midi ports will be created. On the manager side there will be a corresponding
* source and/or sink created respectively.
*
* The driver will be scheduled with exactly the same period as the manager but with
* a configurable number of periods of delay (see netjack2.latency, default 2).
*
* ## Module Name
*
@ -53,7 +72,10 @@
*
* ## Module Options
*
* - `driver.mode`: the driver mode, sink|source|duplex, default duplex
* - `driver.mode`: the driver mode, sink|source|duplex, default duplex. This set the
* per stream audio.port and midi.ports default from -1 to 0. sink mode defaults to
* no source ports, source mode to no sink ports and duplex leaves the defaults as
* they are.
* - `local.ifname = <str>`: interface name to use
* - `net.ip =<str>`: multicast IP address, default "225.3.19.154"
* - `net.port =<int>`: control port, default 19000
@ -63,11 +85,11 @@
* - `source.ip =<str>`: IP address to bind to, default "0.0.0.0"
* - `source.port =<int>`: port to bind to, default 0 (allocate)
* - `netjack2.client-name`: the name of the NETJACK2 client.
* - `netjack2.save`: if jack port connections should be save automatically. Can also be
* placed per stream.
* - `netjack2.latency`: the latency in cycles, default 2
* - `audio.channels`: the number of audio ports. Can also be added to the stream props.
* - `audio.ports`: the number of audio ports. Can also be added to the stream props.
* A value of -1 will configure to the number of audio ports on the manager.
* - `midi.ports`: the number of midi ports. Can also be added to the stream props.
* A value of -1 will configure to the number of midi ports on the manager.
* - `source.props`: Extra properties for the source filter.
* - `sink.props`: Extra properties for the sink filter.
*
@ -93,15 +115,14 @@
* context.modules = [
* { name = libpipewire-module-netjack2-driver
* args = {
* #driver.mode = duplex
* #netjack2.client-name = PipeWire
* #netjack2.save = false
* #netjack2.latency = 2
* #midi.ports = 0
* #audio.ports = -1
* #audio.channels = 2
* #audio.position = [ FL FR ]
* source.props = {
* # extra sink properties
* # extra source properties
* }
* sink.props = {
* # extra sink properties
@ -133,15 +154,14 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
#define NETWORK_MAX_LATENCY 30
#define DEFAULT_CLIENT_NAME "PipeWire"
#define DEFAULT_CHANNELS 2
#define DEFAULT_POSITION "[ FL FR ]"
#define DEFAULT_MIDI_PORTS 1
#define DEFAULT_MIDI_PORTS -1
#define DEFAULT_AUDIO_PORTS -1
#define FOLLOWER_INIT_TIMEOUT 1
#define FOLLOWER_INIT_RETRY -1
#define MODULE_USAGE "( remote.name=<remote> ) " \
"( driver.mode=<sink|source|duplex> ) " \
"( driver.mode=<sink|source|duplex> ) " \
"( local.ifname=<interface name> ) " \
"( net.ip=<ip address to use, default 225.3.19.154> ) " \
"( net.port=<port to use, default 19000> ) " \
@ -151,11 +171,11 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
"( source.ip=<ip address to bind, default 0.0.0.0> ) " \
"( source.port=<port to bind, default 0> ) " \
"( netjack2.client-name=<name of the NETJACK2 client> ) " \
"( netjack2.save=<bool, save ports> ) " \
"( netjack2.latency=<latency in cycles, default 2> ) " \
"( midi.ports=<number of midi ports> ) " \
"( audio.channels=<number of channels> ) " \
"( audio.position=<channel map> ) " \
"( audio.ports=<number of midi ports, default -1> ) " \
"( midi.ports=<number of midi ports, default -1> ) " \
"( audio.channels=<number of channels, default 0> ) " \
"( audio.position=<channel map, default null> ) " \
"( source.props=<properties> ) " \
"( sink.props=<properties> ) "
@ -182,9 +202,13 @@ struct stream {
struct pw_filter *filter;
struct spa_hook listener;
int32_t wanted_n_midi;
int32_t wanted_n_audio;
struct spa_io_position *position;
struct spa_audio_info_raw info;
uint32_t n_midi;
uint32_t n_ports;
struct port *ports[MAX_PORTS];
@ -222,8 +246,6 @@ struct impl {
struct spa_hook core_proxy_listener;
struct spa_hook core_listener;
struct spa_io_position *position;
struct stream source;
struct stream sink;
@ -369,11 +391,10 @@ static void source_process(void *d, struct spa_io_position *position)
static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size)
{
struct stream *s = data;
struct impl *impl = s->impl;
if (port_data == NULL) {
switch (id) {
case SPA_IO_Position:
impl->position = area;
s->position = area;
break;
default:
break;
@ -431,7 +452,7 @@ static void make_stream_ports(struct stream *s)
} else {
snprintf(name, sizeof(name), "midi%d", i - s->info.channels);
props = pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit raw UMP",
PW_KEY_FORMAT_DSP, "8 bit raw midi",
PW_KEY_AUDIO_CHANNEL, name,
PW_KEY_PORT_PHYSICAL, "true",
NULL);
@ -458,6 +479,7 @@ static void make_stream_ports(struct stream *s)
s->ports[i] = port;
}
pw_filter_set_active(s->filter, true);
}
static struct spa_pod *make_props_param(struct spa_pod_builder *b,
@ -523,7 +545,6 @@ static void stream_param_changed(void *data, void *port_data, uint32_t id,
case SPA_PARAM_PortConfig:
pw_log_debug("PortConfig");
make_stream_ports(s);
pw_filter_set_active(s->filter, true);
break;
case SPA_PARAM_Props:
pw_log_debug("Props");
@ -558,6 +579,7 @@ static int make_stream(struct stream *s, const char *name)
const struct spa_pod *params[4];
uint8_t buffer[1024];
struct spa_pod_builder b;
int res;
n_params = 0;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
@ -583,12 +605,18 @@ static int make_stream(struct stream *s, const char *name)
SPA_PARAM_Format, &s->info);
params[n_params++] = make_props_param(&b, &s->volume);
return pw_filter_connect(s->filter,
if ((res = pw_filter_connect(s->filter,
PW_FILTER_FLAG_INACTIVE |
PW_FILTER_FLAG_DRIVER |
PW_FILTER_FLAG_RT_PROCESS |
PW_FILTER_FLAG_CUSTOM_LATENCY,
params, n_params);
params, n_params)) < 0)
return res;
if (s->info.channels == 0)
make_stream_ports(s);
return res;
}
static int create_filters(struct impl *impl)
@ -616,6 +644,24 @@ static inline uint64_t get_time_nsec(struct impl *impl)
return nsec;
}
static void update_clock(struct impl *impl, struct stream *s, uint64_t nsec, uint32_t nframes)
{
if (s->position) {
struct spa_io_clock *c = &s->position->clock;
c->nsec = nsec;
c->rate = SPA_FRACTION(1, impl->samplerate);
c->position = impl->frame_time;
c->duration = nframes;
c->delay = 0;
c->rate_diff = 1.0;
c->next_nsec = nsec;
c->target_rate = c->rate;
c->target_duration = c->duration;
}
}
static void
on_data_io(void *data, int fd, uint32_t mask)
{
@ -648,27 +694,13 @@ on_data_io(void *data, int fd, uint32_t mask)
impl->frame_time += nframes;
pw_log_trace_fp("process %d %u %u %p %"PRIu64, nframes, source_running,
sink_running, impl->position, impl->frame_time);
pw_log_trace_fp("process %d %u %u %"PRIu64, nframes, source_running,
sink_running, impl->frame_time);
if (impl->new_xrun) {
pw_log_warn("Xrun netjack2:%u PipeWire:%u", impl->nj2_xrun, impl->pw_xrun);
impl->new_xrun = false;
}
if (impl->position) {
struct spa_io_clock *c = &impl->position->clock;
c->nsec = nsec;
c->rate = SPA_FRACTION(1, impl->samplerate);
c->position = impl->frame_time;
c->duration = nframes;
c->delay = 0;
c->rate_diff = 1.0;
c->next_nsec = nsec;
c->target_rate = c->rate;
c->target_duration = c->duration;
}
if (!source_running)
netjack2_recv_data(&impl->peer, NULL, 0, NULL, 0);
@ -676,12 +708,16 @@ on_data_io(void *data, int fd, uint32_t mask)
impl->done = false;
impl->triggered = true;
impl->driving = MODE_SOURCE;
pw_filter_trigger_process(impl->source.filter);
update_clock(impl, &impl->source, nsec, nframes);
if (pw_filter_trigger_process(impl->source.filter) < 0)
pw_log_warn("source not ready");
} else if (impl->mode == MODE_SINK && sink_running) {
impl->done = false;
impl->triggered = true;
impl->driving = MODE_SINK;
pw_filter_trigger_process(impl->sink.filter);
update_clock(impl, &impl->sink, nsec, nframes);
if (pw_filter_trigger_process(impl->sink.filter) < 0)
pw_log_warn("sink not ready");
} else {
sink_running = false;
impl->done = true;
@ -706,7 +742,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen)
static int make_socket(struct sockaddr_storage *src, socklen_t src_len,
struct sockaddr_storage *dst, socklen_t dst_len,
bool loop, int ttl, int dscp)
bool loop, int ttl, int dscp, const char *ifname)
{
int af, fd, val, res;
struct timeval timeout;
@ -722,7 +758,13 @@ static int make_socket(struct sockaddr_storage *src, socklen_t src_len,
pw_log_error("setsockopt failed: %m");
goto error;
}
#ifdef SO_BINDTODEVICE
if (ifname && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) {
res = -errno;
pw_log_error("setsockopt(SO_BINDTODEVICE) failed: %m");
goto error;
}
#endif
#ifdef SO_PRIORITY
val = 6;
if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0)
@ -789,13 +831,12 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p
int res;
struct netjack2_peer *peer = &impl->peer;
uint32_t i;
const char *media;
pw_log_info("got follower setup");
nj2_dump_session_params(params);
nj2_session_params_ntoh(&peer->params, params);
SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels);
SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels);
if (peer->params.send_audio_channels < 0 ||
peer->params.recv_audio_channels < 0 ||
@ -807,23 +848,35 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p
pw_log_warn("invalid follower setup");
return -EINVAL;
}
/* the params are from the perspective of the manager, so send is our
* receive (source) and recv is our send (sink) */
SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels);
SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels);
pw_loop_update_io(impl->main_loop, impl->setup_socket, 0);
impl->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels;
impl->source.info.rate = peer->params.sample_rate;
if ((uint32_t)peer->params.send_audio_channels != impl->source.info.channels) {
impl->source.info.channels = peer->params.send_audio_channels;
for (i = 0; i < SPA_MIN(impl->source.info.channels, SPA_AUDIO_MAX_CHANNELS); i++)
impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
impl->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels;
if (impl->sink.n_ports > MAX_PORTS) {
pw_log_warn("Too many follower sink ports %d > %d", impl->sink.n_ports, MAX_PORTS);
return -EINVAL;
}
impl->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels;
impl->sink.info.rate = peer->params.sample_rate;
if ((uint32_t)peer->params.recv_audio_channels != impl->sink.info.channels) {
impl->sink.info.channels = peer->params.recv_audio_channels;
for (i = 0; i < SPA_MIN(impl->sink.info.channels, SPA_AUDIO_MAX_CHANNELS); i++)
if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) {
impl->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS);
for (i = 0; i < impl->sink.info.channels; i++)
impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
}
impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels;
if (impl->source.n_ports > MAX_PORTS) {
pw_log_warn("Too many follower source ports %d > %d", impl->source.n_ports, MAX_PORTS);
return -EINVAL;
}
impl->source.info.rate = peer->params.sample_rate;
if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) {
impl->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS);
for (i = 0; i < impl->source.info.channels; i++)
impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
}
impl->samplerate = peer->params.sample_rate;
impl->period_size = peer->params.period_size;
@ -843,6 +896,20 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p
pw_properties_setf(impl->source.props, PW_KEY_NODE_FORCE_QUANTUM,
"%u", impl->period_size);
media = impl->sink.info.channels > 0 ? "Audio" : "Midi";
if (pw_properties_get(impl->sink.props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_setf(impl->sink.props, PW_KEY_MEDIA_CLASS, "%s/Sink", media);
media = impl->source.info.channels > 0 ? "Audio" : "Midi";
if (pw_properties_get(impl->source.props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_setf(impl->source.props, PW_KEY_MEDIA_CLASS, "%s/Source", media);
impl->mode = 0;
if (impl->source.n_ports > 0)
impl->mode |= MODE_SOURCE;
if (impl->sink.n_ports > 0)
impl->mode |= MODE_SINK;
if ((res = create_filters(impl)) < 0)
return res;
@ -942,10 +1009,12 @@ static int send_follower_available(struct impl *impl)
snprintf(params.follower_name, sizeof(params.follower_name), "%s", pw_get_host_name());
params.mtu = htonl(impl->mtu);
params.transport_sync = htonl(0);
params.send_audio_channels = htonl(-1);
params.recv_audio_channels = htonl(-1);
params.send_midi_channels = htonl(-1);
params.recv_midi_channels = htonl(-1);
/* send/recv is from the perspective of the manager, so what we send (sink)
* is recv on the manager and vice versa. */
params.recv_audio_channels = htonl(impl->sink.wanted_n_audio);
params.send_audio_channels = htonl(impl->source.wanted_n_audio);
params.recv_midi_channels = htonl(impl->sink.wanted_n_midi);
params.send_midi_channels = htonl(impl->source.wanted_n_midi);
params.sample_encoder = htonl(NJ2_ENCODER_FLOAT);
params.follower_sync_mode = htonl(1);
params.network_latency = htonl(impl->latency);
@ -982,9 +1051,11 @@ static int create_netjack2_socket(struct impl *impl)
impl->ttl = pw_properties_get_uint32(impl->props, "net.ttl", DEFAULT_NET_TTL);
impl->loop = pw_properties_get_bool(impl->props, "net.loop", DEFAULT_NET_LOOP);
impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP);
str = pw_properties_get(impl->props, "local.ifname");
fd = make_socket(&impl->src_addr, impl->src_len,
&impl->dst_addr, impl->dst_len, impl->loop, impl->ttl, impl->dscp);
&impl->dst_addr, impl->dst_len, impl->loop, impl->ttl, impl->dscp,
str);
if (fd < 0) {
res = -errno;
pw_log_error("can't create socket: %s", spa_strerror(res));
@ -1151,8 +1222,7 @@ static void parse_audio_info(const struct pw_properties *props, struct spa_audio
{
spa_audio_info_raw_init_dict_keys(info,
&SPA_DICT_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"),
SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)),
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")),
&props->dict,
SPA_KEY_AUDIO_CHANNELS,
SPA_KEY_AUDIO_POSITION, NULL);
@ -1220,20 +1290,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->sink.impl = impl;
impl->sink.direction = PW_DIRECTION_INPUT;
impl->mode = MODE_DUPLEX;
if ((str = pw_properties_get(props, "driver.mode")) != NULL) {
if (spa_streq(str, "source")) {
impl->mode = MODE_SOURCE;
pw_properties_set(impl->sink.props, "audio.ports", "0");
pw_properties_set(impl->sink.props, "midi.ports", "0");
} else if (spa_streq(str, "sink")) {
impl->mode = MODE_SINK;
} else if (spa_streq(str, "duplex")) {
impl->mode = MODE_DUPLEX;
} else {
pw_properties_set(impl->source.props, "audio.ports", "0");
pw_properties_set(impl->source.props, "midi.ports", "0");
} else if (!spa_streq(str, "duplex")) {
pw_log_error("invalid driver.mode '%s'", str);
res = -EINVAL;
goto error;
}
}
impl->latency = pw_properties_get_uint32(impl->props, "netjack2.latency",
DEFAULT_NETWORK_LATENCY);
@ -1245,11 +1315,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL)
pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true");
pw_properties_set(impl->sink.props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "40000");
pw_properties_set(impl->sink.props, PW_KEY_NODE_NAME, "netjack2_driver_send");
pw_properties_set(impl->source.props, PW_KEY_MEDIA_CLASS, "Audio/Source");
pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "40001");
pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "netjack2_driver_receive");
@ -1264,22 +1332,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS);
copy_props(impl, props, PW_KEY_NODE_GROUP);
copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
copy_props(impl, props, "midi.ports");
copy_props(impl, props, "audio.ports");
parse_audio_info(impl->source.props, &impl->source.info);
parse_audio_info(impl->sink.props, &impl->sink.info);
impl->source.n_midi = pw_properties_get_uint32(impl->source.props,
impl->source.wanted_n_midi = pw_properties_get_int32(impl->source.props,
"midi.ports", DEFAULT_MIDI_PORTS);
impl->sink.n_midi = pw_properties_get_uint32(impl->sink.props,
impl->sink.wanted_n_midi = pw_properties_get_int32(impl->sink.props,
"midi.ports", DEFAULT_MIDI_PORTS);
impl->source.n_ports = impl->source.n_midi + impl->source.info.channels;
impl->sink.n_ports = impl->sink.n_midi + impl->sink.info.channels;
if (impl->source.n_ports > MAX_PORTS || impl->sink.n_ports > MAX_PORTS) {
pw_log_error("too many ports");
res = -EINVAL;
goto error;
}
impl->source.wanted_n_audio = pw_properties_get_int32(impl->source.props,
"audio.ports", DEFAULT_AUDIO_PORTS);
impl->sink.wanted_n_audio = pw_properties_get_int32(impl->sink.props,
"audio.ports", DEFAULT_AUDIO_PORTS);
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
if (impl->core == NULL) {

View file

@ -49,6 +49,19 @@
* The netjack2 manager module listens for new netjack2 driver messages and will
* start a communication channel with them.
*
* Messages are received on a (typically) multicast address.
*
* Normally, the driver will specify the number of send and receive channels it
* wants to set up with the manager. If the driver however specifies a don't-care
* value of -1, the audio.ports and midi.ports configuration values of the manager
* are used.
*
* The manager will create the corresponding streams to send and receive data
* to/from the drivers. These are usually sink and sources but with the
* netjack2.connect property, these will be streams that will be autoconnected to
* the default source and sink by the session manager.
*
*
* ## Module Name
*
* `libpipewire-module-netjack2-manager`
@ -67,8 +80,11 @@
* - `netjack2.period-size`: the buffer size to use, default 1024
* - `netjack2.encoding`: the encoding, float|opus|int, default float
* - `netjack2.kbps`: the number of kilobits per second when encoding, default 64
* - `audio.channels`: the number of audio ports. Can also be added to the stream props.
* - `midi.ports`: the number of midi ports. Can also be added to the stream props.
* - `audio.ports`: the number of audio ports. Can also be added to the stream props. This
* is the default suggestion for drivers that don't specify any number of audio channels.
* - `midi.ports`: the number of midi ports. Can also be added to the stream props. This
* is the default suggestion for drivers that don't specify any number of midi channels.
* - `audio.position`: default channel position for the number of audio.ports.
* - `source.props`: Extra properties for the source filter.
* - `sink.props`: Extra properties for the sink filter.
*
@ -99,11 +115,12 @@
* #netjack2.period-size = 1024
* #netjack2.encoding = float # float|opus
* #netjack2.kbps = 64
* #audio.ports = 0
* #midi.ports = 0
* #audio.channels = 2
* #audio.position = [ FL FR ]
* source.props = {
* # extra sink properties
* # extra source properties
* }
* sink.props = {
* # extra sink properties
@ -137,8 +154,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
#define DEFAULT_PERIOD_SIZE 1024
#define DEFAULT_ENCODING "float"
#define DEFAULT_KBPS 64
#define DEFAULT_CHANNELS 2
#define DEFAULT_POSITION "[ FL FR ]"
#define DEFAULT_AUDIO_PORTS 2
#define DEFAULT_MIDI_PORTS 1
#define MODULE_USAGE "( remote.name=<remote> ) " \
@ -151,8 +167,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
"( netjack2.connect=<autoconnect ports, default false> ) " \
"( netjack2.sample-rate=<sampl erate, default 48000> ) "\
"( netjack2.period-size=<period size, default 1024> ) " \
"( midi.ports=<number of midi ports> ) " \
"( audio.channels=<number of channels> ) " \
"( midi.ports=<number of midi ports, default 1> ) " \
"( audio.channels=<number of channels, default 2> ) " \
"( audio.position=<channel map> ) " \
"( source.props=<properties> ) " \
"( sink.props=<properties> ) "
@ -183,9 +199,12 @@ struct stream {
struct pw_filter *filter;
struct spa_hook listener;
struct spa_audio_info_raw info;
struct spa_io_position *position;
struct spa_audio_info_raw info;
uint32_t n_audio;
uint32_t n_midi;
uint32_t n_ports;
struct port *ports[MAX_PORTS];
@ -202,7 +221,10 @@ struct follower {
struct spa_list link;
struct impl *impl;
struct spa_io_position *position;
#define MODE_SINK (1<<0)
#define MODE_SOURCE (1<<1)
#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE)
uint32_t mode;
struct stream source;
struct stream sink;
@ -227,6 +249,7 @@ struct follower {
unsigned int done:1;
unsigned int new_xrun:1;
unsigned int started:1;
unsigned int freeing:1;
};
struct impl {
@ -235,10 +258,6 @@ struct impl {
struct pw_loop *data_loop;
struct spa_system *system;
#define MODE_SINK (1<<0)
#define MODE_SOURCE (1<<1)
#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE)
uint32_t mode;
struct pw_properties *props;
struct pw_properties *sink_props;
struct pw_properties *source_props;
@ -284,6 +303,7 @@ static void stream_destroy(void *d)
struct stream *s = d;
uint32_t i;
s->running = false;
spa_hook_remove(&s->listener);
for (i = 0; i < s->n_ports; i++)
s->ports[i] = NULL;
@ -354,9 +374,17 @@ static void sink_process(void *d, struct spa_io_position *position)
pw_loop_update_io(s->impl->data_loop, follower->socket, SPA_IO_IN);
}
static void source_process(void *d, struct spa_io_position *position)
static int stop_follower(struct follower *follower);
static int do_stop_follower(struct spa_loop *loop,
bool async, uint32_t seq, const void *data, size_t size, void *user_data)
{
stop_follower(user_data);
return 0;
}
static inline void handle_source_process(struct stream *s, struct spa_io_position *position)
{
struct stream *s = d;
struct follower *follower = s->follower;
uint32_t nframes = position->clock.duration;
struct data_info midi[s->n_ports];
@ -365,28 +393,57 @@ static void source_process(void *d, struct spa_io_position *position)
set_info(s, nframes, midi, &n_midi, audio, &n_audio);
netjack2_manager_sync_wait(&follower->peer);
if (netjack2_manager_sync_wait(&follower->peer) < 0) {
pw_loop_invoke(s->impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower);
return;
}
netjack2_recv_data(&follower->peer, midi, n_midi, audio, n_audio);
}
static void source_process(void *d, struct spa_io_position *position)
{
struct stream *s = d;
struct follower *follower = s->follower;
if (!(follower->mode & MODE_SINK))
sink_process(&follower->sink, position);
handle_source_process(s, position);
}
static void follower_free(struct follower *follower)
{
struct impl *impl = follower->impl;
if (follower->freeing)
return;
follower->freeing = true;
spa_list_remove(&follower->link);
if (follower->source.filter)
if (follower->socket) {
pw_loop_destroy_source(impl->data_loop, follower->socket);
follower->socket = NULL;
}
if (follower->setup_socket) {
pw_loop_destroy_source(impl->main_loop, follower->setup_socket);
follower->setup_socket = NULL;
}
if (follower->source.filter) {
pw_filter_destroy(follower->source.filter);
if (follower->sink.filter)
follower->source.filter = NULL;
}
if (follower->sink.filter) {
pw_filter_destroy(follower->sink.filter);
follower->sink.filter = NULL;
}
pw_properties_free(follower->source.props);
follower->source.props = NULL;
pw_properties_free(follower->sink.props);
if (follower->socket)
pw_loop_destroy_source(impl->data_loop, follower->socket);
if (follower->setup_socket)
pw_loop_destroy_source(impl->main_loop, follower->setup_socket);
follower->sink.props = NULL;
netjack2_cleanup(&follower->peer);
free(follower);
@ -420,10 +477,13 @@ static void
on_setup_io(void *data, int fd, uint32_t mask)
{
struct follower *follower = data;
struct impl *impl = follower->impl;
if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
pw_log_warn("error:%08x", mask);
stop_follower(follower);
pw_loop_destroy_source(impl->main_loop, follower->setup_socket);
follower->setup_socket = NULL;
pw_loop_invoke(impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower);
return;
}
if (mask & SPA_IO_IN) {
@ -468,23 +528,32 @@ on_data_io(void *data, int fd, uint32_t mask)
pw_log_warn("error:%08x", mask);
pw_loop_destroy_source(impl->data_loop, follower->socket);
follower->socket = NULL;
pw_loop_invoke(impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower);
return;
}
if (mask & SPA_IO_IN) {
pw_loop_update_io(impl->data_loop, follower->socket, 0);
pw_filter_trigger_process(follower->source.filter);
if (follower->mode & MODE_SOURCE) {
if (pw_filter_trigger_process(follower->source.filter) < 0) {
pw_log_warn("source not ready");
handle_source_process(&follower->source, follower->source.position);
}
} else {
/* There is no source, handle the source receive side (without ports)
* with the sink position io */
handle_source_process(&follower->source, follower->sink.position);
}
}
}
static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size)
{
struct stream *s = data;
struct follower *follower = s->follower;
if (port_data == NULL) {
switch (id) {
case SPA_IO_Position:
follower->position = area;
s->position = area;
break;
default:
break;
@ -521,6 +590,9 @@ static void make_stream_ports(struct stream *s)
struct spa_latency_info latency;
const struct spa_pod *params[1];
if (s->ready)
return;
for (i = 0; i < s->n_ports; i++) {
struct port *port = s->ports[i];
if (port != NULL) {
@ -542,7 +614,7 @@ static void make_stream_ports(struct stream *s)
} else {
snprintf(name, sizeof(name), "midi%d", i - s->info.channels);
props = pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit raw UMP",
PW_KEY_FORMAT_DSP, "8 bit raw midi",
PW_KEY_PORT_PHYSICAL, "true",
PW_KEY_AUDIO_CHANNEL, name,
NULL);
@ -571,6 +643,9 @@ static void make_stream_ports(struct stream *s)
s->ports[i] = port;
}
s->ready = true;
if (s->follower->started)
pw_filter_set_active(s->filter, true);
}
static struct spa_pod *make_props_param(struct spa_pod_builder *b,
@ -636,9 +711,6 @@ static void stream_param_changed(void *data, void *port_data, uint32_t id,
case SPA_PARAM_PortConfig:
pw_log_debug("PortConfig");
make_stream_ports(s);
s->ready = true;
if (s->follower->started)
pw_filter_set_active(s->filter, true);
break;
case SPA_PARAM_Props:
pw_log_debug("Props");
@ -674,6 +746,7 @@ static int make_stream(struct stream *s, const char *name)
uint8_t buffer[1024];
struct spa_pod_builder b;
uint32_t flags;
int res;
n_params = 0;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
@ -693,7 +766,8 @@ static int make_stream(struct stream *s, const char *name)
} else {
pw_filter_add_listener(s->filter, &s->listener,
&source_events, s);
flags |= PW_FILTER_FLAG_TRIGGER;
if (s->follower->mode & MODE_SINK)
flags |= PW_FILTER_FLAG_TRIGGER;
}
reset_volume(&s->volume, s->info.channels);
@ -705,18 +779,23 @@ static int make_stream(struct stream *s, const char *name)
SPA_PARAM_Format, &s->info);
params[n_params++] = make_props_param(&b, &s->volume);
return pw_filter_connect(s->filter, flags, params, n_params);
if ((res = pw_filter_connect(s->filter, flags, params, n_params)) < 0)
return res;
if (s->info.channels == 0)
make_stream_ports(s);
return res;
}
static int create_filters(struct follower *follower)
{
struct impl *impl = follower->impl;
int res = 0;
if (impl->mode & MODE_SINK)
if (follower->mode & MODE_SINK)
res = make_stream(&follower->sink, "NETJACK2 Send");
if (impl->mode & MODE_SOURCE)
if (follower->mode & MODE_SOURCE)
res = make_stream(&follower->source, "NETJACK2 Receive");
return res;
@ -860,6 +939,8 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param
struct follower *follower;
char buffer[256];
struct netjack2_peer *peer;
uint32_t i;
const char *media;
pw_log_info("got follower available");
nj2_dump_session_params(params);
@ -891,6 +972,12 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param
parse_audio_info(follower->source.props, &follower->source.info);
parse_audio_info(follower->sink.props, &follower->sink.info);
follower->source.n_audio = pw_properties_get_uint32(follower->source.props,
"audio.ports", follower->source.info.channels ?
follower->source.info.channels : DEFAULT_AUDIO_PORTS);
follower->sink.n_audio = pw_properties_get_uint32(follower->sink.props,
"audio.ports", follower->sink.info.channels ?
follower->sink.info.channels : DEFAULT_AUDIO_PORTS);
follower->source.n_midi = pw_properties_get_uint32(follower->source.props,
"midi.ports", DEFAULT_MIDI_PORTS);
follower->sink.n_midi = pw_properties_get_uint32(follower->sink.props,
@ -925,29 +1012,64 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param
peer->params.sample_encoder = impl->encoding;
peer->params.kbps = impl->kbps;
/* params send and recv are from the manager point of view and reversed for the
* driver. So, for us send = sink and recv = source */
if (peer->params.send_audio_channels < 0)
peer->params.send_audio_channels = follower->sink.info.channels;
peer->params.send_audio_channels = follower->sink.n_audio;
if (peer->params.recv_audio_channels < 0)
peer->params.recv_audio_channels = follower->source.info.channels;
peer->params.recv_audio_channels = follower->source.n_audio;
if (peer->params.send_midi_channels < 0)
peer->params.send_midi_channels = follower->sink.n_midi;
if (peer->params.recv_midi_channels < 0)
peer->params.recv_midi_channels = follower->source.n_midi;
follower->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels;
follower->source.info.rate = peer->params.sample_rate;
follower->source.info.channels = peer->params.send_audio_channels;
follower->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels;
follower->sink.info.rate = peer->params.sample_rate;
follower->sink.info.channels = peer->params.recv_audio_channels;
follower->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels;
follower->source.info.rate = peer->params.sample_rate;
if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) {
follower->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS);
for (i = 0; i < follower->source.info.channels; i++)
follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
}
follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels;
follower->sink.info.rate = peer->params.sample_rate;
if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) {
follower->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS);
for (i = 0; i < follower->sink.info.channels; i++)
follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
}
follower->source.n_ports = follower->source.n_midi + follower->source.info.channels;
follower->sink.n_ports = follower->sink.n_midi + follower->sink.info.channels;
if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) {
pw_log_error("too many ports");
pw_log_error("too many ports source:%d sink:%d max:%d", follower->source.n_ports,
follower->sink.n_ports, MAX_PORTS);
res = -EINVAL;
goto cleanup;
}
media = follower->sink.info.channels > 0 ? "Audio" : "Midi";
if (pw_properties_get_bool(follower->sink.props, "netjack2.connect", DEFAULT_CONNECT)) {
if (pw_properties_get(follower->sink.props, PW_KEY_NODE_AUTOCONNECT) == NULL)
pw_properties_set(follower->sink.props, PW_KEY_NODE_AUTOCONNECT, "true");
if (pw_properties_get(follower->sink.props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_setf(follower->sink.props, PW_KEY_MEDIA_CLASS, "Stream/Input/%s", media);
} else {
if (pw_properties_get(follower->sink.props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_setf(follower->sink.props, PW_KEY_MEDIA_CLASS, "%s/Sink", media);
}
media = follower->source.info.channels > 0 ? "Audio" : "Midi";
if (pw_properties_get_bool(follower->source.props, "netjack2.connect", DEFAULT_CONNECT)) {
if (pw_properties_get(follower->source.props, PW_KEY_NODE_AUTOCONNECT) == NULL)
pw_properties_set(follower->source.props, PW_KEY_NODE_AUTOCONNECT, "true");
if (pw_properties_get(follower->source.props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_setf(follower->source.props, PW_KEY_MEDIA_CLASS, "Stream/Output/%s", media);
} else {
if (pw_properties_get(follower->source.props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_setf(follower->source.props, PW_KEY_MEDIA_CLASS, "%s/Source", media);
}
follower->mode = 0;
if (follower->sink.n_ports > 0)
follower->mode |= MODE_SINK;
if (follower->source.n_ports > 0)
follower->mode |= MODE_SOURCE;
if ((res = create_filters(follower)) < 0)
goto create_failed;
@ -1082,8 +1204,7 @@ static int create_netjack2_socket(struct impl *impl)
impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP);
str = pw_properties_get(impl->props, "local.ifname");
fd = make_announce_socket(&impl->src_addr, impl->src_len,
pw_properties_get(impl->props, "local.ifname"));
fd = make_announce_socket(&impl->src_addr, impl->src_len, str);
if (fd < 0) {
res = fd;
pw_log_error("can't create socket: %s", spa_strerror(res));
@ -1173,8 +1294,7 @@ static void parse_audio_info(const struct pw_properties *props, struct spa_audio
{
spa_audio_info_raw_init_dict_keys(info,
&SPA_DICT_ITEMS(
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"),
SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)),
SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")),
&props->dict,
SPA_KEY_AUDIO_CHANNELS,
SPA_KEY_AUDIO_POSITION, NULL);
@ -1238,20 +1358,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->main_loop = pw_context_get_main_loop(context);
impl->system = impl->main_loop->system;
impl->mode = MODE_DUPLEX;
if ((str = pw_properties_get(props, "tunnel.mode")) != NULL) {
if (spa_streq(str, "source")) {
impl->mode = MODE_SOURCE;
} else if (spa_streq(str, "sink")) {
impl->mode = MODE_SINK;
} else if (spa_streq(str, "duplex")) {
impl->mode = MODE_DUPLEX;
} else {
pw_log_error("invalid tunnel.mode '%s'", str);
res = -EINVAL;
goto error;
}
}
impl->samplerate = pw_properties_get_uint32(impl->props, "netjack2.sample-rate",
DEFAULT_SAMPLE_RATE);
impl->period_size = pw_properties_get_uint32(impl->props, "netjack2.period-size",
@ -1303,33 +1409,17 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
copy_props(impl, props, PW_KEY_NODE_LOOP_NAME);
copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
copy_props(impl, props, PW_KEY_NODE_NETWORK);
copy_props(impl, props, PW_KEY_NODE_GROUP);
copy_props(impl, props, PW_KEY_NODE_LINK_GROUP);
copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS);
copy_props(impl, props, PW_KEY_NODE_LOCK_QUANTUM);
copy_props(impl, props, PW_KEY_NODE_LOCK_RATE);
copy_props(impl, props, PW_KEY_AUDIO_CHANNELS);
copy_props(impl, props, SPA_KEY_AUDIO_POSITION);
copy_props(impl, props, "audio.ports");
copy_props(impl, props, "midi.ports");
copy_props(impl, props, "netjack2.connect");
if (pw_properties_get_bool(impl->sink_props, "netjack2.connect", DEFAULT_CONNECT)) {
if (pw_properties_get(impl->sink_props, PW_KEY_NODE_AUTOCONNECT) == NULL)
pw_properties_set(impl->sink_props, PW_KEY_NODE_AUTOCONNECT, "true");
if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Stream/Input/Audio");
} else {
if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
}
if (pw_properties_get_bool(impl->source_props, "netjack2.connect", DEFAULT_CONNECT)) {
if (pw_properties_get(impl->source_props, PW_KEY_NODE_AUTOCONNECT) == NULL)
pw_properties_set(impl->source_props, PW_KEY_NODE_AUTOCONNECT, "true");
if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Stream/Output/Audio");
} else {
if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source");
}
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
if (impl->core == NULL) {
str = pw_properties_get(props, PW_KEY_REMOTE_NAME);

View file

@ -120,7 +120,7 @@ struct nj2_packet_header {
uint32_t cycle; /* process cycle counter */
uint32_t sub_cycle; /* midi/audio subcycle counter */
int32_t frames; /* process cycle size in frames (can be -1 to indicate entire buffer) */
uint32_t is_last; /* is it the last packet of a given cycle ('y' or 'n') */
uint32_t is_last; /* is it the last packet of a given cycle (1=yes or 0=no) */
} __attribute__ ((packed));
#define UDP_HEADER_SIZE 64 /* 40 bytes for IP header in IPV6, 20 in IPV4, 8 for UDP, so take 64 */

View file

@ -242,17 +242,78 @@ static inline void fix_midi_event(uint8_t *data, size_t size)
}
}
static inline void *n2j_midi_buffer_reserve(struct nj2_midi_buffer *buf,
uint32_t offset, uint32_t size)
{
struct nj2_midi_event *ev;
void *ptr;
if (size <= 0)
return NULL;
size_t used_size = sizeof(*buf) + buf->write_pos +
((buf->event_count + 1) * sizeof(struct nj2_midi_event));
ev = &buf->event[buf->event_count];
ev->time = offset;
ev->size = size;
if (size <= MIDI_INLINE_MAX) {
ptr = ev->buffer;
} else {
if (used_size + size > buf->buffer_size)
return NULL;
buf->write_pos += size;
ev->offset = buf->buffer_size - buf->write_pos;
ptr = SPA_PTROFF(buf, ev->offset, void);
}
buf->event_count++;
return ptr;
}
static inline void n2j_midi_buffer_write(struct nj2_midi_buffer *buf,
uint32_t offset, void *data, uint32_t size)
{
void *ptr = n2j_midi_buffer_reserve(buf, offset, size);
if (ptr != NULL)
memcpy(ptr, data, size);
else
buf->lost_events++;
}
static inline void n2j_midi_buffer_append(struct nj2_midi_buffer *buf,
void *data, uint32_t size)
{
struct nj2_midi_event *ev;
uint32_t old_size;
uint8_t *old_ptr, *new_ptr;
ev = &buf->event[--buf->event_count];
old_size = ev->size;
if (old_size <= MIDI_INLINE_MAX) {
old_ptr = ev->buffer;
} else {
buf->write_pos -= old_size;
old_ptr = SPA_PTROFF(buf, ev->offset, void);
}
new_ptr = n2j_midi_buffer_reserve(buf, ev->time, old_size + size);
if (new_ptr == NULL) {
buf->lost_events++;
} else {
memmove(new_ptr, old_ptr, old_size);
memcpy(new_ptr+old_size, data, size);
}
}
static void midi_to_netjack2(struct netjack2_peer *peer,
struct nj2_midi_buffer *buf, float *src, uint32_t n_samples)
{
struct spa_pod *pod;
struct spa_pod_sequence *seq;
struct spa_pod_control *c;
struct nj2_midi_event *ev;
int free_size;
bool in_sysex = false;
buf->magic = MIDI_BUFFER_MAGIC;
buf->buffer_size = peer->quantum_limit * sizeof(float);
buf->buffer_size = peer->params.period_size * sizeof(float);
buf->nframes = n_samples;
buf->write_pos = 0;
buf->event_count = 0;
@ -269,12 +330,10 @@ static void midi_to_netjack2(struct netjack2_peer *peer,
seq = (struct spa_pod_sequence*)pod;
free_size = buf->buffer_size - sizeof(*buf);
SPA_POD_SEQUENCE_FOREACH(seq, c) {
int size;
uint8_t data[16];
void *ptr;
bool was_sysex = in_sysex;
if (c->type != SPA_CONTROL_UMP)
continue;
@ -284,29 +343,24 @@ static void midi_to_netjack2(struct netjack2_peer *peer,
if (size <= 0)
continue;
if (c->offset >= n_samples ||
size >= free_size) {
if (c->offset >= n_samples) {
buf->lost_events++;
continue;
}
if (peer->fix_midi)
if (!in_sysex && data[0] == 0xf0)
in_sysex = true;
if (!in_sysex && peer->fix_midi)
fix_midi_event(data, size);
ev = &buf->event[buf->event_count];
ev->time = c->offset;
ev->size = size;
if (size <= MIDI_INLINE_MAX) {
ptr = ev->buffer;
} else {
buf->write_pos += size;
ev->offset = buf->buffer_size - 1 - buf->write_pos;
free_size -= size;
ptr = SPA_PTROFF(buf, ev->offset, void);
}
memcpy(ptr, data, size);
buf->event_count++;
free_size -= sizeof(*ev);
if (in_sysex && data[size-1] == 0xf7)
in_sysex = false;
if (was_sysex)
n2j_midi_buffer_append(buf, data, size);
else
n2j_midi_buffer_write(buf, c->offset, data, size);
}
if (buf->write_pos > 0)
memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void),
@ -314,15 +368,27 @@ static void midi_to_netjack2(struct netjack2_peer *peer,
buf->write_pos);
}
static inline void netjack2_clear_midi(float *dst, uint32_t size)
{
struct spa_pod_builder b = { 0, };
struct spa_pod_frame f;
spa_pod_builder_init(&b, dst, size);
spa_pod_builder_push_sequence(&b, &f, 0);
spa_pod_builder_pop(&b, &f);
}
static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_buffer *buf)
{
struct spa_pod_builder b = { 0, };
uint32_t i;
struct spa_pod_frame f;
size_t offset = size - buf->write_pos - sizeof(*buf) -
(buf->event_count * sizeof(struct nj2_midi_event));
spa_pod_builder_init(&b, dst, size);
spa_pod_builder_push_sequence(&b, &f, 0);
for (i = 0; buf != NULL && i < buf->event_count; i++) {
for (i = 0; i < buf->event_count; i++) {
struct nj2_midi_event *ev = &buf->event[i];
uint8_t *data;
size_t s;
@ -330,8 +396,8 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b
if (ev->size <= MIDI_INLINE_MAX)
data = ev->buffer;
else if (ev->offset > buf->write_pos)
data = SPA_PTROFF(buf, ev->offset - buf->write_pos, void);
else if (ev->offset > offset)
data = SPA_PTROFF(buf, ev->offset - offset, void);
else
continue;
@ -339,9 +405,10 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b
while (s > 0) {
uint32_t ump[4];
int ump_size = spa_ump_from_midi(&data, &s, ump, sizeof(ump), 0, &state);
if (ump_size <= 0)
if (ump_size <= 0) {
pw_log_warn("invalid MIDI received: %s", spa_strerror(ump_size));
break;
}
spa_pod_builder_control(&b, ev->time, SPA_CONTROL_UMP);
spa_pod_builder_bytes(&b, ump, ump_size);
}
@ -362,7 +429,7 @@ static int netjack2_send_sync(struct netjack2_peer *peer, uint32_t nframes)
is_last = peer->params.send_midi_channels == 0 &&
peer->params.send_audio_channels == 0 ? 1 : 0;
strcpy(header.type, "header");
strncpy(header.type, "header", sizeof(header.type));
header.data_type = htonl('s');
header.data_stream = htonl(peer->our_stream);
header.id = htonl(peer->params.id);
@ -416,7 +483,7 @@ static int netjack2_send_midi(struct netjack2_peer *peer, uint32_t nframes,
max_size = peer->params.mtu - sizeof(header);
num_packets = (midi_size + max_size-1) / max_size;
strcpy(header.type, "header");
strncpy(header.type, "header", sizeof(header.type));
header.data_type = htonl('m');
header.data_stream = htonl(peer->our_stream);
header.id = htonl(peer->params.id);
@ -468,7 +535,7 @@ static int netjack2_send_float(struct netjack2_peer *peer, uint32_t nframes,
sub_period_bytes = sub_period_size * sizeof(float) + sizeof(int32_t);
num_packets = nframes / sub_period_size;
strcpy(header.type, "header");
strncpy(header.type, "header", sizeof(header.type));
header.data_type = htonl('a');
header.data_stream = htonl(peer->our_stream);
header.id = htonl(peer->params.id);
@ -543,7 +610,7 @@ static int netjack2_send_opus(struct netjack2_peer *peer, uint32_t nframes,
}
}
strcpy(header.type, "header");
strncpy(header.type, "header", sizeof(header.type));
header.data_type = htonl('a');
header.data_stream = htonl(peer->our_stream);
header.id = htonl(peer->params.id);
@ -611,7 +678,7 @@ static int netjack2_send_int(struct netjack2_peer *peer, uint32_t nframes,
memset(ap, 0, max_encoded);
}
strcpy(header.type, "header");
strncpy(header.type, "header", sizeof(header.type));
header.data_type = htonl('a');
header.data_stream = htonl(peer->our_stream);
header.id = htonl(peer->params.id);
@ -691,7 +758,7 @@ static inline int32_t netjack2_driver_sync_wait(struct netjack2_peer *peer)
receive_error:
pw_log_warn("recv error: %m");
return 0;
return -errno;
}
static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer)
@ -735,7 +802,7 @@ static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer)
receive_error:
pw_log_warn("recv error: %m");
return 0;
return -errno;
}
static int netjack2_recv_midi(struct netjack2_peer *peer, struct nj2_packet_header *header, uint32_t *count,
@ -1031,7 +1098,7 @@ static int netjack2_recv_data(struct netjack2_peer *peer,
}
for (i = 0; i < n_midi; i++) {
if (!midi[i].filled && midi[i].data != NULL)
netjack2_to_midi(midi[i].data, peer->params.period_size * sizeof(float), NULL);
netjack2_clear_midi(midi[i].data, peer->params.period_size * sizeof(float));
}
peer->sync.cycle = ntohl(header.cycle);
return 0;

View file

@ -1331,10 +1331,12 @@ static int rtsp_post_auth_setup_reply(void *data, int status, const struct spa_d
static int rtsp_do_post_auth_setup(struct impl *impl)
{
static const unsigned char content[33] =
"\x01"
"\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07"
"\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e";
static const uint8_t content[33] = {
0x01,
0x59, 0x02, 0xed, 0xe9, 0x0d, 0x4e, 0xf2, 0xbd,
0x4c, 0xb6, 0x8a, 0x63, 0x30, 0x03, 0x82, 0x07,
0xa9, 0x4d, 0xbd, 0x50, 0xd8, 0xaa, 0x46, 0x5b,
0x5d, 0x8c, 0x01, 0x2a, 0x0c, 0x7e, 0x1d, 0x4e };
return pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict,
"application/octet-stream", content, sizeof(content),

View file

@ -390,7 +390,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core,
res = -EINVAL;
goto out;
}
pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP");
pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
impl->stride = impl->format_info->size;
impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000);
if (impl->rate == 0)

View file

@ -307,7 +307,7 @@ struct vban_stream *vban_stream_new(struct pw_core *core,
res = -EINVAL;
goto out;
}
pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP");
pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
impl->stride = impl->format_info->size;
impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000);
if (impl->rate == 0)

View file

@ -1855,8 +1855,10 @@ void *pw_filter_add_port(struct pw_filter *filter,
add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_Midi);
else if (spa_streq(str, "8 bit raw control"))
add_control_dsp_port_params(impl, p, 0);
else if (spa_streq(str, "32 bit raw UMP"))
else if (spa_streq(str, "32 bit raw UMP")) {
add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_UMP);
pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
}
}
/* then override with user provided if any */
if (update_params(impl, p, SPA_ID_INVALID, params, n_params) < 0)

View file

@ -388,11 +388,10 @@ static int do_negotiate(struct pw_impl_link *this)
}
}
pw_log_pod(SPA_LOG_LEVEL_DEBUG, format);
SPA_POD_OBJECT_ID(format) = SPA_PARAM_Format;
pw_log_debug("%p: doing set format %p fixated:%d", this,
format, spa_pod_is_fixated(format));
pw_log_pod(SPA_LOG_LEVEL_INFO, format);
if (out_state == PW_IMPL_PORT_STATE_CONFIGURE) {
pw_log_debug("%p: doing set format on output", this);
@ -405,7 +404,7 @@ static int do_negotiate(struct pw_impl_link *this)
goto error;
}
if (SPA_RESULT_IS_ASYNC(res)) {
pw_log_info("output set format %d", res);
pw_log_debug("output set format %d", res);
busy_id = pw_work_queue_add(impl->work, &this->output_link,
spa_node_sync(output->node->node, res),
complete_ready, this);
@ -425,7 +424,7 @@ static int do_negotiate(struct pw_impl_link *this)
goto error;
}
if (SPA_RESULT_IS_ASYNC(res2)) {
pw_log_info("input set format %d", res2);
pw_log_debug("input set format %d", res2);
busy_id = pw_work_queue_add(impl->work, &this->input_link,
spa_node_sync(input->node->node, res2),
complete_ready, this);

View file

@ -660,19 +660,20 @@ static int node_send_command(void *object, const struct spa_command *command)
struct resource_data *data = object;
struct pw_impl_node *node = data->node;
uint32_t id = SPA_NODE_COMMAND_ID(command);
int res;
pw_log_debug("%p: got command %d (%s)", node, id,
spa_debug_type_find_name(spa_type_node_command_id, id));
switch (id) {
case SPA_NODE_COMMAND_Suspend:
suspend_node(node);
res = suspend_node(node);
break;
default:
spa_node_send_command(node->node, command);
res = spa_node_send_command(node->node, command);
break;
}
return 0;
return res;
}
static const struct pw_node_methods node_methods = {

View file

@ -141,7 +141,7 @@ pw_loop_add_signal(struct pw_loop *object, int signal_number,
PW_API_LOOP_IMPL void pw_loop_destroy_source(struct pw_loop *object,
struct spa_source *source)
{
return spa_loop_utils_destroy_source(object->utils, source);
spa_loop_utils_destroy_source(object->utils, source);
}
/**

View file

@ -446,7 +446,7 @@ static inline void call_process(struct stream *impl)
if (impl->n_buffers == 0 ||
(impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0))
return;
if (impl->rt_callbacks.funcs)
if (impl->rt_callbacks.funcs && !impl->disconnecting)
spa_callbacks_call_fast(&impl->rt_callbacks, struct pw_stream_events, process, 0);
}
@ -2053,7 +2053,7 @@ pw_stream_connect(struct pw_stream *stream,
pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, str);
else if (impl->media_type == SPA_MEDIA_TYPE_application &&
impl->media_subtype == SPA_MEDIA_SUBTYPE_control)
pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "32 bit raw UMP");
pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
if (pw_properties_get(impl->port_props, PW_KEY_PORT_GROUP) == NULL)
pw_properties_set(impl->port_props, PW_KEY_PORT_GROUP, "stream.0");

View file

@ -940,22 +940,58 @@ static int dump_event_ump(FILE *out, const struct midi_event *ev)
dump_mem(out, "Utility", ev->data, ev->size);
break;
case 0x1:
dump_mem(out, "SysRT", ev->data, ev->size);
{
uint8_t b[3] = { (d[0] >> 16) & 0x7f, (d[0] >> 8) & 0x7f, d[0] & 0x7f };
switch (b[0]) {
case 0xf1:
fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d",
b[1] >> 4, b[1] & 0xf);
break;
case 0xf2:
fprintf(out, "Song Position Pointer: value %d",
((int)b[2] << 7 | b[1]));
break;
case 0xf3:
fprintf(out, "Song Select: value %d", b[1]);
break;
case 0xf6:
fprintf(out, "Tune Request");
break;
case 0xf8:
fprintf(out, "Timing Clock");
break;
case 0xfa:
fprintf(out, "Start Sequence");
break;
case 0xfb:
fprintf(out, "Continue Sequence");
break;
case 0xfc:
fprintf(out, "Stop Sequence");
break;
case 0xfe:
fprintf(out, "Active Sensing");
break;
case 0xff:
fprintf(out, "System Reset");
break;
default:
dump_mem(out, "SysRT", ev->data, ev->size);
break;
}
break;
}
case 0x2:
{
struct midi_event ev1;
uint8_t msg[4];
uint8_t b[3] = { d[0] >> 16, d[0] >> 8, d[0] };
ev1 = *ev;
msg[0] = (d[0] >> 16);
msg[1] = (d[0] >> 8);
msg[2] = (d[0]);
if (msg[0] >= 0xc0 && msg[0] <= 0xdf)
if (b[0] >= 0xc0 && b[0] <= 0xdf)
ev1.size = 2;
else
ev1.size = 3;
ev1.data = msg;
ev1.data = b;
dump_event_midi1(out, &ev1);
break;
}

View file

@ -1343,7 +1343,7 @@ static void format_from_filename(SF_INFO *info, const char *filename)
}
}
if (format == -1)
format = SF_FORMAT_WAV;
format = spa_streq(filename, "-") ? SF_FORMAT_AU : SF_FORMAT_WAV;
if (format == SF_FORMAT_WAV && info->channels > 2)
format = SF_FORMAT_WAVEX;
@ -1436,6 +1436,21 @@ static int setup_encodedfile(struct data *data)
}
#endif
static const char *endianness_to_name(int format)
{
switch (format & SF_FORMAT_ENDMASK) {
case SF_ENDIAN_FILE:
return "Default Endian";
case SF_ENDIAN_LITTLE:
return "Little Endian";
case SF_ENDIAN_BIG:
return "Big Endian";
case SF_ENDIAN_CPU:
return "CPU Endian";
}
return "unknown";
}
static int setup_sndfile(struct data *data)
{
const struct format_info *fi = NULL;
@ -1473,9 +1488,21 @@ static int setup_sndfile(struct data *data)
return -EIO;
}
if (data->verbose)
fprintf(stderr, "sndfile: opened file \"%s\" format %08x channels:%d rate:%d\n",
data->filename, info.format, info.channels, info.samplerate);
if (data->verbose) {
SF_FORMAT_INFO ti, sti;
spa_zero(ti);
ti.format = info.format & SF_FORMAT_TYPEMASK;
if (sf_command(NULL, SFC_GET_FORMAT_INFO, &ti, sizeof(ti)) != 0)
ti.name = "unknown";
spa_zero(sti);
sti.format = info.format & SF_FORMAT_SUBMASK;
if (sf_command(NULL, SFC_GET_FORMAT_INFO, &sti, sizeof(sti)) != 0)
sti.name = "unknown";
fprintf(stderr, "sndfile: opened file \"%s\" format \"%s %s %s\" channels:%d rate:%d\n",
data->filename, endianness_to_name(info.format),
ti.name, sti.name, info.channels, info.samplerate);
}
if (data->channels > 0 && info.channels != (int)data->channels) {
fprintf(stderr, "sndfile: given channels (%u) don't match file channels (%d)\n",
data->channels, info.channels);
@ -1511,7 +1538,6 @@ static int setup_sndfile(struct data *data)
/* try native format first, else decode to float */
if ((fi = format_info_by_sf_format(info.format)) == NULL)
fi = format_info_by_sf_format(SF_FORMAT_FLOAT);
}
if (fi == NULL)
return -EIO;
@ -1959,7 +1985,7 @@ int main(int argc, char *argv[])
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "32 bit raw UMP");
pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
break;
case TYPE_DSD:
{

View file

@ -103,6 +103,7 @@ int main(int argc, char *argv[])
{ "group", required_argument, NULL, 'g' },
{ "name", required_argument, NULL, 'n' },
{ "channels", required_argument, NULL, 'c' },
{ "channel-map", required_argument, NULL, 'm' },
{ "latency", required_argument, NULL, 'l' },
{ "delay", required_argument, NULL, 'd' },
{ "capture", required_argument, NULL, 'C' },