Symmetric follow-up to 47a3479490 which fixed
the same leak in media-sink. media-source transport_start() registers
update_delay_event and dups the transport fd before ensuring SCO I/O; on
HFP failure the fail path only deinited codec_data, leaking the event
source and fd. Early allocation failures also returned without cleanup.
Mirror media-sink: destroy update_delay_event, close the dup'd fd, clear
the decode buffer, and route early errors through the same fail path.
After a6f529b5, opus_05_51/71 only set endpoint_companion. The owner
fill_caps still advertised up to MAX_CHANNELS, there was no combine_caps,
and validate_config was not id-gated. Disabled surround companions remained
visible in endpoint caps, and SetConfiguration could bind a 5.1/7.1 config
to the stereo opus_05 object (wrong bitrate limits / channel profile).
Mirror a2dp-codec-aac.c:
- split fill_caps per codec id (2ch / 5.1 / 7.1; duplex 2+2; pro wide)
- add combine_caps on endpoint owners
- id-gate validate_config so the config-aware resolver picks the companion
- give companions their own fill_caps for the monitor merge path
This is done by unsetting the link to jack internal objects to their
corresponding object.id, which is otherwise used when reacting to
registry events. Without this, serials would "leak" when multiple jack
objects have the same object id during lookup - the first serial would
be picked as jack_port_id even if it's removed and would reference its
node data instead of the new port.
Unsetting object.id also means we need to refactor internals to lookup
by serial instead of id.
See #5356 for more
Codecs that share an A2DP endpoint (e.g. aac + aac_eld) previously used
an asymmetric pattern: one codec (the "owner") had fill_caps and quietly
advertised the union of all siblings' capability bits; companions had
fill_caps = NULL.
This caused a bug: when aac_eld is absent from bluez5.codecs, the "aac"
endpoint still advertised the ELD object-type bit because a2dp_codec_aac's
fill_caps emitted it unconditionally (gated only by eld_supported(), never
by enabled_codecs). A remote could then select ELD; SetConfiguration
resolved the transport to a2dp_codec_aac (the only fill_caps holder for "aac")
Refactor to a symmetric, composable model:
- Add bool endpoint_companion to struct media_codec (default false = owner).
Companion codec objects set this flag instead of fill_caps = NULL.
- Add combine_caps() function to struct media_codec. Owners that share
an endpoint implement this to union two capability blobs of the same
codec_id.
- Each codec's fill_caps now describes only its own bits. AAC LC advertises
only LC object types; AAC ELD advertises only the ELD type (or returns
-ENOTSUP when FDK-AAC lacks ELD support).
- Bump SPA_VERSION_BLUEZ5_CODEC_MEDIA 16 -> 17.
On the monitor side (bluez5-dbus.c):
- New media_codec_fill_endpoint_caps() helper fills the owner's caps then
walks enabled companions on the same endpoint and merges each via
combine_caps. All five endpoint-registration fill_caps call sites are
routed through this helper.
- endpoint_should_be_registered() gates on !endpoint_companion (not on
fill_caps != NULL), so companions correctly skip endpoint registration.
- media_endpoint_to_codec() now filters by is_media_codec_enabled and
prefers owners as a tiebreaker.
- New media_endpoint_to_codec_for_config() resolves SetConfiguration by
calling each enabled candidate's validate_config against the negotiated
config bytes. This ensures that an "aac" endpoint carrying ELD bytes is
mapped to a2dp_codec_aac_eld (not a2dp_codec_aac).
- AAC validate_config and codec_init are id-gated so a stale ELD config
can never be decoded by the LC codec object.
Assisted-by: Claude Opus 4.7
PipeWire re-emits the registry global event for an existing object when the
client's permissions change. This can cause pw_metadata_add_listener() to be
called again on the same hook, corrupting the proxy's object_listener_list.
This is easily triggered by WirePlumber 0.5.15, whose reworked client access
logic sets portal permissions in several stages, causing the metadata global to
be announced more than once.
Bind the default metadata and add the listener only once, and tear down the
proxy and listener in global_remove so a genuine remove/re-add still works.
If the filter-graph can apply volume, it will set the softVolume to 1.0
so that the rest of the audioconvert will not apply softVolume anymore.
If it however not applies softVolume, it should pass the given
softVolume and softMute unmodified to be applied elsewhere.
The problem is that the filter-graph completely removed the softVolume
and softMute properties and in case the filter-graph was in use, this
resulted in the channelVolumes being applied in software. This then
could result in double volumes, once in hardware and another time in
software, causing sudden volume loss.
Fixes#5344
transport_start() creates update_delay_event before ensuring SCO I/O.
When spa_bt_transport_ensure_sco_io() fails, the fail path only cleans
up codec_data and leaks the event source on the loop.
Destroy update_delay_event in fail, matching do_remove_source().
A libcamera camera has a set of static properties found in `Camera::properties()`.
Some of the properties are already available for pipewire clients
(Model, Rotation, Location), most of them are not.
So serialize all properties into a strings and make them available on the
pipewire device. The keys have the form "api.libcamera.property.<vendor>.<name>"
and the values are intended to be valid json values, parsable by
python's `json.loads()` or qt's `QJsonValue::fromJson()`.
Controls of type "rectangle", "size", or "point" are not supported for now since
their json forms are not self-evident. They can be added when the need arises.
Add the function `spa_json_encode_stringn()` that works very similarly
to `spa_json_encode_string()`, with the difference that it works with
non-null terminated strings (e.g. `std::string_view`).
`spa_json_encode_string()` is then implemented with this new function.
Additionally, since the input string is no longer null terminated,
escape bytes of value 0. This is probably better that putting
it into the result.
handle_acp_poll() copies the poll revents into ACP and clears them, but
never actually looks at them. When one of the polled fds reports
POLLERR/POLLHUP - which happens when the ALSA card goes away, e.g. the
audio function of a USB dock being unplugged - the source is never
removed, and because that condition is level-triggered the loop just
keeps redispatching the handler.
The result is wireplumber pegged at 100% CPU, spinning on the card's
control fd:
read(24, ..., 72) = -1 ENODEV (No such device)
with fd 24 pointing at a now-deleted /dev/snd/controlC<n>. It stays like
that until the card comes back or the service is restarted.
Accumulate the returned mask and, if anything reports SPA_IO_ERR or
SPA_IO_HUP, drop the poll sources so we stop hammering a dead descriptor.
The udev monitor still drives the real device removal and setup_sources()
re-arms the poll if the card reappears - the same teardown alsa-pcm.c
already does for its own control sources.
Signed-off-by: Mike Lothian <mike@fireburn.co.uk>
Fix incorrect JSON iterator usage when parsing retransmissions and
max_transport_latency fields in BIS configuration.
The code incorrectly used it[2] iterator instead of it[1] when parsing
BIS object fields, causing undefined behavior and configuration values
being ignored.
Signed-off-by: liujiahe <liujiahe@uniontech.com>
* Add .dox file that describes the internal design of the RTP module.
* Add details to the sink module documentation about the meaning of
source.ip and how it interacts with local.ifname .
* Rename "constant delay mode" to "constant latency mode" comments in the
code to match the documentation.
* Minor comment fixes.
These expected pointer to variables instead of variables directly, which
is inconsistent with how the other atomic macros behave. Furthermore,
SPA_STORE_ONCE was plain broken, since the underlying operation expects
an additional argument that was not included.
Add a wait_negotiated() call to gst_pipewire_src_create(). This works
around a race-condition that we experience where the function is
called after the state is transitioning to paused, and after a
SPA_PARAM_Format with a NULL parameter has been passed. That event
is handled in the pipewire source by setting its negotiated flag to
False, which results in gst_pipewire_src_create() returning
GST_FLOW_NOT_NEGOTIATED, resulting in a failed stream attempt.
With this change, the stream survives the state change.
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
If a system does not have any Audio/Source node, clients that want to capture
audio from the default source should fail instead of capturing audio from a
monitor Audio/Sink node.
For Pro Audio devices, the description is the card name with "Pro" or
Pro 1", "Pro 2", etc. appended. The result is user interfaces showing
around half a dozen devices with descriptions that don't help
disambiguate devices in the slightest.
Append the underlying PCM names instead of the "Pro N" string, so that
devices can be recognised at a glance.
Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/work_items/3982
When a consumer (e.g. Firefox via xdg-desktop-portal) connects to a
pipewiresink mode=provide node and format negotiation fails, the error
reaches the stream as a transient proxy error. The comments in
proxy_error suggest the application should decide whether the error is
permanent.
Before this on_state_changed unconditionally made every proxy error
permanent by calling pw_stream_set_error, which posted
GST_ELEMENT_ERROR and killed the entire provider pipeline. A virtual
camera provider should survive consumer negotiation failures.
In mode=provide, log the error as a warning and continue. Other modes
retain the existing behavior.
To reproduce (with the previous pool fix applied):
Producers:
gst-launch-1.0 -v -e pipewiresrc path=<id> ! \
video/x-raw,width=1280,height=720,framerate=24/1 ! \
jpegenc ! rtpjpegpay ! rtpstreampay ! \
udpsink host=127.0.0.1 port=5000
gst-launch-1.0 -e \
udpsrc address=127.0.0.1 port=5000 ! queue ! \
application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay ! \
application/x-rtp,encoding-name=JPEG ! rtpjpegdepay ! \
decodebin ! videorate ! videoconvert ! \
pipewiresink mode=provide \
stream-properties="properties,media.class=Video/Source,media.role=Camera" \
client-name=VirtualCam
Then open Firefox and select VirtualCam as camera source, without this
fix the provider pipeline exits with an error, bringing down any other
clients streaming from it.
META_VideoCrop is present on every buffer negotiated through the
adapter, even when the producer never sets a meaningful crop
region. Before commit c634ef961, gst_buffer_get_video_crop_meta
returned NULL on new GstBuffers so the zero values were never
applied. After that commit, gst_buffer_add_video_crop_meta always
succeeds, and an invalid crop produces black frames.
Without this, the following produces black frames,
Producer:
gst-launch-1.0 videotestsrc is-live=true ! \
video/x-raw,format=I420,width=1280,height=720,framerate=24/1 ! \
pipewiresink mode=provide \
stream-properties="properties,media.class=Video/Source,media.role=Camera" \
client-name=VirtualCam
Consumer:
gst-launch-1.0 pipewiresrc path=<id> ! videoconvert ! autovideosink
Destroy the bound node proxies in probe() and stop() while the loop is
locked, before releasing the core. This removes the node listeners so no
node_event_info can fire after the core is gone, fixing the root cause of
the resync() NULL-deref crash and a node-proxy leak.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
basesrc will do caps negotiation if pad is reconfigured. Should check and
wait if caps negotiation is currently in progress before corking the stream.
Return true when flushing to avoid caps negotiation failure.
Make this more useful by adding the capture and playback latency.
This structure can be used to say how much latency there is between the
capture and playback hardware.
Make it possible to pass context to plugins and nodes in the
filter-chain.
We can use this to make filters aware of the graph clock or
latency, for example.
Make instantiate create all the related handles in one go.
When a processing node has 1 in/out, multiple handles are created to
support multichannel. By instantiating the handles in one go, the
implementation could do some special multichannel support or do some
optimizations, like parse the config string only once.
Instead of returning a handle (and errno on NULL), return a result code
and store the handle in a return variable.
This makes it easier to handle the errors but also makes it possible to
return multiple handles later.
A forced setup_filter_graphs() deactivates and re-instantiates every graph,
which frees and recreates the underlying plugin handles (node_cleanup sets
node->hndl[i] = NULL before re-instantiating). This was done on a graph that
was still referenced by the RT data-loop snapshot (filter_graph[]), so the
RT thread could run a graph whose handles were NULL mid-rebuild, leading to a
NULL handle dereference in the filter-graph process path.
Mirror the safe ordering already used by load_filter_graph()/clean_filter_handles():
before reconfiguring, mark the graphs not-setup and sync_filter_graph() so the
data loop drops them from filter_graph[] under the loop lock. They are
republished by the sync that follows setup. The cheap snapshot swap is done
under the lock; the heavy re-instantiation stays off the RT path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The drawio-exported SVGs used light-dark() CSS to switch text between
black and white based on color scheme. This caused white-on-light text
when viewing in dark mode. Replace all light-dark() values with their
light-mode equivalents so text and strokes are always black.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Escape <portname>, <tensorname>, <paramname>, <port>, <rules> and
similar angle-bracket placeholders that doxygen interprets as HTML
tags. Also escape @filename (unknown doxygen command) and fix the
pw_stream::process() reference in thread-loop.h.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- modules.dox: remove references to non-existent sendspin modules
- access.dox: remove reference to deprecated pipewire-media-session
- dma-buf.dox: fix \ref EnumFormat to \ref SPA_PARAM_EnumFormat,
fix \ref struct to struct \ref for spa_meta_sync_timeline
- pipewire.conf.5.md: add explicit {#synopsis} anchor for internal links
- pipewire-client.conf.5.md: fix audio_converter to audio_adapter ref
- pipewire-jack.conf.5.md: escape <id> HTML tags
- pipewire-props.7.md: fix monitor-prop__ to props__ for card profiles ref
- pipewire-pulse.1.md: fix pipewire-env ref to full anchor name
- pipewire.1.md: fix \ref CPU to \ref spa_cpu
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prefix all pulse module source files with pulse- to give them unique
basenames, avoiding ambiguous \file suffix matching in doxygen when
identically-named files exist under src/modules/.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>