github.com/whoyao/protocol@v0.0.0-20230519045905-2d8ace718ca5/ingress/validation.go (about) 1 package ingress 2 3 import ( 4 "github.com/whoyao/protocol/livekit" 5 ) 6 7 // This validates that ingress options have no consistency issues and provide enough parameters 8 // to be usable by the ingress service. Options that pass this test may still need some fields to be poulated with default values 9 // before being used in a media pipeline. 10 11 func Validate(info *livekit.IngressInfo) error { 12 if info == nil { 13 return ErrInvalidIngress("missing IngressInfo") 14 } 15 16 // For now, require a room to be set. We should eventually allow changing the room on an active ingress 17 if info.RoomName == "" { 18 return ErrInvalidIngress("no room name") 19 } 20 21 return ValidateForSerialization(info) 22 } 23 24 // Sparse info with not all required fields populated are acceptable for serialization, provided values are consistent 25 func ValidateForSerialization(info *livekit.IngressInfo) error { 26 if info == nil { 27 return ErrInvalidIngress("missing IngressInfo") 28 } 29 30 if info.InputType != livekit.IngressInput_RTMP_INPUT && info.InputType != livekit.IngressInput_WHIP_INPUT { 31 return ErrInvalidIngress("unsupported input type") 32 } 33 34 if info.StreamKey == "" { 35 return ErrInvalidIngress("no stream key") 36 } 37 38 if info.ParticipantIdentity == "" { 39 return ErrInvalidIngress("no participant identity") 40 } 41 42 err := ValidateVideoOptionsConsistency(info.Video) 43 if err != nil { 44 return err 45 } 46 47 err = ValidateAudioOptionsConsistency(info.Audio) 48 if err != nil { 49 return err 50 } 51 52 return nil 53 54 } 55 56 func ValidateVideoOptionsConsistency(options *livekit.IngressVideoOptions) error { 57 if options == nil { 58 return nil 59 } 60 61 switch options.Source { 62 case livekit.TrackSource_UNKNOWN, 63 livekit.TrackSource_CAMERA, 64 livekit.TrackSource_SCREEN_SHARE: 65 // continue 66 default: 67 return NewInvalidVideoParamsError("invalid track source") 68 } 69 70 switch o := options.EncodingOptions.(type) { 71 case nil: 72 // Default, continue 73 case *livekit.IngressVideoOptions_Preset: 74 _, ok := livekit.IngressVideoEncodingPreset_name[int32(o.Preset)] 75 if !ok { 76 return NewInvalidVideoParamsError("invalid preset") 77 } 78 79 case *livekit.IngressVideoOptions_Options: 80 err := ValidateVideoEncodingOptionsConsistency(o.Options) 81 if err != nil { 82 return err 83 } 84 } 85 86 return nil 87 } 88 89 func ValidateVideoEncodingOptionsConsistency(options *livekit.IngressVideoEncodingOptions) error { 90 if options == nil { 91 return NewInvalidVideoParamsError("empty options") 92 } 93 94 if options.FrameRate < 0 { 95 return NewInvalidVideoParamsError("invalid framerate") 96 } 97 98 switch options.VideoCodec { 99 case livekit.VideoCodec_DEFAULT_VC, 100 livekit.VideoCodec_H264_BASELINE, 101 livekit.VideoCodec_VP8: 102 // continue 103 default: 104 return NewInvalidVideoParamsError("video codec unsupported") 105 } 106 107 layersByQuality := make(map[livekit.VideoQuality]*livekit.VideoLayer) 108 109 for _, layer := range options.Layers { 110 if layer.Height == 0 || layer.Width == 0 { 111 return ErrInvalidOutputDimensions 112 } 113 114 if layer.Width%2 == 1 { 115 return ErrInvalidIngress("layer width must be even") 116 } 117 118 if layer.Height%2 == 1 { 119 return ErrInvalidIngress("layer height must be even") 120 } 121 122 if _, ok := layersByQuality[layer.Quality]; ok { 123 return NewInvalidVideoParamsError("more than one layer with the same quality level") 124 } 125 layersByQuality[layer.Quality] = layer 126 } 127 128 var oldLayerArea uint32 129 for q := livekit.VideoQuality_LOW; q <= livekit.VideoQuality_HIGH; q++ { 130 layer, ok := layersByQuality[q] 131 if !ok { 132 continue 133 } 134 layerArea := layer.Width * layer.Height 135 136 if layerArea <= oldLayerArea { 137 return NewInvalidVideoParamsError("video layers do not have increasing pixel count with increasing quality") 138 } 139 oldLayerArea = layerArea 140 } 141 142 return nil 143 } 144 145 func ValidateAudioOptionsConsistency(options *livekit.IngressAudioOptions) error { 146 if options == nil { 147 return nil 148 } 149 150 switch options.Source { 151 case livekit.TrackSource_UNKNOWN, 152 livekit.TrackSource_MICROPHONE, 153 livekit.TrackSource_SCREEN_SHARE_AUDIO: 154 // continue 155 default: 156 return NewInvalidAudioParamsError("invalid track source") 157 } 158 159 switch o := options.EncodingOptions.(type) { 160 case nil: 161 // Default, continue 162 case *livekit.IngressAudioOptions_Preset: 163 _, ok := livekit.IngressAudioEncodingPreset_name[int32(o.Preset)] 164 if !ok { 165 return NewInvalidAudioParamsError("invalid preset") 166 } 167 168 case *livekit.IngressAudioOptions_Options: 169 err := ValidateAudioEncodingOptionsConsistency(o.Options) 170 if err != nil { 171 return err 172 } 173 } 174 175 return nil 176 } 177 178 func ValidateAudioEncodingOptionsConsistency(options *livekit.IngressAudioEncodingOptions) error { 179 if options == nil { 180 return NewInvalidAudioParamsError("empty options") 181 } 182 183 switch options.AudioCodec { 184 case livekit.AudioCodec_DEFAULT_AC, 185 livekit.AudioCodec_OPUS: 186 // continue 187 default: 188 return NewInvalidAudioParamsError("audio codec unsupported") 189 } 190 191 if options.Channels > 2 { 192 return NewInvalidAudioParamsError("invalid audio channel count") 193 } 194 195 return nil 196 }