mirror of
https://github.com/pion/webrtc.git
synced 2025-09-26 19:21:12 +08:00

Currently codecs are matched regardless of the clock rate and the channel count, and this makes impossible to fully support codecs that might have a clock rate or channel count different than the default one, in particular LPCM, PCMU, PCMA and multiopus (the last one is a custom Opus variant present in the Chrome source code to support multichannel Opus). For instance, let's suppose a peer (receiver) wants to receive an audio track encoded with LPCM, 48khz sample rate and 2 channels. This receiver doesn't know the audio codec yet, therefore it advertises all supported sample rates in the SDP: ``` LPCM/44100 LPCM/48000 LPCM/44100/2 LPCM/48000/2 ``` The other peer (sender) receives the SDP, but since the clock rate and channel count are not taken into consideration when matching codecs, the sender codec `LPCM/48000/2` is wrongly associated with the receiver codec `LPCM/44100`. The result is that the audio track cannot be decoded correctly from the receiver side. This patch fixes the issue and has been running smoothly in MediaMTX for almost a year. Unfortunately, in lots of examples and tests, clock rate and/or channels are not present (and in fact they are producing horrible SDPs that contain `VP8/0` instead of `VP8/90000` and are incompatible with lots of servers) therefore this new check causes troubles in existing code. In order to maintain compatibility, default clock rates and channels are provided for most codecs. In the future, it might be better to update examples (i can do it in a future patch) and remove the exception.
171 lines
4.5 KiB
Go
171 lines
4.5 KiB
Go
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package webrtc
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pion/webrtc/v4/internal/fmtp"
|
|
)
|
|
|
|
// RTPCodecType determines the type of a codec.
|
|
type RTPCodecType int
|
|
|
|
const (
|
|
// RTPCodecTypeUnknown is the enum's zero-value.
|
|
RTPCodecTypeUnknown RTPCodecType = iota
|
|
|
|
// RTPCodecTypeAudio indicates this is an audio codec.
|
|
RTPCodecTypeAudio
|
|
|
|
// RTPCodecTypeVideo indicates this is a video codec.
|
|
RTPCodecTypeVideo
|
|
)
|
|
|
|
func (t RTPCodecType) String() string {
|
|
switch t {
|
|
case RTPCodecTypeAudio:
|
|
return "audio" //nolint: goconst
|
|
case RTPCodecTypeVideo:
|
|
return "video" //nolint: goconst
|
|
default:
|
|
return ErrUnknownType.Error()
|
|
}
|
|
}
|
|
|
|
// NewRTPCodecType creates a RTPCodecType from a string.
|
|
func NewRTPCodecType(r string) RTPCodecType {
|
|
switch {
|
|
case strings.EqualFold(r, RTPCodecTypeAudio.String()):
|
|
return RTPCodecTypeAudio
|
|
case strings.EqualFold(r, RTPCodecTypeVideo.String()):
|
|
return RTPCodecTypeVideo
|
|
default:
|
|
return RTPCodecType(0)
|
|
}
|
|
}
|
|
|
|
// RTPCodecCapability provides information about codec capabilities.
|
|
//
|
|
// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpcodeccapability-members
|
|
type RTPCodecCapability struct {
|
|
MimeType string
|
|
ClockRate uint32
|
|
Channels uint16
|
|
SDPFmtpLine string
|
|
RTCPFeedback []RTCPFeedback
|
|
}
|
|
|
|
// RTPHeaderExtensionCapability is used to define a RFC5285 RTP header extension supported by the codec.
|
|
//
|
|
// https://w3c.github.io/webrtc-pc/#dom-rtcrtpcapabilities-headerextensions
|
|
type RTPHeaderExtensionCapability struct {
|
|
URI string
|
|
}
|
|
|
|
// RTPHeaderExtensionParameter represents a negotiated RFC5285 RTP header extension.
|
|
//
|
|
// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpheaderextensionparameters-members
|
|
type RTPHeaderExtensionParameter struct {
|
|
URI string
|
|
ID int
|
|
}
|
|
|
|
// RTPCodecParameters is a sequence containing the media codecs that an RtpSender
|
|
// will choose from, as well as entries for RTX, RED and FEC mechanisms. This also
|
|
// includes the PayloadType that has been negotiated
|
|
//
|
|
// https://w3c.github.io/webrtc-pc/#rtcrtpcodecparameters
|
|
type RTPCodecParameters struct {
|
|
RTPCodecCapability
|
|
PayloadType PayloadType
|
|
|
|
statsID string
|
|
}
|
|
|
|
// RTPParameters is a list of negotiated codecs and header extensions
|
|
//
|
|
// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpparameters-members
|
|
type RTPParameters struct {
|
|
HeaderExtensions []RTPHeaderExtensionParameter
|
|
Codecs []RTPCodecParameters
|
|
}
|
|
|
|
type codecMatchType int
|
|
|
|
const (
|
|
codecMatchNone codecMatchType = 0
|
|
codecMatchPartial codecMatchType = 1
|
|
codecMatchExact codecMatchType = 2
|
|
)
|
|
|
|
// Do a fuzzy find for a codec in the list of codecs
|
|
// Used for lookup up a codec in an existing list to find a match
|
|
// Returns codecMatchExact, codecMatchPartial, or codecMatchNone.
|
|
func codecParametersFuzzySearch(
|
|
needle RTPCodecParameters,
|
|
haystack []RTPCodecParameters,
|
|
) (RTPCodecParameters, codecMatchType) {
|
|
needleFmtp := fmtp.Parse(
|
|
needle.RTPCodecCapability.MimeType,
|
|
needle.RTPCodecCapability.ClockRate,
|
|
needle.RTPCodecCapability.Channels,
|
|
needle.RTPCodecCapability.SDPFmtpLine)
|
|
|
|
// First attempt to match on MimeType + ClockRate + Channels + SDPFmtpLine
|
|
for _, c := range haystack {
|
|
cfmtp := fmtp.Parse(
|
|
c.RTPCodecCapability.MimeType,
|
|
c.RTPCodecCapability.ClockRate,
|
|
c.RTPCodecCapability.Channels,
|
|
c.RTPCodecCapability.SDPFmtpLine)
|
|
|
|
if needleFmtp.Match(cfmtp) {
|
|
return c, codecMatchExact
|
|
}
|
|
}
|
|
|
|
// Fallback to just MimeType + ClockRate + Channels
|
|
for _, c := range haystack {
|
|
if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
|
|
fmtp.ClockRateEqual(c.RTPCodecCapability.MimeType,
|
|
c.RTPCodecCapability.ClockRate,
|
|
needle.RTPCodecCapability.ClockRate) &&
|
|
fmtp.ChannelsEqual(c.RTPCodecCapability.MimeType,
|
|
c.RTPCodecCapability.Channels,
|
|
needle.RTPCodecCapability.Channels) {
|
|
return c, codecMatchPartial
|
|
}
|
|
}
|
|
|
|
return RTPCodecParameters{}, codecMatchNone
|
|
}
|
|
|
|
// Given a CodecParameters find the RTX CodecParameters if one exists.
|
|
func findRTXPayloadType(needle PayloadType, haystack []RTPCodecParameters) PayloadType {
|
|
aptStr := fmt.Sprintf("apt=%d", needle)
|
|
for _, c := range haystack {
|
|
if aptStr == c.SDPFmtpLine {
|
|
return c.PayloadType
|
|
}
|
|
}
|
|
|
|
return PayloadType(0)
|
|
}
|
|
|
|
func rtcpFeedbackIntersection(a, b []RTCPFeedback) (out []RTCPFeedback) {
|
|
for _, aFeedback := range a {
|
|
for _, bFeeback := range b {
|
|
if aFeedback.Type == bFeeback.Type && aFeedback.Parameter == bFeeback.Parameter {
|
|
out = append(out, aFeedback)
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|