github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/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  	}
    60  
    61  	if info.ParticipantIdentity == "" {
    62  		return ErrInvalidIngress("no participant identity")
    63  	}
    64  
    65  	err := ValidateBypassTranscoding(info)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	err = ValidateEnableTranscoding(info)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	err = ValidateVideoOptionsConsistency(info.Video)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	err = ValidateAudioOptionsConsistency(info.Audio)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	return nil
    86  
    87  }
    88  
    89  func ValidateBypassTranscoding(info *livekit.IngressInfo) error {
    90  	if !info.BypassTranscoding {
    91  		return nil
    92  	}
    93  
    94  	if info.InputType != livekit.IngressInput_WHIP_INPUT {
    95  		return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type")
    96  	}
    97  
    98  	videoOptions := info.Video
    99  	if videoOptions != nil && videoOptions.EncodingOptions != nil {
   100  		return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding bypass is selected")
   101  	}
   102  
   103  	audioOptions := info.Audio
   104  	if audioOptions != nil && audioOptions.EncodingOptions != nil {
   105  		return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding bypass is selected")
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  func ValidateEnableTranscoding(info *livekit.IngressInfo) error {
   112  	var enableTranscoding bool
   113  	if info.InputType == livekit.IngressInput_WHIP_INPUT {
   114  		enableTranscoding = false
   115  		if info.EnableTranscoding != nil {
   116  			enableTranscoding = *info.EnableTranscoding
   117  		}
   118  	} else {
   119  		enableTranscoding = true
   120  		if info.EnableTranscoding != nil && *info.EnableTranscoding == false {
   121  			return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type")
   122  		}
   123  	}
   124  
   125  	if !enableTranscoding {
   126  		videoOptions := info.Video
   127  		if videoOptions != nil && videoOptions.EncodingOptions != nil {
   128  			return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding is disabled")
   129  		}
   130  
   131  		audioOptions := info.Audio
   132  		if audioOptions != nil && audioOptions.EncodingOptions != nil {
   133  			return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding is disabled")
   134  		}
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func ValidateVideoOptionsConsistency(options *livekit.IngressVideoOptions) error {
   141  	if options == nil {
   142  		return nil
   143  	}
   144  
   145  	switch options.Source {
   146  	case livekit.TrackSource_UNKNOWN,
   147  		livekit.TrackSource_CAMERA,
   148  		livekit.TrackSource_SCREEN_SHARE:
   149  		// continue
   150  	default:
   151  		return NewInvalidVideoParamsError("invalid track source")
   152  	}
   153  
   154  	switch o := options.EncodingOptions.(type) {
   155  	case nil:
   156  		// Default, continue
   157  	case *livekit.IngressVideoOptions_Preset:
   158  		_, ok := livekit.IngressVideoEncodingPreset_name[int32(o.Preset)]
   159  		if !ok {
   160  			return NewInvalidVideoParamsError("invalid preset")
   161  		}
   162  
   163  	case *livekit.IngressVideoOptions_Options:
   164  		err := ValidateVideoEncodingOptionsConsistency(o.Options)
   165  		if err != nil {
   166  			return err
   167  		}
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  func ValidateVideoEncodingOptionsConsistency(options *livekit.IngressVideoEncodingOptions) error {
   174  	if options == nil {
   175  		return NewInvalidVideoParamsError("empty options")
   176  	}
   177  
   178  	if options.FrameRate < 0 {
   179  		return NewInvalidVideoParamsError("invalid framerate")
   180  	}
   181  
   182  	switch options.VideoCodec {
   183  	case livekit.VideoCodec_DEFAULT_VC,
   184  		livekit.VideoCodec_H264_BASELINE,
   185  		livekit.VideoCodec_VP8:
   186  		// continue
   187  	default:
   188  		return NewInvalidVideoParamsError("video codec unsupported")
   189  	}
   190  
   191  	layersByQuality := make(map[livekit.VideoQuality]*livekit.VideoLayer)
   192  
   193  	for _, layer := range options.Layers {
   194  		if layer.Height == 0 || layer.Width == 0 {
   195  			return ErrInvalidOutputDimensions
   196  		}
   197  
   198  		if layer.Width%2 == 1 {
   199  			return ErrInvalidIngress("layer width must be even")
   200  		}
   201  
   202  		if layer.Height%2 == 1 {
   203  			return ErrInvalidIngress("layer height must be even")
   204  		}
   205  
   206  		if _, ok := layersByQuality[layer.Quality]; ok {
   207  			return NewInvalidVideoParamsError("more than one layer with the same quality level")
   208  		}
   209  		layersByQuality[layer.Quality] = layer
   210  	}
   211  
   212  	var oldW, oldH uint32
   213  	for q := livekit.VideoQuality_LOW; q <= livekit.VideoQuality_HIGH; q++ {
   214  		layer, ok := layersByQuality[q]
   215  		if !ok {
   216  			continue
   217  		}
   218  
   219  		if layer.Height < oldH {
   220  			return NewInvalidVideoParamsError("video layers do not have increasing height with increasing quality")
   221  		}
   222  		if layer.Width < oldW {
   223  			return NewInvalidVideoParamsError("video layers do not have increasing width with increasing quality")
   224  		}
   225  		oldW = layer.Width
   226  		oldH = layer.Height
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  func ValidateAudioOptionsConsistency(options *livekit.IngressAudioOptions) error {
   233  	if options == nil {
   234  		return nil
   235  	}
   236  
   237  	switch options.Source {
   238  	case livekit.TrackSource_UNKNOWN,
   239  		livekit.TrackSource_MICROPHONE,
   240  		livekit.TrackSource_SCREEN_SHARE_AUDIO:
   241  		// continue
   242  	default:
   243  		return NewInvalidAudioParamsError("invalid track source")
   244  	}
   245  
   246  	switch o := options.EncodingOptions.(type) {
   247  	case nil:
   248  		// Default, continue
   249  	case *livekit.IngressAudioOptions_Preset:
   250  		_, ok := livekit.IngressAudioEncodingPreset_name[int32(o.Preset)]
   251  		if !ok {
   252  			return NewInvalidAudioParamsError("invalid preset")
   253  		}
   254  
   255  	case *livekit.IngressAudioOptions_Options:
   256  		err := ValidateAudioEncodingOptionsConsistency(o.Options)
   257  		if err != nil {
   258  			return err
   259  		}
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  func ValidateAudioEncodingOptionsConsistency(options *livekit.IngressAudioEncodingOptions) error {
   266  	if options == nil {
   267  		return NewInvalidAudioParamsError("empty options")
   268  	}
   269  
   270  	switch options.AudioCodec {
   271  	case livekit.AudioCodec_DEFAULT_AC,
   272  		livekit.AudioCodec_OPUS:
   273  		// continue
   274  	default:
   275  		return NewInvalidAudioParamsError("audio codec unsupported")
   276  	}
   277  
   278  	if options.Channels > 2 {
   279  		return NewInvalidAudioParamsError("invalid audio channel count")
   280  	}
   281  
   282  	return nil
   283  }