To provide backward compatibility with already existing Ingresses --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
287 lines
7.6 KiB
Go
287 lines
7.6 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ingress
|
|
|
|
import (
|
|
"github.com/livekit/protocol/livekit"
|
|
)
|
|
|
|
// This validates that ingress options have no consistency issues and provide enough parameters
|
|
// to be usable by the ingress service. Options that pass this test may still need some fields to be poulated with default values
|
|
// before being used in a media pipeline.
|
|
|
|
func Validate(info *livekit.IngressInfo) error {
|
|
if info == nil {
|
|
return ErrInvalidIngress("missing IngressInfo")
|
|
}
|
|
|
|
// For now, require a room to be set. We should eventually allow changing the room on an active ingress
|
|
if info.RoomName == "" {
|
|
return ErrInvalidIngress("no room name")
|
|
}
|
|
|
|
return ValidateForSerialization(info)
|
|
}
|
|
|
|
// Sparse info with not all required fields populated are acceptable for serialization, provided values are consistent
|
|
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")
|
|
}
|
|
|
|
// Validate source
|
|
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:
|
|
// continue
|
|
default:
|
|
return NewInvalidVideoParamsError("invalid track source")
|
|
}
|
|
|
|
switch o := options.EncodingOptions.(type) {
|
|
case nil:
|
|
// Default, continue
|
|
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:
|
|
// continue
|
|
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:
|
|
// continue
|
|
default:
|
|
return NewInvalidAudioParamsError("invalid track source")
|
|
}
|
|
|
|
switch o := options.EncodingOptions.(type) {
|
|
case nil:
|
|
// Default, continue
|
|
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:
|
|
// continue
|
|
default:
|
|
return NewInvalidAudioParamsError("audio codec unsupported")
|
|
}
|
|
|
|
if options.Channels > 2 {
|
|
return NewInvalidAudioParamsError("invalid audio channel count")
|
|
}
|
|
|
|
return nil
|
|
}
|