github.com/livekit/protocol@v1.39.3/ingress/validation.go (about) 1 // Copyright 2023 LiveKit, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ingress 16 17 import ( 18 "github.com/livekit/protocol/livekit" 19 ) 20 21 // This validates that ingress options have no consistency issues and provide enough parameters 22 // to be usable by the ingress service. Options that pass this test may still need some fields to be poulated with default values 23 // before being used in a media pipeline. 24 25 func Validate(info *livekit.IngressInfo) error { 26 if info == nil { 27 return ErrInvalidIngress("missing IngressInfo") 28 } 29 30 // For now, require a room to be set. We should eventually allow changing the room on an active ingress 31 if info.RoomName == "" { 32 return ErrInvalidIngress("no room name") 33 } 34 35 return ValidateForSerialization(info) 36 } 37 38 // Sparse info with not all required fields populated are acceptable for serialization, provided values are consistent 39 func ValidateForSerialization(info *livekit.IngressInfo) error { 40 if info == nil { 41 return ErrInvalidIngress("missing IngressInfo") 42 } 43 44 if info.InputType != livekit.IngressInput_RTMP_INPUT && info.InputType != livekit.IngressInput_WHIP_INPUT && info.InputType != livekit.IngressInput_URL_INPUT { 45 return ErrInvalidIngress("unsupported input type") 46 } 47 48 // Validate source 49 switch info.InputType { 50 case livekit.IngressInput_RTMP_INPUT, 51 livekit.IngressInput_WHIP_INPUT: 52 if info.StreamKey == "" { 53 return ErrInvalidIngress("no stream key") 54 } 55 case livekit.IngressInput_URL_INPUT: 56 if info.Url == "" { 57 return ErrInvalidIngress("no source URL") 58 } 59 if info.Enabled != nil && !*info.Enabled { 60 return ErrInvalidIngress("disabled non reusable ingress") 61 } 62 } 63 64 if info.ParticipantIdentity == "" { 65 return ErrInvalidIngress("no participant identity") 66 } 67 68 err := ValidateBypassTranscoding(info) 69 if err != nil { 70 return err 71 } 72 73 err = ValidateEnableTranscoding(info) 74 if err != nil { 75 return err 76 } 77 78 err = ValidateVideoOptionsConsistency(info.Video) 79 if err != nil { 80 return err 81 } 82 83 err = ValidateAudioOptionsConsistency(info.Audio) 84 if err != nil { 85 return err 86 } 87 88 return nil 89 90 } 91 92 func ValidateBypassTranscoding(info *livekit.IngressInfo) error { 93 if !info.BypassTranscoding { 94 return nil 95 } 96 97 if info.InputType != livekit.IngressInput_WHIP_INPUT { 98 return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type") 99 } 100 101 videoOptions := info.Video 102 if videoOptions != nil && videoOptions.EncodingOptions != nil { 103 return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding bypass is selected") 104 } 105 106 audioOptions := info.Audio 107 if audioOptions != nil && audioOptions.EncodingOptions != nil { 108 return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding bypass is selected") 109 } 110 111 return nil 112 } 113 114 func ValidateEnableTranscoding(info *livekit.IngressInfo) error { 115 var enableTranscoding bool 116 if info.InputType == livekit.IngressInput_WHIP_INPUT { 117 enableTranscoding = false 118 if info.EnableTranscoding != nil { 119 enableTranscoding = *info.EnableTranscoding 120 } 121 } else { 122 enableTranscoding = true 123 if info.EnableTranscoding != nil && *info.EnableTranscoding == false { 124 return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type") 125 } 126 } 127 128 if !enableTranscoding { 129 videoOptions := info.Video 130 if videoOptions != nil && videoOptions.EncodingOptions != nil { 131 return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding is disabled") 132 } 133 134 audioOptions := info.Audio 135 if audioOptions != nil && audioOptions.EncodingOptions != nil { 136 return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding is disabled") 137 } 138 } 139 140 return nil 141 } 142 143 func ValidateVideoOptionsConsistency(options *livekit.IngressVideoOptions) error { 144 if options == nil { 145 return nil 146 } 147 148 switch options.Source { 149 case livekit.TrackSource_UNKNOWN, 150 livekit.TrackSource_CAMERA, 151 livekit.TrackSource_SCREEN_SHARE: 152 // continue 153 default: 154 return NewInvalidVideoParamsError("invalid track source") 155 } 156 157 switch o := options.EncodingOptions.(type) { 158 case nil: 159 // Default, continue 160 case *livekit.IngressVideoOptions_Preset: 161 _, ok := livekit.IngressVideoEncodingPreset_name[int32(o.Preset)] 162 if !ok { 163 return NewInvalidVideoParamsError("invalid preset") 164 } 165 166 case *livekit.IngressVideoOptions_Options: 167 err := ValidateVideoEncodingOptionsConsistency(o.Options) 168 if err != nil { 169 return err 170 } 171 } 172 173 return nil 174 } 175 176 func ValidateVideoEncodingOptionsConsistency(options *livekit.IngressVideoEncodingOptions) error { 177 if options == nil { 178 return NewInvalidVideoParamsError("empty options") 179 } 180 181 if options.FrameRate < 0 { 182 return NewInvalidVideoParamsError("invalid framerate") 183 } 184 185 switch options.VideoCodec { 186 case livekit.VideoCodec_DEFAULT_VC, 187 livekit.VideoCodec_H264_BASELINE, 188 livekit.VideoCodec_VP8: 189 // continue 190 default: 191 return NewInvalidVideoParamsError("video codec unsupported") 192 } 193 194 layersByQuality := make(map[livekit.VideoQuality]*livekit.VideoLayer) 195 196 for _, layer := range options.Layers { 197 if layer.Height == 0 || layer.Width == 0 { 198 return ErrInvalidOutputDimensions 199 } 200 201 if layer.Width%2 == 1 { 202 return ErrInvalidIngress("layer width must be even") 203 } 204 205 if layer.Height%2 == 1 { 206 return ErrInvalidIngress("layer height must be even") 207 } 208 209 if _, ok := layersByQuality[layer.Quality]; ok { 210 return NewInvalidVideoParamsError("more than one layer with the same quality level") 211 } 212 layersByQuality[layer.Quality] = layer 213 } 214 215 var oldW, oldH uint32 216 for q := livekit.VideoQuality_LOW; q <= livekit.VideoQuality_HIGH; q++ { 217 layer, ok := layersByQuality[q] 218 if !ok { 219 continue 220 } 221 222 if layer.Height < oldH { 223 return NewInvalidVideoParamsError("video layers do not have increasing height with increasing quality") 224 } 225 if layer.Width < oldW { 226 return NewInvalidVideoParamsError("video layers do not have increasing width with increasing quality") 227 } 228 oldW = layer.Width 229 oldH = layer.Height 230 } 231 232 return nil 233 } 234 235 func ValidateAudioOptionsConsistency(options *livekit.IngressAudioOptions) error { 236 if options == nil { 237 return nil 238 } 239 240 switch options.Source { 241 case livekit.TrackSource_UNKNOWN, 242 livekit.TrackSource_MICROPHONE, 243 livekit.TrackSource_SCREEN_SHARE_AUDIO: 244 // continue 245 default: 246 return NewInvalidAudioParamsError("invalid track source") 247 } 248 249 switch o := options.EncodingOptions.(type) { 250 case nil: 251 // Default, continue 252 case *livekit.IngressAudioOptions_Preset: 253 _, ok := livekit.IngressAudioEncodingPreset_name[int32(o.Preset)] 254 if !ok { 255 return NewInvalidAudioParamsError("invalid preset") 256 } 257 258 case *livekit.IngressAudioOptions_Options: 259 err := ValidateAudioEncodingOptionsConsistency(o.Options) 260 if err != nil { 261 return err 262 } 263 } 264 265 return nil 266 } 267 268 func ValidateAudioEncodingOptionsConsistency(options *livekit.IngressAudioEncodingOptions) error { 269 if options == nil { 270 return NewInvalidAudioParamsError("empty options") 271 } 272 273 switch options.AudioCodec { 274 case livekit.AudioCodec_DEFAULT_AC, 275 livekit.AudioCodec_OPUS: 276 // continue 277 default: 278 return NewInvalidAudioParamsError("audio codec unsupported") 279 } 280 281 if options.Channels > 2 { 282 return NewInvalidAudioParamsError("invalid audio channel count") 283 } 284 285 return nil 286 }