Files
webrtc/mediaengine.go
Josh Bleecher Snyder 455e955636 Document that some MediaEngines may be re-used
And while we're here, improve a bunch of related documentation
and comments.
2020-06-06 13:28:32 -07:00

389 lines
10 KiB
Go

// +build !js
package webrtc
import (
"fmt"
"strconv"
"strings"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/sdp/v2"
)
// PayloadTypes for the default codecs
const (
DefaultPayloadTypePCMU = 0
DefaultPayloadTypePCMA = 8
DefaultPayloadTypeG722 = 9
DefaultPayloadTypeOpus = 111
DefaultPayloadTypeVP8 = 96
DefaultPayloadTypeVP9 = 98
DefaultPayloadTypeH264 = 102
mediaNameAudio = "audio"
mediaNameVideo = "video"
)
// A MediaEngine defines the codecs supported by a PeerConnection.
// MediaEngines populated using RegisterCodec (and RegisterDefaultCodecs)
// may be set up once and reused, including concurrently,
// as long as no other codecs are added subsequently.
// MediaEngines populated using PopulateFromSDP should be used
// only for that session.
type MediaEngine struct {
codecs []*RTPCodec
}
// RegisterCodec adds codec to m.
// RegisterCodec is not safe for concurrent use.
func (m *MediaEngine) RegisterCodec(codec *RTPCodec) uint8 {
// TODO: dynamically generate a payload type in the range 96-127 if one wasn't provided.
// See https://github.com/pion/webrtc/issues/43
m.codecs = append(m.codecs, codec)
return codec.PayloadType
}
// RegisterDefaultCodecs registers the default codecs supported by Pion WebRTC.
// RegisterDefaultCodecs is not safe for concurrent use.
func (m *MediaEngine) RegisterDefaultCodecs() {
// Audio Codecs in descending order of preference
m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
m.RegisterCodec(NewRTPPCMUCodec(DefaultPayloadTypePCMU, 8000))
m.RegisterCodec(NewRTPPCMACodec(DefaultPayloadTypePCMA, 8000))
m.RegisterCodec(NewRTPG722Codec(DefaultPayloadTypeG722, 8000))
// Video Codecs in descending order of preference
m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
m.RegisterCodec(NewRTPVP9Codec(DefaultPayloadTypeVP9, 90000))
m.RegisterCodec(NewRTPH264Codec(DefaultPayloadTypeH264, 90000))
}
// PopulateFromSDP finds all codecs in sd and adds them to m, using the dynamic
// payload types and parameters from sd.
// PopulateFromSDP is intended for use when answering a request.
// The offerer sets the PayloadTypes for the connection.
// PopulateFromSDP allows an answerer to properly match the PayloadTypes from the offerer.
// A MediaEngine populated by PopulateFromSDP should be used only for a single session.
func (m *MediaEngine) PopulateFromSDP(sd SessionDescription) error {
sdp := sdp.SessionDescription{}
if err := sdp.Unmarshal([]byte(sd.SDP)); err != nil {
return err
}
for _, md := range sdp.MediaDescriptions {
if md.MediaName.Media != mediaNameAudio && md.MediaName.Media != mediaNameVideo {
continue
}
for _, format := range md.MediaName.Formats {
pt, err := strconv.Atoi(format)
if err != nil {
return fmt.Errorf("format parse error")
}
payloadType := uint8(pt)
payloadCodec, err := sdp.GetCodecForPayloadType(payloadType)
if err != nil {
return fmt.Errorf("could not find codec for payload type %d", payloadType)
}
var codec *RTPCodec
switch {
case strings.EqualFold(payloadCodec.Name, PCMA):
codec = NewRTPPCMACodec(payloadType, payloadCodec.ClockRate)
case strings.EqualFold(payloadCodec.Name, PCMU):
codec = NewRTPPCMUCodec(payloadType, payloadCodec.ClockRate)
case strings.EqualFold(payloadCodec.Name, G722):
codec = NewRTPG722Codec(payloadType, payloadCodec.ClockRate)
case strings.EqualFold(payloadCodec.Name, Opus):
codec = NewRTPOpusCodec(payloadType, payloadCodec.ClockRate)
case strings.EqualFold(payloadCodec.Name, VP8):
codec = NewRTPVP8Codec(payloadType, payloadCodec.ClockRate)
case strings.EqualFold(payloadCodec.Name, VP9):
codec = NewRTPVP9Codec(payloadType, payloadCodec.ClockRate)
case strings.EqualFold(payloadCodec.Name, H264):
codec = NewRTPH264Codec(payloadType, payloadCodec.ClockRate)
default:
// ignoring other codecs
continue
}
codec.SDPFmtpLine = payloadCodec.Fmtp
m.RegisterCodec(codec)
}
}
return nil
}
func (m *MediaEngine) getCodec(payloadType uint8) (*RTPCodec, error) {
for _, codec := range m.codecs {
if codec.PayloadType == payloadType {
return codec, nil
}
}
return nil, ErrCodecNotFound
}
func (m *MediaEngine) getCodecSDP(sdpCodec sdp.Codec) (*RTPCodec, error) {
for _, codec := range m.codecs {
if codec.Name == sdpCodec.Name &&
codec.ClockRate == sdpCodec.ClockRate &&
(sdpCodec.EncodingParameters == "" ||
strconv.Itoa(int(codec.Channels)) == sdpCodec.EncodingParameters) &&
codec.SDPFmtpLine == sdpCodec.Fmtp { // pion/webrtc#43
return codec, nil
}
}
return nil, ErrCodecNotFound
}
// GetCodecsByKind returns all codecs of kind kind that are supported by m.
// The returned codecs should not be modified.
func (m *MediaEngine) GetCodecsByKind(kind RTPCodecType) []*RTPCodec {
var codecs []*RTPCodec
for _, codec := range m.codecs {
if codec.Type == kind {
// TODO: clone the codec for safety?
codecs = append(codecs, codec)
}
}
return codecs
}
// Names for the default codecs supported by Pion WebRTC
const (
PCMU = "PCMU"
PCMA = "PCMA"
G722 = "G722"
Opus = "opus"
VP8 = "VP8"
VP9 = "VP9"
H264 = "H264"
)
// NewRTPPCMUCodec is a helper to create a PCMU codec
func NewRTPPCMUCodec(payloadType uint8, clockrate uint32) *RTPCodec {
c := NewRTPCodec(RTPCodecTypeAudio,
PCMU,
clockrate,
0,
"",
payloadType,
&codecs.G711Payloader{})
return c
}
// NewRTPPCMACodec is a helper to create a PCMA codec
func NewRTPPCMACodec(payloadType uint8, clockrate uint32) *RTPCodec {
c := NewRTPCodec(RTPCodecTypeAudio,
PCMA,
clockrate,
0,
"",
payloadType,
&codecs.G711Payloader{})
return c
}
// NewRTPG722Codec is a helper to create a G722 codec
func NewRTPG722Codec(payloadType uint8, clockrate uint32) *RTPCodec {
c := NewRTPCodec(RTPCodecTypeAudio,
G722,
clockrate,
0,
"",
payloadType,
&codecs.G722Payloader{})
return c
}
// NewRTPOpusCodec is a helper to create an Opus codec
func NewRTPOpusCodec(payloadType uint8, clockrate uint32) *RTPCodec {
c := NewRTPCodec(RTPCodecTypeAudio,
Opus,
clockrate,
2, //According to RFC7587, Opus RTP streams must have exactly 2 channels.
"minptime=10;useinbandfec=1",
payloadType,
&codecs.OpusPayloader{})
return c
}
// NewRTPVP8Codec is a helper to create an VP8 codec
func NewRTPVP8Codec(payloadType uint8, clockrate uint32) *RTPCodec {
c := NewRTPCodec(RTPCodecTypeVideo,
VP8,
clockrate,
0,
"",
payloadType,
&codecs.VP8Payloader{})
return c
}
// NewRTPVP8CodecExt is a helper to create an VP8 codec
func NewRTPVP8CodecExt(payloadType uint8, clockrate uint32, rtcpfb []RTCPFeedback, fmtp string) *RTPCodec {
c := NewRTPCodecExt(RTPCodecTypeVideo,
VP8,
clockrate,
0,
fmtp,
payloadType,
rtcpfb,
&codecs.VP8Payloader{})
return c
}
// NewRTPVP9Codec is a helper to create an VP9 codec
func NewRTPVP9Codec(payloadType uint8, clockrate uint32) *RTPCodec {
c := NewRTPCodec(RTPCodecTypeVideo,
VP9,
clockrate,
0,
"",
payloadType,
&codecs.VP9Payloader{})
return c
}
// NewRTPH264Codec is a helper to create an H264 codec
func NewRTPH264Codec(payloadType uint8, clockrate uint32) *RTPCodec {
c := NewRTPCodec(RTPCodecTypeVideo,
H264,
clockrate,
0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
payloadType,
&codecs.H264Payloader{})
return c
}
// NewRTPH264CodecExt is a helper to create an H264 codec
func NewRTPH264CodecExt(payloadType uint8, clockrate uint32, rtcpfb []RTCPFeedback, fmtp string) *RTPCodec {
c := NewRTPCodecExt(RTPCodecTypeVideo,
H264,
clockrate,
0,
fmtp,
payloadType,
rtcpfb,
&codecs.H264Payloader{})
return c
}
// RTPCodecType determines the type of a codec
type RTPCodecType int
const (
// RTPCodecTypeAudio indicates this is an audio codec
RTPCodecTypeAudio RTPCodecType = iota + 1
// RTPCodecTypeVideo indicates this is a video codec
RTPCodecTypeVideo
)
func (t RTPCodecType) String() string {
switch t {
case RTPCodecTypeAudio:
return "audio"
case RTPCodecTypeVideo:
return "video"
default:
return ErrUnknownType.Error()
}
}
// NewRTPCodecType creates a RTPCodecType from a string
func NewRTPCodecType(r string) RTPCodecType {
switch {
case strings.EqualFold(r, "audio"):
return RTPCodecTypeAudio
case strings.EqualFold(r, "video"):
return RTPCodecTypeVideo
default:
return RTPCodecType(0)
}
}
// RTPCodec represents a codec supported by the PeerConnection
type RTPCodec struct {
RTPCodecCapability
Type RTPCodecType
Name string
PayloadType uint8
Payloader rtp.Payloader
}
// NewRTPCodec is used to define a new codec
func NewRTPCodec(
codecType RTPCodecType,
name string,
clockrate uint32,
channels uint16,
fmtp string,
payloadType uint8,
payloader rtp.Payloader,
) *RTPCodec {
return &RTPCodec{
RTPCodecCapability: RTPCodecCapability{
MimeType: codecType.String() + "/" + name,
ClockRate: clockrate,
Channels: channels,
SDPFmtpLine: fmtp,
},
PayloadType: payloadType,
Payloader: payloader,
Type: codecType,
Name: name,
}
}
// NewRTPCodecExt is used to define a new codec
func NewRTPCodecExt(
codecType RTPCodecType,
name string,
clockrate uint32,
channels uint16,
fmtp string,
payloadType uint8,
rtcpfb []RTCPFeedback,
payloader rtp.Payloader,
) *RTPCodec {
return &RTPCodec{
RTPCodecCapability: RTPCodecCapability{
MimeType: codecType.String() + "/" + name,
ClockRate: clockrate,
Channels: channels,
SDPFmtpLine: fmtp,
RTCPFeedback: rtcpfb,
},
PayloadType: payloadType,
Payloader: payloader,
Type: codecType,
Name: name,
}
}
// RTPCodecCapability provides information about codec capabilities.
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.
type RTPHeaderExtensionCapability struct {
URI string
}
// RTPCapabilities represents the capabilities of a transceiver
type RTPCapabilities struct {
Codecs []RTPCodecCapability
HeaderExtensions []RTPHeaderExtensionCapability
}