When there is a stream without tx_latency enabled, the fill_count ends
with MIN_FILL value. This causes one buffer of silence to be written to
every stream before the actual data in each iteration.
Consequently, more data is written than consumed in each iteration.
After several iterations, spa_bt_send fails, triggering a
group_latency_check failure in few next iterations and leading to
dropped data.
Skip streams without tx_latency enabled in fill level calculations
to prevent these audio glitches.
Add a standalone tool that creates virtual AVB talker/listener endpoints
visible in the PipeWire graph (e.g. Helvum). Uses the loopback transport
so no AVB hardware or network access is needed.
The sink node consumes audio silently, the source produces silence.
Supports --milan flag for Milan v1.2 mode and --name for custom node
name prefix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add stream_setup_socket and stream_send ops to avb_transport_ops so the
stream data plane can use the same pluggable transport backend as the
control plane. Move the raw AF_PACKET socket setup from stream.c into
avdecc.c as raw_stream_setup_socket(), and add a raw_stream_send()
wrapper around sendmsg().
Add a stream list (spa_list) to struct server so streams can be iterated
after creation, and add stream_activate_virtual() for lightweight
activation without MRP/MAAP network operations.
Implement loopback stream ops: eventfd-based dummy sockets and no-op
send that discards audio data. This enables virtual AVB nodes that work
without network hardware or privileges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 10 Phase 6 tests for the AVTP audio data path:
- IEC61883 and AAF packet structure layout validation
- 802.1Q frame header construction
- PDU size calculations for various audio configurations
- Ringbuffer audio data round-trip integrity
- Ringbuffer wrap-around with multiple PDU-sized writes
- IEC61883 receive simulation (packet → ringbuffer)
- IEC61883 transmit PDU construction and field verification
- Ringbuffer overrun detection
- Sequence number and DBC counter wrapping
These tests validate the AVTP packet formats and audio data path
logic without requiring hardware, AF_PACKET sockets, or CLOCK_TAI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 12 Phase 5 tests for the AECP/AEM entity model:
- READ_DESCRIPTOR for existing and non-existent descriptors
- AECP packet filtering (wrong EtherType, wrong subtype)
- Unsupported AECP message types (ADDRESS_ACCESS, etc.)
- Unimplemented AEM commands (REBOOT, etc.)
- ACQUIRE_ENTITY and LOCK_ENTITY for legacy mode
- Milan ENTITY_AVAILABLE, LOCK_ENTITY (lock/contention/unlock)
- Milan LOCK_ENTITY for non-entity descriptors
- Milan ACQUIRE_ENTITY returns NOT_SUPPORTED
- Milan READ_DESCRIPTOR
Also adds Milan test server helper with properly sized entity
descriptor for lock state, and AECP/AEM packet builder utility.
Updates avb-bugs.md with 3 new bugs found (bugs #6-#8).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Phase 4 ACMP integration tests:
- NOT_SUPPORTED response for unimplemented commands
- CONNECT_TX_COMMAND with no streams (error response)
- Entity ID filtering (wrong GUID ignored)
- CONNECT_RX_COMMAND forwarding to talker
- Pending request timeout and retry
- Packet filtering (wrong EtherType/subtype)
Also add avb-bugs.md documenting all bugs found by the test suite.
Total: 24 tests, all passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a test suite for the AVB (Audio Video Bridging) protocol stack that
runs entirely in software, requiring no hardware, root privileges, or
running PipeWire daemon.
The loopback transport (avb-transport-loopback.h) replaces raw AF_PACKET
sockets with in-memory packet capture, using a synthetic MAC address and
eventfd for protocol handlers that need a valid fd.
Test utilities (test-avb-utils.h) provide helpers for creating test
servers, injecting packets, advancing time, and building ADP packets.
Tests cover:
- ADP entity available/departing/discover/timeout
- MRP attribute lifecycle (create, begin, join)
- Milan v1.2 mode server creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce struct avb_transport_ops vtable with setup/send_packet/
make_socket/destroy callbacks. The existing raw AF_PACKET socket code
becomes the default "raw" transport. avdecc_server_new() defaults to
avb_transport_raw if no transport is set, and avdecc_server_free()
delegates cleanup through the transport ops.
This enables alternative transports (e.g. loopback for testing) without
modifying protocol handler code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix two bugs in handle_cmd_lock_entity_milan_v12():
1. When server_find_descriptor() returns NULL, reply_status() was called
with the AEM packet pointer instead of the full ethernet frame,
corrupting the response ethernet header.
2. When refreshing an existing lock, the expire timeout was extended by
raw seconds (60) instead of nanoseconds (60 * SPA_NSEC_PER_SEC),
causing the lock to expire almost immediately after re-lock.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handle_acquire_entity_avb_legacy() and handle_lock_entity_avb_legacy()
incorrectly treated the full ethernet frame pointer as the AEM packet
pointer, causing p->payload to read descriptor_type and descriptor_id
from the wrong offset. Fix by properly skipping the ethernet header,
matching the pattern used by all other AEM command handlers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In handle_connect_tx_command() and handle_disconnect_tx_command(),
AVB_PACKET_ACMP_SET_MESSAGE_TYPE() is called after the goto done
target. When find_stream() fails and jumps to done, the response
is sent with the original command message type (e.g., CONNECT_TX_COMMAND)
instead of the correct response type (CONNECT_TX_RESPONSE).
Move the SET_MESSAGE_TYPE call before find_stream() so error responses
are always sent with the correct response message type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AVB_MRP_SEND_NEW was defined as 0, making it indistinguishable from
"no pending send" in the MSRP and MVRP event handlers which check
`if (!pending_send)`. This meant that when an attribute was first
declared (applicant state VN or AN), the NEW message was silently
dropped instead of being transmitted on the network.
Fix by shifting all AVB_MRP_SEND_* values to start at 1, so that 0
unambiguously means "no send pending". Update the MSRP and MVRP
encoders to subtract 1 when encoding to the IEEE 802.1Q wire format
(which uses 0-based event values).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The msrp_notify() and mvrp_notify() functions call dispatch table
notify callbacks without checking for NULL. In MSRP, the
TALKER_FAILED attribute type has a NULL notify callback, which would
crash if a talker-failed attribute received a registrar state change
notification (e.g. RX_NEW triggering NOTIFY_NEW).
Add NULL checks before calling the dispatch notify callbacks, matching
the defensive pattern used in the encode path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
server_add_descriptor() allocates the descriptor and its data in a
single calloc (d->ptr = SPA_PTROFF(d, sizeof(struct descriptor))),
so d->ptr points inside the same allocation as d. Calling free(d->ptr)
frees an interior pointer, corrupting the heap. Only free(d) is needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When we add a Format property after we dereffed all the other params in
the builder, we might relocate the builder memory and invalidate all
previously dereffed params, causing corruption.
Instead, first add all the params to the builder and then deref the
params.
There is a special case when we have both a capture and playback
stream. The capture stream will receive all filter params and the
playback stream will just receive its Format param.
Fixes#5202
The control values are only set in the port control_data after the
filter has been activated and the instances are created.
Property enumerations might happen before that and then we can either
return the current_value (when set in a control section or later with a
param property) or the default value.
Make a function to parse the passive mode and use that in ports and
nodes. Improve the node passive mode parsing a little.
Also make Duplex nodes follow-suspend.
If we pass a path /usr/libevil/mycode.so, it might have a prefix of
/usr/lib but we should still reject it. Do thi by checking that after
the prefix match, we start a new directory.
Integer overflows can result in map_range_init() to return wrong offset
or size that can result in access to invalid or unmapped memory.
Check for the overflows and return an EOVERFLOW error.
Found by Claude Code.
We might overflow the path buffer when we strcat the provided filename
into it, which might crash or cause unexpected behaviour.
Instead use spa_scnprintf which avoids overflow and properly truncates
and null-terminates the string.
Found by Claude Code.
Check that the number of fds for the message does not exceed the number
of received fds with SCM_RIGHTS.
The check was simply doing an array bounds check. This could still lead
to out-of-sync fds or usage of uninitialized/invalid fds when the
message header claims more fds than there were passed with SCM_RIGHTS.
Found by Claude Code.
spa_poll_event should have exactly same layout as epoll_events to be
compatible across platforms. The structure is packed only on x86-64.
Fix packing and replace the data member with similar union as
epoll_data, to fix compatibility on 32-bit etc.
This reverts commit bb0efd777f.
It is unclear what the problem was before this commit. If there are any
pending operations, the suspend should simply cancel them.
See #5207
FDK-AAC encoder uses band pass filter, which is automatically
applied at all bitrates.
For CBR encoding mode, its values are as follows (for stereo):
* 0-12 kb/s: 5 kHz
* 12-20 kb/s: 6.4 kHz
* 20-28 kb/s: 9.6 kHz
* 40-56 kb/s: 13 kHz
* 56-72 kb/s: 16 kHz
* 72-576 kb/s: 17 kHz
VBR uses the following table (stereo):
* Mode 1: 13 kHz
* Mode 2: 13 kHz
* Mode 3: 15.7 kHz
* Mode 4: 16.5 kHz
* Mode 5: 19.3 kHz
17 kHz for CBR is a limiting value for high bitrate.
Assume >110 kbit/s as a "high bitrate" CBR and increase the
band pass cutout up to 19.3 kHz (as in mode 5 VBR).
Link: d8e6b1a3aa/libAACenc/src/bandwidth.cpp (L114-L160)
This makes it the same size as epoll_event and we don't need to copy the
results over.
It however technically causes an ABI break, in case someone was using
the system interface directly.
Using connect() on a UDP receiver creates a strict filter based on
the sender's _source_ port, not the sender's destination port. The
source port specifies at what sender port the packet exits the sender.
The destination port specifies at what receiver port the packet enters
the receiver. But, the RTP sink uses an ephemeral (= random) port as the
source port. Consequently, connect() at the receiver will cause a
comparison of that ephemeral port with the fixated one (which is actually
the number of the _destination_ port). This incorrect filtering causes
all packets to be dropped.
Use bind() to filter for the local destination port, and use recvmsg()
with manual IP comparison to filter for the sender's identity.