88
99static const int32_t XCWMaximumEncodedDimension = 1920 ;
1010static const int32_t XCWMaximumSoftwareEncodedDimension = 1600 ;
11+ static const int32_t XCWMaximumLowLatencySoftwareEncodedDimension = 1472 ;
1112static const int32_t XCWTargetRealTimeFrameRate = 60 ;
1213static const int32_t XCWTargetSoftwareFrameRate = 60 ;
14+ static const int32_t XCWTargetLowLatencySoftwareFrameRate = 45 ;
1315static const NSUInteger XCWMaximumInFlightFrames = 2 ;
1416static const int32_t XCWMinimumAverageBitRate = 18000000 ;
1517static const int32_t XCWMinimumSoftwareAverageBitRate = 3000000 ;
18+ static const int32_t XCWMinimumLowLatencySoftwareAverageBitRate = 2500000 ;
1619static const int64_t XCWBitsPerPixelBudget = 10 ;
1720static const int64_t XCWSoftwareBitsPerPixelBudget = 6 ;
21+ static const int64_t XCWLowLatencySoftwareBitsPerPixelBudget = 4 ;
1822static const uint64_t XCWSoftwareMinimumFrameIntervalUs = 16667 ;
1923static const uint64_t XCWSoftwareInitialFrameIntervalUs = 16667 ;
2024static const uint64_t XCWSoftwareMaximumFrameIntervalUs = 50000 ;
2125static const uint64_t XCWSoftwareFrameIntervalStepUs = 5556 ;
2226static const NSUInteger XCWSoftwareHealthyFrameWindow = 4 ;
27+ static const uint64_t XCWLowLatencySoftwareMinimumFrameIntervalUs = 22222 ;
28+ static const uint64_t XCWLowLatencySoftwareInitialFrameIntervalUs = 22222 ;
29+ static const uint64_t XCWLowLatencySoftwareMaximumFrameIntervalUs = 100000 ;
30+ static const uint64_t XCWLowLatencySoftwareFrameIntervalStepUs = 11111 ;
31+ static const NSUInteger XCWLowLatencySoftwareHealthyFrameWindow = 8 ;
2332
2433typedef NS_ENUM (NSUInteger , XCWVideoEncoderMode) {
2534 XCWVideoEncoderModeHEVCHardware,
@@ -39,6 +48,15 @@ static XCWVideoEncoderMode XCWVideoEncoderModeFromEnvironment(void) {
3948 return XCWVideoEncoderModeHEVCHardware;
4049}
4150
51+ static BOOL XCWLowLatencyModeFromEnvironment (void ) {
52+ const char *rawValue = getenv (" SIMDECK_LOW_LATENCY" );
53+ NSString *value = rawValue != NULL ? [[[NSString alloc ] initWithUTF8String: rawValue] lowercaseString ] : @" " ;
54+ return [value isEqualToString: @" 1" ] ||
55+ [value isEqualToString: @" true" ] ||
56+ [value isEqualToString: @" yes" ] ||
57+ [value isEqualToString: @" on" ];
58+ }
59+
4260static CMVideoCodecType XCWVideoCodecTypeForMode (XCWVideoEncoderMode mode) {
4361 switch (mode) {
4462 case XCWVideoEncoderModeH264Hardware:
@@ -242,14 +260,17 @@ static int32_t XCWRoundToEvenDimension(double value) {
242260 return rounded;
243261}
244262
245- static CGSize XCWScaledDimensionsForSourceSize (int32_t width, int32_t height, XCWVideoEncoderMode mode) {
263+ static CGSize XCWScaledDimensionsForSourceSize (int32_t width, int32_t height, XCWVideoEncoderMode mode, BOOL lowLatencyMode ) {
246264 if (width <= 0 || height <= 0 ) {
247265 return CGSizeZero;
248266 }
249267
250- int32_t maximumDimension = mode == XCWVideoEncoderModeH264Software
251- ? XCWMaximumSoftwareEncodedDimension
252- : XCWMaximumEncodedDimension;
268+ int32_t maximumDimension = XCWMaximumEncodedDimension;
269+ if (mode == XCWVideoEncoderModeH264Software) {
270+ maximumDimension = lowLatencyMode
271+ ? XCWMaximumLowLatencySoftwareEncodedDimension
272+ : XCWMaximumSoftwareEncodedDimension;
273+ }
253274 int32_t longestEdge = MAX (width, height);
254275 if (longestEdge <= maximumDimension) {
255276 return CGSizeMake (width, height);
@@ -260,13 +281,17 @@ static CGSize XCWScaledDimensionsForSourceSize(int32_t width, int32_t height, XC
260281 XCWRoundToEvenDimension (height * scale));
261282}
262283
263- static int32_t XCWAverageBitRateForDimensions (int32_t width, int32_t height, XCWVideoEncoderMode mode) {
264- int64_t bitsPerPixelBudget = mode == XCWVideoEncoderModeH264Software
265- ? XCWSoftwareBitsPerPixelBudget
266- : XCWBitsPerPixelBudget;
267- int64_t minimumAverageBitRate = mode == XCWVideoEncoderModeH264Software
268- ? XCWMinimumSoftwareAverageBitRate
269- : XCWMinimumAverageBitRate;
284+ static int32_t XCWAverageBitRateForDimensions (int32_t width, int32_t height, XCWVideoEncoderMode mode, BOOL lowLatencyMode) {
285+ int64_t bitsPerPixelBudget = XCWBitsPerPixelBudget;
286+ int64_t minimumAverageBitRate = XCWMinimumAverageBitRate;
287+ if (mode == XCWVideoEncoderModeH264Software) {
288+ bitsPerPixelBudget = lowLatencyMode
289+ ? XCWLowLatencySoftwareBitsPerPixelBudget
290+ : XCWSoftwareBitsPerPixelBudget;
291+ minimumAverageBitRate = lowLatencyMode
292+ ? XCWMinimumLowLatencySoftwareAverageBitRate
293+ : XCWMinimumSoftwareAverageBitRate;
294+ }
270295 int64_t computedBitRate = (int64_t )width * (int64_t )height * bitsPerPixelBudget;
271296 if (computedBitRate < minimumAverageBitRate) {
272297 computedBitRate = minimumAverageBitRate;
@@ -364,6 +389,7 @@ @implementation XCWH264Encoder {
364389 CVPixelBufferRef _scaledPixelBuffer;
365390 OSType _scaledPixelFormat;
366391 XCWVideoEncoderMode _encoderMode;
392+ BOOL _lowLatencyMode;
367393 CMVideoCodecType _codecType;
368394 BOOL _hardwareAccelerated;
369395 NSUInteger _inputFrameCount;
@@ -397,8 +423,9 @@ - (instancetype)initWithOutputHandler:(XCWH264EncoderOutputHandler)outputHandler
397423 _pendingLock = OS_UNFAIR_LOCK_INIT;
398424 _needsKeyFrame = YES ;
399425 _encoderMode = XCWVideoEncoderModeFromEnvironment ();
426+ _lowLatencyMode = XCWLowLatencyModeFromEnvironment ();
400427 _codecType = XCWVideoCodecTypeForMode (_encoderMode);
401- _softwareFrameIntervalUs = XCWSoftwareInitialFrameIntervalUs ;
428+ _softwareFrameIntervalUs = [ self initialSoftwareFrameIntervalUsLocked ] ;
402429 return self;
403430}
404431
@@ -441,6 +468,28 @@ - (void)requestKeyFrame {
441468 });
442469}
443470
471+ - (void )reconfigureWithCurrentEnvironment {
472+ os_unfair_lock_lock (&_pendingLock);
473+ if (_pendingPixelBuffer != NULL ) {
474+ CVPixelBufferRelease (_pendingPixelBuffer);
475+ _pendingPixelBuffer = NULL ;
476+ }
477+ _drainScheduled = NO ;
478+ os_unfair_lock_unlock (&_pendingLock);
479+
480+ dispatch_sync (_queue, ^{
481+ [self invalidateCompressionSessionLocked ];
482+ self->_encoderMode = XCWVideoEncoderModeFromEnvironment ();
483+ self->_lowLatencyMode = XCWLowLatencyModeFromEnvironment ();
484+ self->_codecType = XCWVideoCodecTypeForMode (self->_encoderMode );
485+ self->_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked ];
486+ self->_lastSoftwareSubmissionUs = 0 ;
487+ self->_softwarePacedFrameCount = 0 ;
488+ self->_softwareHealthyFrameCount = 0 ;
489+ self->_needsKeyFrame = YES ;
490+ });
491+ }
492+
444493- (NSDictionary *)statsRepresentation {
445494 __block NSUInteger inputFrameCount = 0 ;
446495 __block NSUInteger pendingReplacementCount = 0 ;
@@ -466,6 +515,7 @@ - (NSDictionary *)statsRepresentation {
466515 @" softwarePacedFrames" : @(self->_softwarePacedFrameCount ),
467516 @" transportCodec" : XCWCodecName (self->_codecType ),
468517 @" encoderMode" : XCWVideoEncoderModeName (self->_encoderMode ),
518+ @" lowLatencyMode" : @(self->_lowLatencyMode ),
469519 @" encoderId" : XCWVideoEncoderIDForMode (self->_encoderMode ) ?: @" automatic" ,
470520 @" hardwareAccelerated" : @(self->_hardwareAccelerated ),
471521 @" lastSessionStatus" : @(self->_lastSessionStatus ),
@@ -492,12 +542,43 @@ - (void)invalidate {
492542 os_unfair_lock_unlock (&_pendingLock);
493543}
494544
545+ - (NSUInteger )maximumInFlightFrameCountLocked {
546+ return (_encoderMode == XCWVideoEncoderModeH264Software && _lowLatencyMode) ? 1 : XCWMaximumInFlightFrames;
547+ }
548+
549+ - (uint64_t )minimumSoftwareFrameIntervalUsLocked {
550+ return _lowLatencyMode ? XCWLowLatencySoftwareMinimumFrameIntervalUs : XCWSoftwareMinimumFrameIntervalUs;
551+ }
552+
553+ - (uint64_t )initialSoftwareFrameIntervalUsLocked {
554+ return _lowLatencyMode ? XCWLowLatencySoftwareInitialFrameIntervalUs : XCWSoftwareInitialFrameIntervalUs;
555+ }
556+
557+ - (uint64_t )maximumSoftwareFrameIntervalUsLocked {
558+ return _lowLatencyMode ? XCWLowLatencySoftwareMaximumFrameIntervalUs : XCWSoftwareMaximumFrameIntervalUs;
559+ }
560+
561+ - (uint64_t )softwareFrameIntervalStepUsLocked {
562+ return _lowLatencyMode ? XCWLowLatencySoftwareFrameIntervalStepUs : XCWSoftwareFrameIntervalStepUs;
563+ }
564+
565+ - (NSUInteger )softwareHealthyFrameWindowLocked {
566+ return _lowLatencyMode ? XCWLowLatencySoftwareHealthyFrameWindow : XCWSoftwareHealthyFrameWindow;
567+ }
568+
569+ - (int32_t )expectedFrameRateLocked {
570+ if (_encoderMode == XCWVideoEncoderModeH264Software) {
571+ return _lowLatencyMode ? XCWTargetLowLatencySoftwareFrameRate : XCWTargetSoftwareFrameRate;
572+ }
573+ return XCWTargetRealTimeFrameRate;
574+ }
575+
495576- (BOOL )shouldPaceSoftwareFrameAtTimeUs : (uint64_t )nowUs {
496577 if (_encoderMode != XCWVideoEncoderModeH264Software || _needsKeyFrame) {
497578 return NO ;
498579 }
499580 if (_softwareFrameIntervalUs == 0 ) {
500- _softwareFrameIntervalUs = XCWSoftwareInitialFrameIntervalUs ;
581+ _softwareFrameIntervalUs = [ self initialSoftwareFrameIntervalUsLocked ] ;
501582 }
502583 if (_lastSoftwareSubmissionUs == 0 ) {
503584 return NO ;
@@ -515,29 +596,32 @@ - (void)adaptSoftwarePacingForLatencyUs:(uint64_t)latencyUs {
515596 return ;
516597 }
517598 if (_softwareFrameIntervalUs == 0 ) {
518- _softwareFrameIntervalUs = XCWSoftwareInitialFrameIntervalUs ;
599+ _softwareFrameIntervalUs = [ self initialSoftwareFrameIntervalUsLocked ] ;
519600 }
520601
521- uint64_t highLatencyBudgetUs = ( _softwareFrameIntervalUs * 3 ) / 2 ;
602+ uint64_t highLatencyBudgetUs = _lowLatencyMode ? _softwareFrameIntervalUs : (( _softwareFrameIntervalUs * 3 ) / 2 ) ;
522603 if (latencyUs > highLatencyBudgetUs) {
523- uint64_t nextIntervalUs = _softwareFrameIntervalUs + XCWSoftwareFrameIntervalStepUs;
524- uint64_t latencyBoundIntervalUs = latencyUs + XCWSoftwareFrameIntervalStepUs;
604+ uint64_t stepUs = [self softwareFrameIntervalStepUsLocked ];
605+ uint64_t nextIntervalUs = _softwareFrameIntervalUs + stepUs;
606+ uint64_t latencyBoundIntervalUs = latencyUs + stepUs;
525607 if (nextIntervalUs < latencyBoundIntervalUs) {
526608 nextIntervalUs = latencyBoundIntervalUs;
527609 }
528- _softwareFrameIntervalUs = MIN (nextIntervalUs, XCWSoftwareMaximumFrameIntervalUs );
610+ _softwareFrameIntervalUs = MIN (nextIntervalUs, [ self maximumSoftwareFrameIntervalUsLocked ] );
529611 _softwareHealthyFrameCount = 0 ;
530612 return ;
531613 }
532614
533615 if (latencyUs < _softwareFrameIntervalUs &&
534- _softwareFrameIntervalUs > XCWSoftwareMinimumFrameIntervalUs ) {
616+ _softwareFrameIntervalUs > [ self minimumSoftwareFrameIntervalUsLocked ] ) {
535617 _softwareHealthyFrameCount += 1 ;
536- if (_softwareHealthyFrameCount >= XCWSoftwareHealthyFrameWindow) {
537- uint64_t nextIntervalUs = _softwareFrameIntervalUs > XCWSoftwareFrameIntervalStepUs
538- ? _softwareFrameIntervalUs - XCWSoftwareFrameIntervalStepUs
539- : XCWSoftwareMinimumFrameIntervalUs;
540- _softwareFrameIntervalUs = MAX (nextIntervalUs, XCWSoftwareMinimumFrameIntervalUs);
618+ if (_softwareHealthyFrameCount >= [self softwareHealthyFrameWindowLocked ]) {
619+ uint64_t stepUs = [self softwareFrameIntervalStepUsLocked ];
620+ uint64_t minimumIntervalUs = [self minimumSoftwareFrameIntervalUsLocked ];
621+ uint64_t nextIntervalUs = _softwareFrameIntervalUs > stepUs
622+ ? _softwareFrameIntervalUs - stepUs
623+ : minimumIntervalUs;
624+ _softwareFrameIntervalUs = MAX (nextIntervalUs, minimumIntervalUs);
541625 _softwareHealthyFrameCount = 0 ;
542626 }
543627 return ;
@@ -548,7 +632,7 @@ - (void)adaptSoftwarePacingForLatencyUs:(uint64_t)latencyUs {
548632
549633- (void )drainPendingFramesLocked {
550634 while (YES ) {
551- if (_inFlightFrameCount >= XCWMaximumInFlightFrames ) {
635+ if (_inFlightFrameCount >= [ self maximumInFlightFrameCountLocked ] ) {
552636 _drainScheduled = NO ;
553637 return ;
554638 }
@@ -576,7 +660,7 @@ - (BOOL)encodePixelBufferLocked:(CVPixelBufferRef)pixelBuffer {
576660 return NO ;
577661 }
578662
579- CGSize targetSize = XCWScaledDimensionsForSourceSize (sourceWidth, sourceHeight, _encoderMode);
663+ CGSize targetSize = XCWScaledDimensionsForSourceSize (sourceWidth, sourceHeight, _encoderMode, _lowLatencyMode );
580664 int32_t targetWidth = (int32_t )targetSize.width ;
581665 int32_t targetHeight = (int32_t )targetSize.height ;
582666 if (targetWidth <= 0 || targetHeight <= 0 ) {
@@ -683,10 +767,8 @@ - (BOOL)ensureCompressionSessionWithWidth:(int32_t)width height:(int32_t)height
683767 _timestampOriginUs = 0 ;
684768 _needsKeyFrame = YES ;
685769
686- int expectedFrameRate = _encoderMode == XCWVideoEncoderModeH264Software
687- ? XCWTargetSoftwareFrameRate
688- : XCWTargetRealTimeFrameRate;
689- int averageBitRate = XCWAverageBitRateForDimensions (width, height, _encoderMode);
770+ int expectedFrameRate = [self expectedFrameRateLocked ];
771+ int averageBitRate = XCWAverageBitRateForDimensions (width, height, _encoderMode, _lowLatencyMode);
690772
691773 VTSessionSetProperty (session, kVTCompressionPropertyKey_RealTime , kCFBooleanTrue );
692774 if (@available (macOS 10.14 , *)) {
@@ -713,8 +795,8 @@ - (BOOL)ensureCompressionSessionWithWidth:(int32_t)width height:(int32_t)height
713795 VTSessionSetProperty (session, kVTCompressionPropertyKey_ProfileLevel , kVTProfileLevel_H264_High_AutoLevel );
714796 }
715797 VTSessionSetProperty (session, kVTCompressionPropertyKey_ExpectedFrameRate , (__bridge CFTypeRef)@(expectedFrameRate));
716- VTSessionSetProperty (session, kVTCompressionPropertyKey_MaxKeyFrameInterval , (__bridge CFTypeRef)@(expectedFrameRate * 2 ));
717- VTSessionSetProperty (session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration , (__bridge CFTypeRef)@2.0 );
798+ VTSessionSetProperty (session, kVTCompressionPropertyKey_MaxKeyFrameInterval , (__bridge CFTypeRef)@(_lowLatencyMode ? expectedFrameRate : expectedFrameRate * 2 ));
799+ VTSessionSetProperty (session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration , (__bridge CFTypeRef)@(_lowLatencyMode ? 1.0 : 2.0 ) );
718800 VTSessionSetProperty (session, kVTCompressionPropertyKey_AverageBitRate , (__bridge CFTypeRef)@(averageBitRate));
719801 if (@available (macOS 11.0 , *)) {
720802 VTSessionSetProperty (session,
0 commit comments