rtp

package
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultJitterPlaybackDelay = 80 * time.Millisecond

DefaultJitterPlaybackDelay is the default inbound playout delay (reorder + smoothing).

Variables

View Source
var ErrRTPDiscard = errors.New("rtp: discard packet")

ErrRTPDiscard signals that a datagram was read but must be ignored (malformed RTP / policy). Callers that loop on ReceiveRTP should continue without treating it as a transport failure.

Functions

func FingerprintSHA256

func FingerprintSHA256(certDER []byte) string

FingerprintSHA256 computes the SDP `a=fingerprint:sha-256 HEX` digest for a DER-encoded cert. Returns the colon-separated uppercase hex form (32 bytes → 95-char string).

func IsDTLSPacket

func IsDTLSPacket(b byte) bool

IsDTLSPacket reports whether the first byte of a UDP datagram on a multiplexed RTP socket indicates DTLS (RFC 5764 §5.1.2). The type-byte ranges are:

  • 0..3 STUN
  • 16..19 ZRTP
  • 20..63 DTLS ← us
  • 64..79 TURN channel
  • 128..191 RTP / RTCP

Ranges are disjoint by design so a demux can route by first byte.

func IsRTPPacket

func IsRTPPacket(b byte) bool

IsRTPPacket reports whether the first byte indicates RTP/RTCP. Useful for the muxer's "default route" check.

func SelfSignedDTLSCert

func SelfSignedDTLSCert(notAfter time.Time) ([]byte, *ecdsa.PrivateKey, error)

SelfSignedDTLSCert generates a fresh ECDSA P-256 cert for one DTLS endpoint. RFC 5763 §6.5 says the cert SHOULD be self-signed because trust comes from the SDP fingerprint, not a CA — minting per-call certs reduces compromise blast radius.

notAfter defaults to 30 days when zero. We don't need to rotate the cert often because each call brings its own.

Types

type DTLSEndpoint

type DTLSEndpoint struct {
	// contains filtered or unexported fields
}

DTLSEndpoint wraps a single DTLS-SRTP handshake on a UDP socket. Construct via NewDTLSEndpoint, run Handshake (server or client), then call SRTPKeys to get the derived material. Close releases the underlying conn.

This is the standalone form — when integrating with Session, we pass a `net.Conn` that's actually a DTLS-only multiplexed view of the shared RTP socket (built in dtls_session.go, slice A-3).

func NewDTLSEndpoint

func NewDTLSEndpoint(ctx context.Context, transport net.Conn, asServer bool, certDER []byte, key *ecdsa.PrivateKey, profiles []SRTPProfile) (*DTLSEndpoint, error)

NewDTLSEndpoint wraps an underlying transport (any net.Conn — for our purposes a UDP socket already pre-set with the remote addr, or a packet-conn-backed pseudo-stream) and runs a DTLS handshake.

Profiles are the SRTP profiles to advertise in the use_srtp extension (RFC 5764 §4.1.1). Empty = our default order.

certDER + key identify our side; the peer commits to its own via the SDP fingerprint that the SIP layer validated separately against the cert presented in the handshake (RFC 5763 §3).

asServer = true when we're the DTLS server (a=setup:passive).

func (*DTLSEndpoint) AsClient

func (e *DTLSEndpoint) AsClient() bool

AsClient reports whether this endpoint drove the handshake. Used by SRTPKeys.AsLocalRemote to swap the client/server material.

func (*DTLSEndpoint) Close

func (e *DTLSEndpoint) Close() error

Close shuts the DTLS conn (which sends close_notify and stops reading). Idempotent.

func (*DTLSEndpoint) PeerCertificates

func (e *DTLSEndpoint) PeerCertificates() [][]byte

PeerCertificates returns the DER-encoded certificates that the peer presented during the handshake. The leaf is index 0. Used by callers to verify against the SDP a=fingerprint per RFC 5763 §3 — without this check the cert is uncommitted and a passive MITM owns the call.

Returns nil before handshake completion or after Close.

func (*DTLSEndpoint) SRTPContexts

func (e *DTLSEndpoint) SRTPContexts(keys *SRTPKeys) (rx, tx *srtp.Context, err error)

SRTPContexts builds inbound + outbound *srtp.Context from derived keying material, matched to this endpoint's role. Convenience for callers that want to enable SRTP on a Session immediately.

func (*DTLSEndpoint) SRTPKeys

func (e *DTLSEndpoint) SRTPKeys() (*SRTPKeys, error)

SRTPKeys returns the post-handshake keying material per RFC 5764 §4.2. Must be called after Handshake (i.e. after construction) and before Close.

type DTLSResult

type DTLSResult struct {
	Endpoint *DTLSEndpoint
	Keys     *SRTPKeys
	Err      error
}

DTLSResult is what StartDTLS hands back when the handshake either completes successfully or fails. Either Endpoint+Keys are non-nil (success), or Err is non-nil (failure). Caller is responsible for closing the Endpoint when the call ends.

type RTCPStats

type RTCPStats struct {
	// PeerSeenRR tells callers whether our peer has emitted at least
	// one RR about us; useful to distinguish "no RTCP support" from
	// "no observed loss yet".
	PeerSeenRR bool
	// RTTMs is the round-trip time computed from the latest peer RR.
	RTTMs uint32
	// PeerJitter (in RTP clock units) as reported by peer's RR.
	PeerJitter uint32
	// PeerLossFraction is fraction-lost from peer's last RR (Q0.8).
	PeerLossFraction uint8
	// PeerCumulativeLost — peer's running total of dropped pkts of ours.
	PeerCumulativeLost uint32
	// LocalJitter is what we measured on the inbound stream.
	LocalJitter uint32
	// LocalPacketsRecv — RTP packets we accepted.
	LocalPacketsRecv uint32
}

RTCPStats is the read-only snapshot exposed via SessionStats.

type RTPHeader

type RTPHeader struct {
	Version        uint8
	Padding        bool
	Extension      bool
	CSRCCount      uint8
	Marker         bool
	PayloadType    uint8
	SequenceNumber uint16
	Timestamp      uint32
	SSRC           uint32
}

RTPHeader RTP header fields (RFC 3550).

Notes: - This implementation supports:

  • CSRC list (CC)
  • header extension (X)
  • padding (P)

- It does not attempt to interpret extension payloads beyond carrying raw bytes.

type RTPPacket

type RTPPacket struct {
	Header RTPHeader

	// CSRC identifiers (0..15 entries).
	CSRC []uint32

	// If Header.Extension is true, these fields represent the extension header.
	ExtensionProfile uint16
	ExtensionPayload []byte // raw extension bytes

	// Payload is the RTP payload after removing CSRC/extension/padding.
	Payload []byte
}

RTPPacket RTP packet containing an RTP header and payload.

func (*RTPPacket) Marshal

func (p *RTPPacket) Marshal() ([]byte, error)

Marshal serializes RTPPacket into a byte slice.

func (*RTPPacket) Unmarshal

func (p *RTPPacket) Unmarshal(data []byte) error

Unmarshal parses RTP packet bytes into the RTPPacket.

type SIPRTPTransport

type SIPRTPTransport struct {

	// PreserveSessionOnClose, if true, Close() does not close the underlying RTP UDP socket.
	// Used when stopping the default MediaSession and handing media to an in-process bridge.
	PreserveSessionOnClose bool

	// OnInputPayload, if set, receives a copy of each incoming RTP payload before DTMF heuristics.
	OnInputPayload func([]byte)
	// OnInputRTP, if set, receives RTP timing metadata + payload copy for recording/reconstruction.
	OnInputRTP func(seq uint16, ts uint32, payload []byte)
	// OnOutputPayload, if set, receives a copy of each outgoing encoded audio RTP payload (output transport only).
	OnOutputPayload func([]byte)
	// OnOutputRTP, if set, receives RTP timing metadata + payload copy for recording/reconstruction.
	// seq/ts correspond to the packet values before Session.SendRTP increments counters.
	OnOutputRTP func(seq uint16, ts uint32, payload []byte)

	// JitterPlaybackDelay, if > 0 on DirectionInput, delays playout and absorbs reorder/jitter (see DefaultJitterPlaybackDelay).
	JitterPlaybackDelay time.Duration
	// contains filtered or unexported fields
}

SIPRTPTransport adapts an RTP Session to the media.MediaTransport interface.

It is direction-aware:

  • direction == media.DirectionInput : Next() reads from UDP & returns AudioPacket, Send() is a no-op
  • direction == media.DirectionOutput: Send() writes AudioPacket as RTP, Next() is a no-op

func NewSIPRTPTransport

func NewSIPRTPTransport(sess *Session, codec media.CodecConfig, direction string, telephoneEventPT uint8) *SIPRTPTransport

NewSIPRTPTransport creates a new SIPRTPTransport.

codec describes the negotiated RTP codec (from SDP), including sample rate, channels, bit depth and payload type. telephoneEventPT is the RTP payload type for telephone-event (RFC 2833); use 0 if not negotiated.

func (*SIPRTPTransport) Attach

func (t *SIPRTPTransport) Attach(s *media.MediaSession)

Attach is called by MediaSession when the transport is registered.

func (*SIPRTPTransport) Close

func (t *SIPRTPTransport) Close() error

Close closes the underlying RTP session.

func (*SIPRTPTransport) Codec

func (t *SIPRTPTransport) Codec() media.CodecConfig

Codec returns the negotiated codec configuration.

func (*SIPRTPTransport) Next

Next reads one RTP packet from the underlying Session and converts it to a media.AudioPacket for the input direction. For output transports it returns (nil, nil).

func (*SIPRTPTransport) Send

func (t *SIPRTPTransport) Send(ctx context.Context, packet media.MediaPacket) (int, error)

Send sends a media.AudioPacket as a single RTP packet for the output direction. For input transports it is a no-op.

func (*SIPRTPTransport) String

func (t *SIPRTPTransport) String() string

func (*SIPRTPTransport) WakeupRead

func (t *SIPRTPTransport) WakeupRead()

WakeupRead unblocks a goroutine stuck in Next() (same idea as transfer RTP relay stop).

type SRTPKeys

type SRTPKeys struct {
	Profile         SRTPProfile
	ClientWriteKey  []byte // 16 B
	ClientWriteSalt []byte // 14 B
	ServerWriteKey  []byte // 16 B
	ServerWriteSalt []byte // 14 B
}

SRTPKeys is the post-handshake material derived from the DTLS master secret per RFC 5764 §4.2. The "client" and "server" designations come from the handshake roles; downstream SRTP code uses them as "outbound from this side" / "inbound to this side" based on whether we drove the handshake (active = client).

func (*SRTPKeys) AsLocalRemote

func (k *SRTPKeys) AsLocalRemote(iWasClient bool) (localKey, localSalt, remoteKey, remoteSalt []byte)

AsLocalRemote returns (localKey, localSalt, remoteKey, remoteSalt) based on whether this side drove the handshake.

  • iWasClient = true → we are the client; clientWrite is OUR tx, serverWrite is the peer's tx (our rx)
  • iWasClient = false → roles flipped

type SRTPProfile

type SRTPProfile string

SRTPProfile names what we negotiated in the DTLS handshake. We only support the two RFC 5764 §4.1.2 must-implement profiles — adding more (AEAD_AES_128_GCM, AEAD_AES_256_GCM) is a follow-up once we hit a peer that requires them.

const (
	// ProfileAES128CMHMACSHA180: AES_128_CM_HMAC_SHA1_80, 128-bit
	// confidentiality + 80-bit auth tag. The default everywhere.
	ProfileAES128CMHMACSHA180 SRTPProfile = "AES_CM_128_HMAC_SHA1_80"
	// ProfileAES128CMHMACSHA132: same cipher, 32-bit tag. Used by a
	// few legacy SBCs to save bandwidth on every packet.
	ProfileAES128CMHMACSHA132 SRTPProfile = "AES_CM_128_HMAC_SHA1_32"
)

type Session

type Session struct {
	LocalAddr  *net.UDPAddr
	RemoteAddr *net.UDPAddr
	Conn       *net.UDPConn

	// SSRC/sequence/timestamp are advanced by this session.
	SSRC      uint32
	SeqNum    uint16
	Timestamp uint32
	// contains filtered or unexported fields
}

Session is a minimal RTP-over-UDP session.

RTCP (and SRTCP) is not generated or parsed; SDP may still advertise a=rtcp for peer stacks. Optional SRTP (SDES) encrypts/decrypts RTP payloads only (Session.EnableSDESSRTP).

It is intentionally protocol-agnostic: - Timestamp increments are provided by the caller via `samples` argument. - Payload framing / codec packetization happens above this layer.

func NewSession

func NewSession(localPort int) (*Session, error)

NewSession creates a RTP UDP session.

If localPort is 0 or negative, the OS will choose an available ephemeral port.

func (*Session) AddMirrorRemote

func (s *Session) AddMirrorRemote(addr *net.UDPAddr, ttl time.Duration)

AddMirrorRemote adds a temporary extra RTP destination for outbound packets. Useful for NAT/ALG scenarios where real media port differs from SDP offer.

func (*Session) Close

func (s *Session) Close() error

func (*Session) EnableDTLSSRTP

func (s *Session) EnableDTLSSRTP(rx, tx *srtp.Context) error

EnableDTLSSRTP installs DTLS-derived SRTP contexts onto a Session. Mirrors EnableSDESSRTP but takes pre-built contexts to avoid duplicating the role-mapping logic between the two key sources.

func (*Session) EnableSDESSRTP

func (s *Session) EnableSDESSRTP(peerOutboundKey, peerOutboundSalt, localOutboundKey, localOutboundSalt []byte) error

EnableSDESSRTP is a backwards-compatible shim that defaults to AES_CM_128_HMAC_SHA1_80. New callers should use EnableSDESSRTPWithProfile so they can carry the negotiated suite (RFC 3711 supports multiple auth tag lengths in addition to _80).

func (*Session) EnableSDESSRTPWithProfile

func (s *Session) EnableSDESSRTPWithProfile(prof srtp.ProtectionProfile,
	peerOutboundKey, peerOutboundSalt, localOutboundKey, localOutboundSalt []byte) error

EnableSDESSRTPWithProfile configures SRTP using RFC 4568 SDES material with an explicit pion protection profile so callers that negotiated AES_CM_128_HMAC_SHA1_32 (Cisco / Avaya interop) don't silently fall back to _80 and end up with auth-tag length mismatches on the wire.

  • rx context: decrypts the peer's outbound RTP (their tx → our rx)
  • tx context: encrypts our outbound RTP (our tx → their rx)

Both contexts MUST use the same profile — RFC 4568 §6.1 forbids asymmetric suites within one m=audio block.

func (*Session) FirstPacket

func (s *Session) FirstPacket() <-chan struct{}

FirstPacket returns a channel that is closed once the first RTP packet is received.

func (*Session) RTCPSnapshot

func (s *Session) RTCPSnapshot() RTCPStats

RTCPSnapshot returns the RTCP-derived metrics. Nil-safe.

func (*Session) ReceiveRTP

func (s *Session) ReceiveRTP(buffer []byte) (n int, from *net.UDPAddr, packet *RTPPacket, err error)

ReceiveRTP reads a UDP datagram and parses it into an RTPPacket.

It also opportunistically "learns" remote address (symmetric RTP behavior).

func (*Session) SendRTP

func (s *Session) SendRTP(payload []byte, payloadType uint8, samples uint32) error

SendRTP sends one RTP packet.

`samples` is the number of audio samples represented by `payload` at the RTP clock rate. For PCM-based codecs, this should match the negotiated codec frame duration.

func (*Session) SetRTCPClockRate

func (s *Session) SetRTCPClockRate(hz uint32)

SetRTCPClockRate sets the codec clock rate used for jitter computation. Default is 8000; call this with 48000 for Opus, or the actual negotiated rate otherwise.

func (*Session) SetRemoteAddr

func (s *Session) SetRemoteAddr(addr *net.UDPAddr)

SetRemoteAddr sets the remote RTP address for outgoing packets.

func (*Session) StartDTLS

func (s *Session) StartDTLS(ctx context.Context, asServer bool, certDER []byte, key *ecdsa.PrivateKey, profiles []SRTPProfile) <-chan DTLSResult

StartDTLS installs a DTLS demux route on this Session, then runs the handshake on a goroutine. The returned channel receives the result exactly once. Caller passes the cert/key it committed to in SDP (`a=fingerprint`); peer presents its own and the SDP layer is responsible for verifying the cert digest matches what was advertised (RFC 5763 §3) — that check is wired in slice A-4 alongside SDP offer/answer rendering.

asServer = true when our SDP `a=setup` resolved to "passive".

Concurrent StartDTLS calls on the same Session are an error; teardown happens via DTLSEndpoint.Close (the route is dropped from Session automatically).

func (*Session) StatsSnapshot

func (s *Session) StatsSnapshot() SessionStats

type SessionStats

type SessionStats struct {
	LocalSocket string
	RemoteSDP   string
	RemoteNow   string
	TXPackets   uint64
	TXBytes     uint64
	RXPackets   uint64
	RXBytes     uint64
	FirstTXAgo  int64
	FirstRXAgo  int64
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL