package ingress
import (
"github.com/livekit/protocol/livekit"
)
func Validate(info *livekit.IngressInfo) error {
if info == nil {
return ErrInvalidIngress("missing IngressInfo")
}
if info.RoomName == "" {
return ErrInvalidIngress("no room name")
}
return ValidateForSerialization(info)
}
func ValidateForSerialization(info *livekit.IngressInfo) error {
if info == nil {
return ErrInvalidIngress("missing IngressInfo")
}
if info.InputType != livekit.IngressInput_RTMP_INPUT && info.InputType != livekit.IngressInput_WHIP_INPUT && info.InputType != livekit.IngressInput_URL_INPUT {
return ErrInvalidIngress("unsupported input type")
}
switch info.InputType {
case livekit.IngressInput_RTMP_INPUT,
livekit.IngressInput_WHIP_INPUT:
if info.StreamKey == "" {
return ErrInvalidIngress("no stream key")
}
case livekit.IngressInput_URL_INPUT:
if info.Url == "" {
return ErrInvalidIngress("no source URL")
}
if info.Enabled != nil && !*info.Enabled {
return ErrInvalidIngress("disabled non reusable ingress")
}
}
if info.ParticipantIdentity == "" {
return ErrInvalidIngress("no participant identity")
}
err := ValidateBypassTranscoding(info)
if err != nil {
return err
}
err = ValidateEnableTranscoding(info)
if err != nil {
return err
}
err = ValidateVideoOptionsConsistency(info.Video)
if err != nil {
return err
}
err = ValidateAudioOptionsConsistency(info.Audio)
if err != nil {
return err
}
return nil
}
func ValidateBypassTranscoding(info *livekit.IngressInfo) error {
if !info.BypassTranscoding {
return nil
}
if info.InputType != livekit.IngressInput_WHIP_INPUT {
return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type")
}
videoOptions := info.Video
if videoOptions != nil && videoOptions.EncodingOptions != nil {
return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding bypass is selected")
}
audioOptions := info.Audio
if audioOptions != nil && audioOptions.EncodingOptions != nil {
return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding bypass is selected")
}
return nil
}
func ValidateEnableTranscoding(info *livekit.IngressInfo) error {
var enableTranscoding bool
if info.InputType == livekit.IngressInput_WHIP_INPUT {
enableTranscoding = false
if info.EnableTranscoding != nil {
enableTranscoding = *info.EnableTranscoding
}
} else {
enableTranscoding = true
if info.EnableTranscoding != nil && *info.EnableTranscoding == false {
return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type")
}
}
if !enableTranscoding {
videoOptions := info.Video
if videoOptions != nil && videoOptions.EncodingOptions != nil {
return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding is disabled")
}
audioOptions := info.Audio
if audioOptions != nil && audioOptions.EncodingOptions != nil {
return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding is disabled")
}
}
return nil
}
func ValidateVideoOptionsConsistency(options *livekit.IngressVideoOptions) error {
if options == nil {
return nil
}
switch options.Source {
case livekit.TrackSource_UNKNOWN,
livekit.TrackSource_CAMERA,
livekit.TrackSource_SCREEN_SHARE:
default:
return NewInvalidVideoParamsError("invalid track source")
}
switch o := options.EncodingOptions.(type) {
case nil:
case *livekit.IngressVideoOptions_Preset:
_, ok := livekit.IngressVideoEncodingPreset_name[int32(o.Preset)]
if !ok {
return NewInvalidVideoParamsError("invalid preset")
}
case *livekit.IngressVideoOptions_Options:
err := ValidateVideoEncodingOptionsConsistency(o.Options)
if err != nil {
return err
}
}
return nil
}
func ValidateVideoEncodingOptionsConsistency(options *livekit.IngressVideoEncodingOptions) error {
if options == nil {
return NewInvalidVideoParamsError("empty options")
}
if options.FrameRate < 0 {
return NewInvalidVideoParamsError("invalid framerate")
}
switch options.VideoCodec {
case livekit.VideoCodec_DEFAULT_VC,
livekit.VideoCodec_H264_BASELINE,
livekit.VideoCodec_VP8:
default:
return NewInvalidVideoParamsError("video codec unsupported")
}
layersByQuality := make(map[livekit.VideoQuality]*livekit.VideoLayer)
for _, layer := range options.Layers {
if layer.Height == 0 || layer.Width == 0 {
return ErrInvalidOutputDimensions
}
if layer.Width%2 == 1 {
return ErrInvalidIngress("layer width must be even")
}
if layer.Height%2 == 1 {
return ErrInvalidIngress("layer height must be even")
}
if _, ok := layersByQuality[layer.Quality]; ok {
return NewInvalidVideoParamsError("more than one layer with the same quality level")
}
layersByQuality[layer.Quality] = layer
}
var oldW, oldH uint32
for q := livekit.VideoQuality_LOW; q <= livekit.VideoQuality_HIGH; q++ {
layer, ok := layersByQuality[q]
if !ok {
continue
}
if layer.Height < oldH {
return NewInvalidVideoParamsError("video layers do not have increasing height with increasing quality")
}
if layer.Width < oldW {
return NewInvalidVideoParamsError("video layers do not have increasing width with increasing quality")
}
oldW = layer.Width
oldH = layer.Height
}
return nil
}
func ValidateAudioOptionsConsistency(options *livekit.IngressAudioOptions) error {
if options == nil {
return nil
}
switch options.Source {
case livekit.TrackSource_UNKNOWN,
livekit.TrackSource_MICROPHONE,
livekit.TrackSource_SCREEN_SHARE_AUDIO:
default:
return NewInvalidAudioParamsError("invalid track source")
}
switch o := options.EncodingOptions.(type) {
case nil:
case *livekit.IngressAudioOptions_Preset:
_, ok := livekit.IngressAudioEncodingPreset_name[int32(o.Preset)]
if !ok {
return NewInvalidAudioParamsError("invalid preset")
}
case *livekit.IngressAudioOptions_Options:
err := ValidateAudioEncodingOptionsConsistency(o.Options)
if err != nil {
return err
}
}
return nil
}
func ValidateAudioEncodingOptionsConsistency(options *livekit.IngressAudioEncodingOptions) error {
if options == nil {
return NewInvalidAudioParamsError("empty options")
}
switch options.AudioCodec {
case livekit.AudioCodec_DEFAULT_AC,
livekit.AudioCodec_OPUS:
default:
return NewInvalidAudioParamsError("audio codec unsupported")
}
if options.Channels > 2 {
return NewInvalidAudioParamsError("invalid audio channel count")
}
return nil
}