update_delay is called primarily when the stream format or latency changes,
and from playback thread, if stream reports different delay as before.
This function calculates the number of compensate samples for each stream
based on the latencies of other streams (which must be in a streaming state).
During the first playback on a new format, update_delay is called multiple times
due to format or latency changes. The delay is calculated only from streams
that are currently streaming. If some streams are not yet streaming, their
latencies are ignored, and the delay is updated later in the processing
thread. The processing thread also stores the stream delay in a local variable
(accessed only from that thread, thus requiring no locking).
On a subsequent playback using the same format, update_delay is still called a
few times, and the delay is updated based on the currently streaming streams.
If some streams are not streaming, their latencies are ignored.
However, this time, the processing thread fails to update the delay for the
previously non-streaming streams. Because the format didn't change, the streams
delay matches the last stored delay from the previous playback. As a
result, the compensate samples are not recalculated.
To properly update the compensate samples, update_delay must also be called
when a stream's state changes to streaming (avoiding the need to clear the
thread-buffered value, which would require locking in the processing
thread).
Move the source offs, stride, data and size calculations out of the
destination loop. We only need to clamp the size to copy to the maxsize
of the destination buffer.
Add a monitor mode that creates an Audio/Source combining audio from the
monitor ports of all Audio/Sink nodes. This allows capturing everything
that is being played back across all sinks into a single source.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Memory Safety: Medium
In update_delay(), the delay compensation size is computed as
delay * sizeof(float) where delay is int64_t but size is uint32_t.
When the delay value is very large, the multiplication result
truncates to a small uint32_t value. This causes an undersized
buffer allocation in resize_delay(), while compensate_samples
retains the original large value. Subsequent use of
compensate_samples could then write past the end of the buffer.
A negative delay (possible if delay_samples overflows) would also
produce a large unsigned size due to implicit conversion.
Fix by clamping the delay to be non-negative and within the maximum
delay buffer size before the multiplication, ensuring the size
cannot truncate or wrap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a function that accepts the size of the position array when reading
the audio positions. This makes it possible to decouple the position
array size from SPA_AUDIO_MAX_CHANNELS.
Also use SPA_N_ELEMENTS to pass the number of array elements to
functions instead of a fixed constant. This makes it easier to change
the array size later to a different constant without having to patch up
all the places where the size is used.
When stream is paused, internal delay buffers were cleared, but some
data could stay in stream output queue. Without a flush, these data where
played in front of a new data.
Patch was inspired by 64d6ff4184 fixing the
same issue in a filter-chain module.
Signed-off-by: Martin Geier <martin.geier@streamunlimited.com>
Combine stream selects the biggest latency from all output streams and sent
the latency upstream. To select the biggest latency, each stream needs to have
the sample rate and the quantum size set.
The combine stream recalculates the latency in the latency changed callback
or during data processing.
Stream sets the sample rate and the quantum size in a copy_position call
which is normally called during processing the output data or when state
changes to streaming.
Before this change, it wasn't guarantee the copy_position was called for
each stream already and latency in the combine stream was selected from
random stream.
Signed-off-by: Martin Geier <martin.geier@streamunlimited.com>
config.h needs to be consistently included before any standard headers
if we ever want to set feature test macros (like _GNU_SOURCE or whatever)
inside. It can lead to hard-to-debug issues without that.
It can also be problematic just for our own HAVE_* that it may define
if it's not consistently made available before our own headers. Just
always include it first, before everything.
We already did this in many files, just not consistently.
When we simply need to change some state for the code executed in the
loop, we can use locked() instead of invoke(). This is more efficient
and avoids some context switches in the normal case.
When combine-stream initiation code was moved around in b46673b4, a
bitwise or of flags was accidentally dropped, and thus flags were
overwritten instead of added to.
Make a function that can initialize raw audio info from a dict and fill
in the defaults. We can use this in many of the modules when the audio
format is parsed.
Use the helper instead of duplicating the same code.
Also add some helpers to parse a json array of uint32_t
Move some functions to convert between type name and id.
Add spa_json_begin_array/object to replace
spa_json_init+spa_json_begin_array/object
This function is better because it does not waste a useless spa_json
structure as an iterator. The relaxed versions also error out when the
container is mismatched because parsing a mismatched container is not
going to give any results anyway.
When the combine-stream module is used as a source and the input streams
are overlapping, mix the samples instead of overwriting the previous
samples.
See #3710
Expose the acquire_loop/release_loop functions and use them in the
modules.
Make sure the nodes created from the module use the same data loop as
the module. We need to ensure this because otherwise, the nodes might
be scheduled on different data loops and the invoke or timer logic will
fail.
When resample.disabled=true, which is now the default, Format has zero
rate, so latency buffers get zero size. The rate in this case is the
graph rate.
Fix by just using the delay in samples, as all streams must in any case
run at same rate for the combining to work.
Fixes: bff252ce60 ("combine-stream: actually make use of resample.disable")
resample.disable was made to default to true, but copying it to stream
properties was forgotten so it didn't have any effect. Make sure to copy
it.
This will also prevent different input/output streams from negotiating
to different rates, which would result to broken audio since we are just
passing sample data through.
Instead of just following static target match rules to create output streams,
this feature allows the user to dynamically create more output streams
with custom targets using metadata.
When we don't set a rate, assume both input and output streams are
following the graph rate and so disable the resampler.
This mostly works around an issue where the input and output could
negotiate to different rates in some cases. With the resampler disabled
this would still result in the same amount of samples going in as
comming out instead of a stuttering mismatch.
See #2969
Use separate flag for indicating if pw_stream_destroy is needed.
Don't set s->stream = NULL to indicate that, it will race with data
loop. Setting to null separately is not needed, removing from the stream
list is enough.
Add latencyOffsetNsec prop to the combine node.
This is mainly useful for BAP device sets; the property appears in
Pulseaudio UI only when the node is associated with a device.