mirror of
https://github.com/pion/webrtc.git
synced 2025-09-30 04:52:21 +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.
186 lines
4.0 KiB
Go
186 lines
4.0 KiB
Go
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Package fmtp implements per codec parsing of fmtp lines
|
|
package fmtp
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
func defaultClockRate(mimeType string) uint32 {
|
|
defaults := map[string]uint32{
|
|
"audio/opus": 48000,
|
|
"audio/pcmu": 8000,
|
|
"audio/pcma": 8000,
|
|
}
|
|
|
|
if def, ok := defaults[strings.ToLower(mimeType)]; ok {
|
|
return def
|
|
}
|
|
|
|
return 90000
|
|
}
|
|
|
|
func defaultChannels(mimeType string) uint16 {
|
|
defaults := map[string]uint16{
|
|
"audio/opus": 2,
|
|
}
|
|
|
|
if def, ok := defaults[strings.ToLower(mimeType)]; ok {
|
|
return def
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func parseParameters(line string) map[string]string {
|
|
parameters := make(map[string]string)
|
|
|
|
for _, p := range strings.Split(line, ";") {
|
|
pp := strings.SplitN(strings.TrimSpace(p), "=", 2)
|
|
key := strings.ToLower(pp[0])
|
|
var value string
|
|
if len(pp) > 1 {
|
|
value = pp[1]
|
|
}
|
|
parameters[key] = value
|
|
}
|
|
|
|
return parameters
|
|
}
|
|
|
|
// ClockRateEqual checks whether two clock rates are equal.
|
|
func ClockRateEqual(mimeType string, valA, valB uint32) bool {
|
|
// Lots of users use formats without setting clock rate or channels.
|
|
// In this case, use default values.
|
|
// It would be better to remove this exception in a future major release.
|
|
if valA == 0 {
|
|
valA = defaultClockRate(mimeType)
|
|
}
|
|
if valB == 0 {
|
|
valB = defaultClockRate(mimeType)
|
|
}
|
|
|
|
return valA == valB
|
|
}
|
|
|
|
// ChannelsEqual checks whether two channels are equal.
|
|
func ChannelsEqual(mimeType string, valA, valB uint16) bool {
|
|
// Lots of users use formats without setting clock rate or channels.
|
|
// In this case, use default values.
|
|
// It would be better to remove this exception in a future major release.
|
|
if valA == 0 {
|
|
valA = defaultChannels(mimeType)
|
|
}
|
|
if valB == 0 {
|
|
valB = defaultChannels(mimeType)
|
|
}
|
|
|
|
// RFC8866: channel count "is OPTIONAL and may be omitted
|
|
// if the number of channels is one".
|
|
if valA == 0 {
|
|
valA = 1
|
|
}
|
|
if valB == 0 {
|
|
valB = 1
|
|
}
|
|
|
|
return valA == valB
|
|
}
|
|
|
|
func paramsEqual(valA, valB map[string]string) bool {
|
|
for k, v := range valA {
|
|
if vb, ok := valB[k]; ok && !strings.EqualFold(vb, v) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
for k, v := range valB {
|
|
if va, ok := valA[k]; ok && !strings.EqualFold(va, v) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// FMTP interface for implementing custom
|
|
// FMTP parsers based on MimeType.
|
|
type FMTP interface {
|
|
// MimeType returns the MimeType associated with
|
|
// the fmtp
|
|
MimeType() string
|
|
// Match compares two fmtp descriptions for
|
|
// compatibility based on the MimeType
|
|
Match(f FMTP) bool
|
|
// Parameter returns a value for the associated key
|
|
// if contained in the parsed fmtp string
|
|
Parameter(key string) (string, bool)
|
|
}
|
|
|
|
// Parse parses an fmtp string based on the MimeType.
|
|
func Parse(mimeType string, clockRate uint32, channels uint16, line string) FMTP {
|
|
var fmtp FMTP
|
|
|
|
parameters := parseParameters(line)
|
|
|
|
switch {
|
|
case strings.EqualFold(mimeType, "video/h264"):
|
|
fmtp = &h264FMTP{
|
|
parameters: parameters,
|
|
}
|
|
|
|
case strings.EqualFold(mimeType, "video/vp9"):
|
|
fmtp = &vp9FMTP{
|
|
parameters: parameters,
|
|
}
|
|
|
|
case strings.EqualFold(mimeType, "video/av1"):
|
|
fmtp = &av1FMTP{
|
|
parameters: parameters,
|
|
}
|
|
|
|
default:
|
|
fmtp = &genericFMTP{
|
|
mimeType: mimeType,
|
|
clockRate: clockRate,
|
|
channels: channels,
|
|
parameters: parameters,
|
|
}
|
|
}
|
|
|
|
return fmtp
|
|
}
|
|
|
|
type genericFMTP struct {
|
|
mimeType string
|
|
clockRate uint32
|
|
channels uint16
|
|
parameters map[string]string
|
|
}
|
|
|
|
func (g *genericFMTP) MimeType() string {
|
|
return g.mimeType
|
|
}
|
|
|
|
// Match returns true if g and b are compatible fmtp descriptions
|
|
// The generic implementation is used for MimeTypes that are not defined.
|
|
func (g *genericFMTP) Match(b FMTP) bool {
|
|
fmtp, ok := b.(*genericFMTP)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return strings.EqualFold(g.mimeType, fmtp.MimeType()) &&
|
|
ClockRateEqual(g.mimeType, g.clockRate, fmtp.clockRate) &&
|
|
ChannelsEqual(g.mimeType, g.channels, fmtp.channels) &&
|
|
paramsEqual(g.parameters, fmtp.parameters)
|
|
}
|
|
|
|
func (g *genericFMTP) Parameter(key string) (string, bool) {
|
|
v, ok := g.parameters[key]
|
|
|
|
return v, ok
|
|
}
|