Skip to content

Route release pops a stale descriptor: AudioVideo adds two RouteDescriptors, RemoveRouteDescriptor removes one #1494

Description

@ediablo80

Summary

An AudioVideo route adds two RouteDescriptors to RouteDescriptorCollection.DefaultCollection — one Audio, one Video — but RemoveRouteDescriptor removes only one. Every route change to an AudioVideo destination therefore orphans a descriptor, and each subsequent release pops a progressively older one.

The consequences are that a destination's release acts on a stale route, its output ports' InUseTracker counts never return to zero, and routed-source feedback derived from the collection lags reality.

Observed on v3.0.0-rc.6, CP4N.

Mechanism

RunRouteRequest builds the pair (Extensions.cs#L240-L249):

var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Audio);
...
var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Video);

AddRouteDescriptor dedups on (Source, Destination, SignalType, InputPort), so both are stored (RouteDescriptorCollection.cs#L55-L60).

But the lookup used on release ignores SignalType entirely and returns the first match (RouteDescriptorCollection.cs#L89):

return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);

RemoveRouteDescriptor removes that single descriptor, and ReleaseRouteInternal releases only it. Since RouteDescriptors is a List<>, FirstOrDefault returns the oldest surviving entry — so the lag grows in insertion order rather than staying at one.

The in-use count is the same bug seen from the other end. ReleaseRoutes removes a user keyed by signal type (RouteDescriptor.cs#L151):

route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);

Two descriptors add destination-Audio and destination-Video; releasing one removes one. The other user is never removed, so the port is permanently marked in use.

Reproduction

Room with an AudioVideo destination (display) and a separate Audio-only destination (dsp-audio-in) fed from the same switcher. Cycle through six sources.

The Audio-only destination is the control group: it has exactly one descriptor, and it is correct on every single press. The AudioVideo destination drifts.

Press display actually on display released dsp-audio-in released
appleTv zoom zoom ✅ zoom ✅
iptv appletv zoom ❌ appletv ✅
signage iptv appletv ❌ iptv ✅
zoom wireless appletv ❌ wireless ✅
appleTv zoom iptv ❌ zoom ✅

Accumulation is visible directly in the log. While the source is unchanged, one of the pair is already present, so it is a net +1 against one removal:

Adding route descriptor: zoom-source -> display:hdmiIn1 (Audio)
Route from zoom-source to display:hdmiIn1 (Video) already exists in this collection

On a source change neither exists, so it is a net +2 against one removal:

Adding route descriptor: appletv-source -> display:hdmiIn1 (Audio)
Adding route descriptor: appletv-source -> display:hdmiIn1 (Video)

Matching in-use counts from the same presses — the AudioVideo port never reaches 0, the Audio-only port is clean every time:

Port dmLiteOut1 releasing. Count=1     ... routing. Count=1 ... routing. Count=2
Port auxOut1    releasing. Count=0     ... routing. Count=1

Impact

The executed switches are always correct, so AV lands on the right input and the fault is not audible or visible in the room. What breaks is the bookkeeping built on it:

  • A destination's "currently routed source" is wrong after the second source change. This is the same Audio/Video split behind the routed-source label flashing on an NVX bench.
  • InUseTracker counts oscillate and never reach zero, so anything gating on "is this port free" sees a port that is never free.
  • ReleaseRoute(clearRoute: true) clears the stale route's switch rather than the current one.

Suggested fix

RemoveRouteDescriptor should remove every descriptor for the destination (optionally filtered by input port key) and release each, rather than only the first. The caller's intent at ReleaseRouteInternal is "release whatever is currently routed to this destination", which is all of them.

Returning a single RouteDescriptor is part of the signature, so this likely wants an overload or a return of the released set — ReleaseRouteInternal uses the returned SignalType afterwards to decide which ICurrentSources entries to clear, and that logic would want the union.

Filtering the lookup by SignalType instead would fix the in-use counts but leave the caller still releasing one of the two, so the stale-descriptor half would remain.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions