Skip to content

TEL-886: Media Port rework - #802

Open
alexlivekit wants to merge 14 commits into
tel-886/prepfrom
tel-886/media-port
Open

TEL-886: Media Port rework#802
alexlivekit wants to merge 14 commits into
tel-886/prepfrom
tel-886/media-port

Conversation

@alexlivekit

@alexlivekit alexlivekit commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

This change is intended to:

  • Have little functional differences between it an main.
    • the one known thing: Plug-in processor moved back to ingesting 48khz audio and returning 48khz audio.
  • Add interface for clarity.
  • Add machinery to support renegotiation, early media, hold, etc. All these are still disabled for now, but there.
  • Add tests.

@alexlivekit
alexlivekit requested a review from a team as a code owner August 20, 2026 19:20

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread pkg/sip/outbound.go
Comment on lines +389 to +393
if c.lkRoomIn != nil {
if err := c.lkRoomIn.Close(); err != nil {
log.Warnw("error closing livekit room audio input", err)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Outbound call closes the room audio track twice on teardown

When an outbound call ends, the room-facing audio track is closed once by the media port teardown (c.media.Close() at pkg/sip/outbound.go:382, which propagates through the media port's inbound-audio switch) and then closed a second time explicitly (c.lkRoomIn.Close() at pkg/sip/outbound.go:390), so the same audio writer is torn down twice.
Impact: The call-teardown path double-releases the outbound audio track, which can surface as spurious error logs or, if the underlying encoder is not close-idempotent, resource corruption during shutdown.

Why the same object is closed on two paths

In connectMedia the (possibly processor-wrapped) c.lkRoomIn is handed to the media port via c.media.WriteInboundAudioTo(c.lkRoomIn) (pkg/sip/outbound.go:559), which stores it as the inner writer of the port's audioIn switch (pkg/sip/media_port.go:734-735).

On mediaPort.Close(), p.audioIn.Close() is called with the explicit comment "Propagate Close() to onwards to room" (pkg/sip/media_port.go:693), closing c.lkRoomIn.

Then outboundCall.close closes it again at pkg/sip/outbound.go:389-393. The inbound path (inboundCall.closeMedia at pkg/sip/inbound.go:1578-1585) only closes it once via c.media.Close(), so the outbound explicit close is redundant and asymmetric.

Prompt for agents
In outboundCall.close (pkg/sip/outbound.go around lines 389-393), c.lkRoomIn.Close() is called explicitly, but c.media.Close() (called just above at line 382) already propagates Close() to the inbound-audio writer stored via connectMedia's c.media.WriteInboundAudioTo(c.lkRoomIn), which is c.lkRoomIn. This results in a double Close() of the room participant track / opus encoder on every outbound call teardown. The inbound path (inboundCall.closeMedia) relies solely on media.Close() and does not double-close. Decide on a single ownership model: either drop the explicit c.lkRoomIn.Close() here (relying on media.Close to close it), or ensure the explicit close only runs when connectMedia never wired c.lkRoomIn into the media port (e.g. early-failure paths). Make outbound consistent with inbound.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed that this is actually a thing: I'm seeing the log warning a lot in staging. Will address and fix.

@alexlivekit

Copy link
Copy Markdown
Contributor Author

Huh, the race in CI is real, and is apparently a problem in media-sdk (yay zerocopy) that previous tests simply did not expose.
So... no regression compared to main.

@alexfish8

Copy link
Copy Markdown
Contributor

Huh, the race in CI is real, and is apparently a problem in media-sdk (yay zerocopy) that previous tests simply did not expose. So... no regression compared to main.

Will fix this in media-sdk.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment thread pkg/sip/media_port.go
return nil, SDPError{Err: err}
}
}
p.reportPeerCodecs(offer.MediaDesc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 SDP re-INVITE metric label always false

isReinvite is derived from p.offer != nil, but p.offer is set only by outbound GenerateOffer and cleared once negotiated. GenerateAnswer runs in the answering role, so it is always nil here. Every parsed offer, including real inbound re-INVITEs, records reinvite=false on the new SDP metrics. The intended signal is p.negotiated != nil.

Suggested change
p.reportPeerCodecs(offer.MediaDesc)
isReinvite := p.negotiated != nil
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants