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  }