4242static const double XCWEncoderStrainedLoadPercent = 85.0 ;
4343static const double XCWEncoderOverloadedLoadPercent = 105.0 ;
4444static const NSUInteger XCWEncoderConsecutiveOverBudgetFrameThreshold = 3 ;
45+ static const uint64_t XCWAutoHardwareRetryIntervalUs = 10000000 ;
4546
4647typedef NS_ENUM (NSUInteger , XCWVideoEncoderMode) {
4748 XCWVideoEncoderModeAuto,
@@ -507,6 +508,7 @@ @implementation XCWH264Encoder {
507508 OSType _scaledPixelFormat;
508509 BOOL _scalingActive;
509510 XCWVideoEncoderMode _encoderMode;
511+ XCWVideoEncoderMode _activeEncoderMode;
510512 BOOL _lowLatencyMode;
511513 BOOL _realtimeStreamMode;
512514 CMVideoCodecType _codecType;
@@ -533,6 +535,9 @@ @implementation XCWH264Encoder {
533535 uint64_t _hardwareFrameIntervalUs;
534536 uint64_t _lastHardwareSubmissionUs;
535537 NSUInteger _hardwarePacedFrameCount;
538+ uint64_t _autoSoftwareFallbackUntilUs;
539+ NSUInteger _autoSoftwareFallbackCount;
540+ NSUInteger _autoHardwareRetryCount;
536541 NSString *_selectedEncoderID;
537542 NSInteger _lastSessionStatus;
538543 NSInteger _lastPrepareStatus;
@@ -553,9 +558,10 @@ - (instancetype)initWithOutputHandler:(XCWH264EncoderOutputHandler)outputHandler
553558 _pendingLock = OS_UNFAIR_LOCK_INIT ;
554559 _needsKeyFrame = YES ;
555560 _encoderMode = XCWVideoEncoderModeFromEnvironment ();
561+ _activeEncoderMode = _encoderMode;
556562 _lowLatencyMode = (_encoderMode == XCWVideoEncoderModeH264Software) && XCWLowLatencyModeFromEnvironment ();
557563 _realtimeStreamMode = XCWRealtimeStreamModeFromEnvironment () || _lowLatencyMode;
558- _codecType = XCWVideoCodecTypeForMode (_encoderMode );
564+ _codecType = XCWVideoCodecTypeForMode (_activeEncoderMode );
559565 _softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked ];
560566 _hardwareFrameIntervalUs = [self initialHardwareFrameIntervalUsLocked ];
561567 return self;
@@ -604,15 +610,17 @@ - (void)reconfigureForStreamQualityChange {
604610 dispatch_async (_queue, ^{
605611 [self invalidateCompressionSessionLocked ];
606612 self->_encoderMode = XCWVideoEncoderModeFromEnvironment ();
613+ self->_activeEncoderMode = self->_encoderMode ;
607614 self->_lowLatencyMode = (self->_encoderMode == XCWVideoEncoderModeH264Software) && XCWLowLatencyModeFromEnvironment ();
608615 self->_realtimeStreamMode = XCWRealtimeStreamModeFromEnvironment () || self->_lowLatencyMode ;
609- self->_codecType = XCWVideoCodecTypeForMode (self->_encoderMode );
616+ self->_codecType = XCWVideoCodecTypeForMode (self->_activeEncoderMode );
610617 self->_needsKeyFrame = YES ;
611618 self->_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked ];
612619 self->_softwarePacedFrameCount = 0 ;
613620 self->_softwareHealthyFrameCount = 0 ;
614621 self->_hardwareFrameIntervalUs = [self initialHardwareFrameIntervalUsLocked ];
615622 self->_hardwarePacedFrameCount = 0 ;
623+ self->_autoSoftwareFallbackUntilUs = 0 ;
616624 });
617625}
618626
@@ -653,6 +661,12 @@ - (NSDictionary *)statsRepresentation {
653661 ? @" average-latency-near-budget"
654662 : @" consecutive-frames-near-budget" ;
655663 }
664+ uint64_t nowUs = (uint64_t )(CACurrentMediaTime () * 1000000.0 );
665+ BOOL autoSoftwareFallbackActive = [self isAutoSoftwareFallbackActiveLocked ];
666+ uint64_t autoSoftwareFallbackRemainingUs = autoSoftwareFallbackActive &&
667+ self->_autoSoftwareFallbackUntilUs > nowUs
668+ ? self->_autoSoftwareFallbackUntilUs - nowUs
669+ : 0 ;
656670 stats = @{
657671 @" inputFrames" : @(inputFrameCount),
658672 @" pendingReplacements" : @(pendingReplacementCount),
@@ -684,9 +698,14 @@ - (NSDictionary *)statsRepresentation {
684698 @" hardwarePacedFrames" : @(self->_hardwarePacedFrameCount ),
685699 @" transportCodec" : XCWCodecName (self->_codecType ),
686700 @" encoderMode" : XCWVideoEncoderModeName (self->_encoderMode ),
701+ @" activeEncoderMode" : XCWVideoEncoderModeName (self->_activeEncoderMode ),
702+ @" autoSoftwareFallbackActive" : @(autoSoftwareFallbackActive),
703+ @" autoSoftwareFallbackRemainingUs" : @(autoSoftwareFallbackRemainingUs),
704+ @" autoSoftwareFallbacks" : @(self->_autoSoftwareFallbackCount ),
705+ @" autoHardwareRetries" : @(self->_autoHardwareRetryCount ),
687706 @" lowLatencyMode" : @(self->_lowLatencyMode ),
688707 @" realtimeStreamMode" : @(self->_realtimeStreamMode ),
689- @" encoderId" : XCWVideoEncoderIDForMode (self->_encoderMode ) ?: @" automatic" ,
708+ @" encoderId" : XCWVideoEncoderIDForMode (self->_activeEncoderMode ) ?: @" automatic" ,
690709 @" selectedEncoderId" : self->_selectedEncoderID ?: NSNull .null ,
691710 @" hardwareAccelerated" : @(self->_hardwareAccelerated ),
692711 @" lastSessionStatus" : @(self->_lastSessionStatus ),
@@ -760,19 +779,67 @@ - (uint64_t)initialHardwareFrameIntervalUsLocked {
760779 return _realtimeStreamMode ? XCWRealtimeFrameIntervalUs () : XCWLocalStreamFrameIntervalUs ();
761780}
762781
782+ - (BOOL )isAutoSoftwareFallbackActiveLocked {
783+ return _encoderMode == XCWVideoEncoderModeAuto &&
784+ _activeEncoderMode == XCWVideoEncoderModeH264Software;
785+ }
786+
787+ - (void )resetAutoFallbackLatencyStateLocked {
788+ _latestEncodeLatencyUs = 0 ;
789+ _averageEncodeLatencyUs = 0 ;
790+ _peakEncodeLatencyUs = 0 ;
791+ _consecutiveOverBudgetFrameCount = 0 ;
792+ _consecutiveStrainedFrameCount = 0 ;
793+ _wasOverloaded = NO ;
794+ }
795+
796+ - (void )enterAutoSoftwareFallbackLockedAtTimeUs : (uint64_t )nowUs {
797+ if (_encoderMode != XCWVideoEncoderModeAuto ||
798+ _activeEncoderMode == XCWVideoEncoderModeH264Software) {
799+ return ;
800+ }
801+ _activeEncoderMode = XCWVideoEncoderModeH264Software;
802+ _codecType = XCWVideoCodecTypeForMode (_activeEncoderMode);
803+ _autoSoftwareFallbackUntilUs = nowUs + XCWAutoHardwareRetryIntervalUs;
804+ _autoSoftwareFallbackCount += 1 ;
805+ _softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked ];
806+ _softwareHealthyFrameCount = 0 ;
807+ _softwarePacedFrameCount = 0 ;
808+ [self invalidateCompressionSessionLocked ];
809+ [self resetAutoFallbackLatencyStateLocked ];
810+ _needsKeyFrame = YES ;
811+ }
812+
813+ - (void )retryAutoHardwareIfNeededLockedAtTimeUs : (uint64_t )nowUs {
814+ if (![self isAutoSoftwareFallbackActiveLocked ] ||
815+ _autoSoftwareFallbackUntilUs == 0 ||
816+ nowUs < _autoSoftwareFallbackUntilUs) {
817+ return ;
818+ }
819+ _activeEncoderMode = XCWVideoEncoderModeAuto;
820+ _codecType = XCWVideoCodecTypeForMode (_activeEncoderMode);
821+ _autoSoftwareFallbackUntilUs = 0 ;
822+ _autoHardwareRetryCount += 1 ;
823+ _hardwareFrameIntervalUs = [self initialHardwareFrameIntervalUsLocked ];
824+ _hardwarePacedFrameCount = 0 ;
825+ [self invalidateCompressionSessionLocked ];
826+ [self resetAutoFallbackLatencyStateLocked ];
827+ _needsKeyFrame = YES ;
828+ }
829+
763830- (uint64_t )activeFrameIntervalUsLocked {
764- if (_encoderMode == XCWVideoEncoderModeH264Software) {
831+ if (_activeEncoderMode == XCWVideoEncoderModeH264Software) {
765832 return _softwareFrameIntervalUs > 0 ? _softwareFrameIntervalUs : [self initialSoftwareFrameIntervalUsLocked ];
766833 }
767- if (_encoderMode == XCWVideoEncoderModeAuto || _encoderMode == XCWVideoEncoderModeH264Hardware) {
834+ if (_activeEncoderMode == XCWVideoEncoderModeAuto || _activeEncoderMode == XCWVideoEncoderModeH264Hardware) {
768835 return _hardwareFrameIntervalUs > 0 ? _hardwareFrameIntervalUs : [self initialHardwareFrameIntervalUsLocked ];
769836 }
770837 int32_t expectedFrameRate = MAX (1 , [self expectedFrameRateLocked ]);
771838 return (uint64_t )llround (1000000.0 / (double )expectedFrameRate);
772839}
773840
774841- (int32_t )expectedFrameRateLocked {
775- if (_encoderMode == XCWVideoEncoderModeH264Software) {
842+ if (_activeEncoderMode == XCWVideoEncoderModeH264Software) {
776843 if (_lowLatencyMode) {
777844 return XCWTargetLowLatencySoftwareFrameRate;
778845 }
@@ -785,7 +852,7 @@ - (int32_t)expectedFrameRateLocked {
785852}
786853
787854- (BOOL )shouldPaceHardwareFrameAtTimeUs : (uint64_t )nowUs {
788- if ((_encoderMode != XCWVideoEncoderModeAuto && _encoderMode != XCWVideoEncoderModeH264Hardware) || _needsKeyFrame) {
855+ if ((_activeEncoderMode != XCWVideoEncoderModeAuto && _activeEncoderMode != XCWVideoEncoderModeH264Hardware) || _needsKeyFrame) {
789856 return NO ;
790857 }
791858 if (_realtimeStreamMode) {
@@ -806,7 +873,7 @@ - (BOOL)shouldPaceHardwareFrameAtTimeUs:(uint64_t)nowUs {
806873}
807874
808875- (BOOL )shouldPaceSoftwareFrameAtTimeUs : (uint64_t )nowUs {
809- if (_encoderMode != XCWVideoEncoderModeH264Software || _needsKeyFrame) {
876+ if (_activeEncoderMode != XCWVideoEncoderModeH264Software || _needsKeyFrame) {
810877 return NO ;
811878 }
812879 if (_softwareFrameIntervalUs == 0 ) {
@@ -824,7 +891,7 @@ - (BOOL)shouldPaceSoftwareFrameAtTimeUs:(uint64_t)nowUs {
824891}
825892
826893- (void )adaptSoftwarePacingForLatencyUs : (uint64_t )latencyUs {
827- if (_encoderMode != XCWVideoEncoderModeH264Software || !_lowLatencyMode || latencyUs == 0 ) {
894+ if (_activeEncoderMode != XCWVideoEncoderModeH264Software || !_lowLatencyMode || latencyUs == 0 ) {
828895 return ;
829896 }
830897 if (_softwareFrameIntervalUs == 0 ) {
@@ -892,15 +959,17 @@ - (BOOL)encodePixelBufferLocked:(CVPixelBufferRef)pixelBuffer {
892959 return NO ;
893960 }
894961
895- CGSize targetSize = XCWScaledDimensionsForSourceSize (sourceWidth, sourceHeight, _encoderMode, _lowLatencyMode, _realtimeStreamMode);
962+ uint64_t nowUs = (uint64_t )(CACurrentMediaTime () * 1000000.0 );
963+ [self retryAutoHardwareIfNeededLockedAtTimeUs: nowUs];
964+
965+ CGSize targetSize = XCWScaledDimensionsForSourceSize (sourceWidth, sourceHeight, _activeEncoderMode, _lowLatencyMode, _realtimeStreamMode);
896966 int32_t targetWidth = (int32_t )targetSize.width ;
897967 int32_t targetHeight = (int32_t )targetSize.height ;
898968 if (targetWidth <= 0 || targetHeight <= 0 ) {
899969 return NO ;
900970 }
901971 _scalingActive = sourceWidth != targetWidth || sourceHeight != targetHeight;
902972
903- uint64_t nowUs = (uint64_t )(CACurrentMediaTime () * 1000000.0 );
904973 if ([self shouldPaceSoftwareFrameAtTimeUs: nowUs] || [self shouldPaceHardwareFrameAtTimeUs: nowUs]) {
905974 return YES ;
906975 }
@@ -946,13 +1015,13 @@ - (BOOL)encodePixelBufferLocked:(CVPixelBufferRef)pixelBuffer {
9461015
9471016 _inFlightFrameCount += 1 ;
9481017 _submittedFrameCount += 1 ;
949- if (_encoderMode == XCWVideoEncoderModeH264Software) {
1018+ if (_activeEncoderMode == XCWVideoEncoderModeH264Software) {
9501019 _lastSoftwareSubmissionUs = nowUs;
951- } else if (_encoderMode == XCWVideoEncoderModeAuto || _encoderMode == XCWVideoEncoderModeH264Hardware) {
1020+ } else if (_activeEncoderMode == XCWVideoEncoderModeAuto || _activeEncoderMode == XCWVideoEncoderModeH264Hardware) {
9521021 _lastHardwareSubmissionUs = nowUs;
9531022 }
9541023 _maxInFlightFrameCount = MAX (_maxInFlightFrameCount, _inFlightFrameCount);
955- if (_encoderMode == XCWVideoEncoderModeH264Software || !_realtimeStreamMode) {
1024+ if (_activeEncoderMode == XCWVideoEncoderModeH264Software || !_realtimeStreamMode) {
9561025 VTCompressionSessionCompleteFrames (_compressionSession, presentationTime);
9571026 }
9581027 return YES ;
@@ -966,20 +1035,20 @@ - (BOOL)ensureCompressionSessionWithWidth:(int32_t)width height:(int32_t)height
9661035 [self invalidateCompressionSessionLocked ];
9671036
9681037 NSMutableDictionary *encoderSpecification = [NSMutableDictionary dictionary ];
969- NSString *encoderID = XCWVideoEncoderIDForMode (_encoderMode );
1038+ NSString *encoderID = XCWVideoEncoderIDForMode (_activeEncoderMode );
9701039 if (encoderID.length > 0 ) {
9711040 encoderSpecification[(__bridge NSString *)kVTVideoEncoderSpecification_EncoderID ] = encoderID;
9721041 }
973- if (_encoderMode != XCWVideoEncoderModeH264Software && (_lowLatencyMode || _realtimeStreamMode)) {
1042+ if (_activeEncoderMode != XCWVideoEncoderModeH264Software && (_lowLatencyMode || _realtimeStreamMode)) {
9741043 if (@available (macOS 11.3 , *)) {
9751044 encoderSpecification[(__bridge NSString *)kVTVideoEncoderSpecification_EnableLowLatencyRateControl ] = @YES ;
9761045 }
9771046 }
978- if (_encoderMode == XCWVideoEncoderModeH264Software) {
1047+ if (_activeEncoderMode == XCWVideoEncoderModeH264Software) {
9791048 encoderSpecification[(__bridge NSString *)kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder ] = @NO ;
980- } else if (_encoderMode == XCWVideoEncoderModeH264Hardware) {
1049+ } else if (_activeEncoderMode == XCWVideoEncoderModeH264Hardware) {
9811050 encoderSpecification[(__bridge NSString *)kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder ] = @YES ;
982- } else if (_encoderMode == XCWVideoEncoderModeAuto && _realtimeStreamMode) {
1051+ } else if (_activeEncoderMode == XCWVideoEncoderModeAuto && _realtimeStreamMode) {
9831052 encoderSpecification[(__bridge NSString *)kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder ] = @YES ;
9841053 }
9851054
@@ -1006,7 +1075,7 @@ - (BOOL)ensureCompressionSessionWithWidth:(int32_t)width height:(int32_t)height
10061075 _needsKeyFrame = YES ;
10071076
10081077 int expectedFrameRate = [self expectedFrameRateLocked ];
1009- int averageBitRate = XCWAverageBitRateForDimensions (width, height, _encoderMode , _lowLatencyMode, _realtimeStreamMode);
1078+ int averageBitRate = XCWAverageBitRateForDimensions (width, height, _activeEncoderMode , _lowLatencyMode, _realtimeStreamMode);
10101079
10111080 VTSessionSetProperty (session, kVTCompressionPropertyKey_RealTime , kCFBooleanTrue );
10121081 if (@available (macOS 10.14 , *)) {
@@ -1123,7 +1192,7 @@ - (nullable CVPixelBufferRef)copyScaledPixelBufferIfNeeded:(CVPixelBufferRef)pix
11231192 if (_pixelTransferSession == NULL ) {
11241193 OSStatus sessionStatus = VTPixelTransferSessionCreate (kCFAllocatorDefault , &_pixelTransferSession);
11251194 if (sessionStatus != noErr || _pixelTransferSession == NULL ) {
1126- if (_encoderMode == XCWVideoEncoderModeH264Software) {
1195+ if (_activeEncoderMode == XCWVideoEncoderModeH264Software) {
11271196 return [self copySoftwareScaledPixelBuffer: pixelBuffer
11281197 targetWidth: targetWidth
11291198 targetHeight: targetHeight];
@@ -1155,7 +1224,7 @@ - (nullable CVPixelBufferRef)copyScaledPixelBufferIfNeeded:(CVPixelBufferRef)pix
11551224 _lastScaleStatus = transferStatus;
11561225 if (transferStatus != noErr) {
11571226 CVPixelBufferRelease (scaledPixelBuffer);
1158- if (_encoderMode == XCWVideoEncoderModeH264Software) {
1227+ if (_activeEncoderMode == XCWVideoEncoderModeH264Software) {
11591228 return [self copySoftwareScaledPixelBuffer: pixelBuffer
11601229 targetWidth: targetWidth
11611230 targetHeight: targetHeight];
@@ -1177,7 +1246,7 @@ - (BOOL)shouldUseSoftwareScalerForSourceWidth:(int32_t)sourceWidth
11771246 if (sourceWidth == targetWidth && sourceHeight == targetHeight) {
11781247 return NO ;
11791248 }
1180- return _encoderMode == XCWVideoEncoderModeAuto || _encoderMode == XCWVideoEncoderModeH264Hardware;
1249+ return _activeEncoderMode == XCWVideoEncoderModeAuto || _activeEncoderMode == XCWVideoEncoderModeH264Hardware;
11811250}
11821251
11831252- (nullable CVPixelBufferRef)copySoftwareScaledPixelBuffer : (CVPixelBufferRef)pixelBuffer
@@ -1315,8 +1384,11 @@ - (void)handleEncodedSampleBuffer:(CMSampleBufferRef)sampleBuffer
13151384 if (isKeyFrame) {
13161385 _keyFrameOutputCount += 1 ;
13171386 }
1387+ BOOL shouldEnterAutoSoftwareFallback = NO ;
1388+ uint64_t measurementTimeUs = 0 ;
13181389 if (submittedAtUs > 0 ) {
13191390 uint64_t nowUs = (uint64_t )(CACurrentMediaTime () * 1000000.0 );
1391+ measurementTimeUs = nowUs;
13201392 _latestEncodeLatencyUs = nowUs >= submittedAtUs ? nowUs - submittedAtUs : 0 ;
13211393 _peakEncodeLatencyUs = MAX (_peakEncodeLatencyUs, _latestEncodeLatencyUs);
13221394 _averageEncodeLatencyUs = _averageEncodeLatencyUs <= 0.0
@@ -1346,6 +1418,10 @@ - (void)handleEncodedSampleBuffer:(CMSampleBufferRef)sampleBuffer
13461418 _overloadEventCount += 1 ;
13471419 }
13481420 _wasOverloaded = overloaded;
1421+ shouldEnterAutoSoftwareFallback = overloaded &&
1422+ _encoderMode == XCWVideoEncoderModeAuto &&
1423+ _activeEncoderMode != XCWVideoEncoderModeH264Software &&
1424+ _hardwareAccelerated;
13491425 [self adaptSoftwarePacingForLatencyUs: _latestEncodeLatencyUs];
13501426 }
13511427 NSString *codec = nil ;
@@ -1400,6 +1476,9 @@ - (void)handleEncodedSampleBuffer:(CMSampleBufferRef)sampleBuffer
14001476
14011477 CGSize dimensions = CGSizeMake (_width, _height);
14021478 self.outputHandler (sampleData, timestampUs, isKeyFrame, codec, decoderConfig, dimensions);
1479+ if (shouldEnterAutoSoftwareFallback) {
1480+ [self enterAutoSoftwareFallbackLockedAtTimeUs: measurementTimeUs];
1481+ }
14031482}
14041483
14051484- (void )completeInFlightFrame {
0 commit comments