4040static const NSUInteger XCWRealtimeHardwareHealthyFrameWindow = 6 ;
4141static const NSUInteger XCWMaximumRealtimeInFlightFrames = 3 ;
4242static const int32_t XCWRealtimeKeyFrameIntervalSeconds = 5 ;
43+ static const double XCWEncoderLatencyEWMAAlpha = 0.2 ;
44+ static const double XCWEncoderStrainedLoadPercent = 85.0 ;
45+ static const double XCWEncoderOverloadedLoadPercent = 105.0 ;
46+ static const NSUInteger XCWEncoderConsecutiveOverBudgetFrameThreshold = 3 ;
4347
4448typedef NS_ENUM (NSUInteger , XCWVideoEncoderMode) {
4549 XCWVideoEncoderModeAuto,
@@ -477,11 +481,17 @@ @interface XCWH264Encoder ()
477481- (nullable CVPixelBufferRef)copySoftwareScaledPixelBuffer : (CVPixelBufferRef)pixelBuffer
478482 targetWidth : (int32_t )targetWidth
479483 targetHeight : (int32_t )targetHeight ;
484+ - (BOOL )shouldUseSoftwareScalerForSourceWidth : (int32_t )sourceWidth
485+ sourceHeight : (int32_t )sourceHeight
486+ targetWidth : (int32_t )targetWidth
487+ targetHeight : (int32_t )targetHeight
488+ pixelFormat : (OSType)pixelFormat ;
480489- (nullable CVPixelBufferRef)copyPixelBufferFromScalingPoolWithWidth : (int32_t )targetWidth
481490 height : (int32_t )targetHeight
482491 pixelFormat : (OSType)pixelFormat ;
483492- (void )handleCompressionOutputSampleBuffer : (CMSampleBufferRef)sampleBuffer
484493 submittedAtUs : (uint64_t )submittedAtUs ;
494+ - (uint64_t )activeFrameIntervalUsLocked ;
485495
486496@end
487497
@@ -515,6 +525,13 @@ @implementation XCWH264Encoder {
515525 NSUInteger _keyFrameOutputCount;
516526 NSUInteger _maxInFlightFrameCount;
517527 uint64_t _latestEncodeLatencyUs;
528+ double _averageEncodeLatencyUs;
529+ uint64_t _peakEncodeLatencyUs;
530+ NSUInteger _overBudgetFrameCount;
531+ NSUInteger _consecutiveOverBudgetFrameCount;
532+ NSUInteger _consecutiveStrainedFrameCount;
533+ NSUInteger _overloadEventCount;
534+ BOOL _wasOverloaded;
518535 uint64_t _softwareFrameIntervalUs;
519536 uint64_t _lastSoftwareSubmissionUs;
520537 NSUInteger _softwarePacedFrameCount;
@@ -616,6 +633,33 @@ - (NSDictionary *)statsRepresentation {
616633
617634 __block NSDictionary *stats = nil ;
618635 dispatch_sync (_queue, ^{
636+ uint64_t encoderBudgetUs = [self activeFrameIntervalUsLocked ];
637+ double latestLoadPercent = encoderBudgetUs > 0
638+ ? ((double )self->_latestEncodeLatencyUs * 100.0 ) / (double )encoderBudgetUs
639+ : 0.0 ;
640+ double averageLoadPercent = encoderBudgetUs > 0
641+ ? (self->_averageEncodeLatencyUs * 100.0 ) / (double )encoderBudgetUs
642+ : 0.0 ;
643+ BOOL overloaded = averageLoadPercent >= XCWEncoderOverloadedLoadPercent ||
644+ self->_consecutiveOverBudgetFrameCount >= XCWEncoderConsecutiveOverBudgetFrameThreshold;
645+ BOOL strained = overloaded ||
646+ averageLoadPercent >= XCWEncoderStrainedLoadPercent ||
647+ self->_consecutiveStrainedFrameCount >= XCWEncoderConsecutiveOverBudgetFrameThreshold;
648+ NSString *overloadState = overloaded
649+ ? @" overloaded"
650+ : strained
651+ ? @" strained"
652+ : @" nominal" ;
653+ NSString *overloadReason = @" within-budget" ;
654+ if (overloaded) {
655+ overloadReason = self->_consecutiveOverBudgetFrameCount >= XCWEncoderConsecutiveOverBudgetFrameThreshold
656+ ? @" consecutive-frames-over-budget"
657+ : @" average-latency-over-budget" ;
658+ } else if (strained) {
659+ overloadReason = averageLoadPercent >= XCWEncoderStrainedLoadPercent
660+ ? @" average-latency-near-budget"
661+ : @" consecutive-frames-near-budget" ;
662+ }
619663 stats = @{
620664 @" inputFrames" : @(inputFrameCount),
621665 @" pendingReplacements" : @(pendingReplacementCount),
@@ -626,6 +670,18 @@ - (NSDictionary *)statsRepresentation {
626670 @" inFlightFrames" : @(self->_inFlightFrameCount ),
627671 @" maxInFlightFrames" : @(self->_maxInFlightFrameCount ),
628672 @" latestEncodeLatencyUs" : @(self->_latestEncodeLatencyUs ),
673+ @" averageEncodeLatencyUs" : @(self->_averageEncodeLatencyUs ),
674+ @" peakEncodeLatencyUs" : @(self->_peakEncodeLatencyUs ),
675+ @" encoderBudgetUs" : @(encoderBudgetUs),
676+ @" encoderLoadPercent" : @(latestLoadPercent),
677+ @" averageEncoderLoadPercent" : @(averageLoadPercent),
678+ @" overloadState" : overloadState,
679+ @" overloaded" : @(overloaded),
680+ @" overloadReason" : overloadReason,
681+ @" overBudgetFrames" : @(self->_overBudgetFrameCount ),
682+ @" consecutiveOverBudgetFrames" : @(self->_consecutiveOverBudgetFrameCount ),
683+ @" consecutiveStrainedFrames" : @(self->_consecutiveStrainedFrameCount ),
684+ @" overloadEvents" : @(self->_overloadEventCount ),
629685 @" softwareFrameIntervalUs" : @(self->_softwareFrameIntervalUs ),
630686 @" softwareTargetFps" : @(self->_softwareFrameIntervalUs > 0 ? (1000000.0 / (double )self->_softwareFrameIntervalUs ) : 0.0 ),
631687 @" softwarePacedFrames" : @(self->_softwarePacedFrameCount ),
@@ -715,7 +771,22 @@ - (uint64_t)initialHardwareFrameIntervalUsLocked {
715771}
716772
717773- (uint64_t )maximumHardwareFrameIntervalUsLocked {
718- return _realtimeStreamMode ? XCWRealtimeMaximumFrameIntervalUs () : XCWLocalStreamMaximumFrameIntervalUs ();
774+ if (_realtimeStreamMode) {
775+ uint64_t minimumFpsIntervalUs = (uint64_t )llround (1000000.0 / (double )XCWMinimumLocalStreamFrameRate);
776+ return MAX (XCWRealtimeMaximumFrameIntervalUs (), minimumFpsIntervalUs);
777+ }
778+ return XCWLocalStreamMaximumFrameIntervalUs ();
779+ }
780+
781+ - (uint64_t )activeFrameIntervalUsLocked {
782+ if (_encoderMode == XCWVideoEncoderModeH264Software) {
783+ return _softwareFrameIntervalUs > 0 ? _softwareFrameIntervalUs : [self initialSoftwareFrameIntervalUsLocked ];
784+ }
785+ if (_encoderMode == XCWVideoEncoderModeAuto || _encoderMode == XCWVideoEncoderModeH264Hardware) {
786+ return _hardwareFrameIntervalUs > 0 ? _hardwareFrameIntervalUs : [self initialHardwareFrameIntervalUsLocked ];
787+ }
788+ int32_t expectedFrameRate = MAX (1 , [self expectedFrameRateLocked ]);
789+ return (uint64_t )llround (1000000.0 / (double )expectedFrameRate);
719790}
720791
721792- (int32_t )expectedFrameRateLocked {
@@ -807,7 +878,7 @@ - (void)adaptSoftwarePacingForLatencyUs:(uint64_t)latencyUs {
807878}
808879
809880- (void )adaptHardwarePacingForLatencyUs : (uint64_t )latencyUs {
810- if ((_encoderMode != XCWVideoEncoderModeAuto && _encoderMode != XCWVideoEncoderModeH264Hardware) || !_lowLatencyMode || latencyUs == 0 ) {
881+ if ((_encoderMode != XCWVideoEncoderModeAuto && _encoderMode != XCWVideoEncoderModeH264Hardware) || !_realtimeStreamMode || latencyUs == 0 ) {
811882 return ;
812883 }
813884 if (_hardwareFrameIntervalUs == 0 ) {
@@ -1066,6 +1137,12 @@ - (void)invalidateCompressionSessionLocked {
10661137 _inFlightFrameCount = 0 ;
10671138 _lastSoftwareSubmissionUs = 0 ;
10681139 _lastHardwareSubmissionUs = 0 ;
1140+ _latestEncodeLatencyUs = 0 ;
1141+ _averageEncodeLatencyUs = 0 ;
1142+ _peakEncodeLatencyUs = 0 ;
1143+ _consecutiveOverBudgetFrameCount = 0 ;
1144+ _consecutiveStrainedFrameCount = 0 ;
1145+ _wasOverloaded = NO ;
10691146 _hardwareAccelerated = NO ;
10701147 _selectedEncoderID = nil ;
10711148 _scalingActive = NO ;
@@ -1077,11 +1154,24 @@ - (nullable CVPixelBufferRef)copyScaledPixelBufferIfNeeded:(CVPixelBufferRef)pix
10771154 targetHeight : (int32_t )targetHeight {
10781155 int32_t sourceWidth = (int32_t )CVPixelBufferGetWidth (pixelBuffer);
10791156 int32_t sourceHeight = (int32_t )CVPixelBufferGetHeight (pixelBuffer);
1157+ OSType sourcePixelFormat = CVPixelBufferGetPixelFormatType (pixelBuffer);
10801158 BOOL shouldCopyStableRealtimeBuffer = _realtimeStreamMode || _lowLatencyMode;
10811159 if (sourceWidth == targetWidth && sourceHeight == targetHeight && !shouldCopyStableRealtimeBuffer) {
10821160 CVPixelBufferRetain (pixelBuffer);
10831161 return pixelBuffer;
10841162 }
1163+ if ([self shouldUseSoftwareScalerForSourceWidth: sourceWidth
1164+ sourceHeight: sourceHeight
1165+ targetWidth: targetWidth
1166+ targetHeight: targetHeight
1167+ pixelFormat: sourcePixelFormat]) {
1168+ CVPixelBufferRef scaledPixelBuffer = [self copySoftwareScaledPixelBuffer: pixelBuffer
1169+ targetWidth: targetWidth
1170+ targetHeight: targetHeight];
1171+ if (scaledPixelBuffer != NULL ) {
1172+ return scaledPixelBuffer;
1173+ }
1174+ }
10851175
10861176 if (_pixelTransferSession == NULL ) {
10871177 OSStatus sessionStatus = VTPixelTransferSessionCreate (kCFAllocatorDefault , &_pixelTransferSession);
@@ -1105,7 +1195,6 @@ - (nullable CVPixelBufferRef)copyScaledPixelBufferIfNeeded:(CVPixelBufferRef)pix
11051195 kVTScalingMode_Normal );
11061196 }
11071197
1108- OSType sourcePixelFormat = CVPixelBufferGetPixelFormatType (pixelBuffer);
11091198 CVPixelBufferRef scaledPixelBuffer = [self copyPixelBufferFromScalingPoolWithWidth: targetWidth
11101199 height: targetHeight
11111200 pixelFormat: sourcePixelFormat];
@@ -1130,6 +1219,20 @@ - (nullable CVPixelBufferRef)copyScaledPixelBufferIfNeeded:(CVPixelBufferRef)pix
11301219 return scaledPixelBuffer;
11311220}
11321221
1222+ - (BOOL )shouldUseSoftwareScalerForSourceWidth : (int32_t )sourceWidth
1223+ sourceHeight : (int32_t )sourceHeight
1224+ targetWidth : (int32_t )targetWidth
1225+ targetHeight : (int32_t )targetHeight
1226+ pixelFormat : (OSType)pixelFormat {
1227+ if (!_realtimeStreamMode || !XCWPixelFormatSupportsSoftwareScaling (pixelFormat)) {
1228+ return NO ;
1229+ }
1230+ if (sourceWidth == targetWidth && sourceHeight == targetHeight) {
1231+ return NO ;
1232+ }
1233+ return _encoderMode == XCWVideoEncoderModeAuto || _encoderMode == XCWVideoEncoderModeH264Hardware;
1234+ }
1235+
11331236- (nullable CVPixelBufferRef)copySoftwareScaledPixelBuffer : (CVPixelBufferRef)pixelBuffer
11341237 targetWidth : (int32_t )targetWidth
11351238 targetHeight : (int32_t )targetHeight {
@@ -1268,6 +1371,34 @@ - (void)handleEncodedSampleBuffer:(CMSampleBufferRef)sampleBuffer
12681371 if (submittedAtUs > 0 ) {
12691372 uint64_t nowUs = (uint64_t )(CACurrentMediaTime () * 1000000.0 );
12701373 _latestEncodeLatencyUs = nowUs >= submittedAtUs ? nowUs - submittedAtUs : 0 ;
1374+ _peakEncodeLatencyUs = MAX (_peakEncodeLatencyUs, _latestEncodeLatencyUs);
1375+ _averageEncodeLatencyUs = _averageEncodeLatencyUs <= 0.0
1376+ ? (double )_latestEncodeLatencyUs
1377+ : (_averageEncodeLatencyUs * (1.0 - XCWEncoderLatencyEWMAAlpha)) + ((double )_latestEncodeLatencyUs * XCWEncoderLatencyEWMAAlpha);
1378+ uint64_t encoderBudgetUs = [self activeFrameIntervalUsLocked ];
1379+ double averageLoadPercent = encoderBudgetUs > 0
1380+ ? (_averageEncodeLatencyUs * 100.0 ) / (double )encoderBudgetUs
1381+ : 0.0 ;
1382+ double latestLoadPercent = encoderBudgetUs > 0
1383+ ? ((double )_latestEncodeLatencyUs * 100.0 ) / (double )encoderBudgetUs
1384+ : 0.0 ;
1385+ if (encoderBudgetUs > 0 && _latestEncodeLatencyUs > encoderBudgetUs) {
1386+ _overBudgetFrameCount += 1 ;
1387+ _consecutiveOverBudgetFrameCount += 1 ;
1388+ } else {
1389+ _consecutiveOverBudgetFrameCount = 0 ;
1390+ }
1391+ if (latestLoadPercent >= XCWEncoderStrainedLoadPercent) {
1392+ _consecutiveStrainedFrameCount += 1 ;
1393+ } else {
1394+ _consecutiveStrainedFrameCount = 0 ;
1395+ }
1396+ BOOL overloaded = averageLoadPercent >= XCWEncoderOverloadedLoadPercent ||
1397+ _consecutiveOverBudgetFrameCount >= XCWEncoderConsecutiveOverBudgetFrameThreshold;
1398+ if (overloaded && !_wasOverloaded) {
1399+ _overloadEventCount += 1 ;
1400+ }
1401+ _wasOverloaded = overloaded;
12711402 [self adaptSoftwarePacingForLatencyUs: _latestEncodeLatencyUs];
12721403 [self adaptHardwarePacingForLatencyUs: _latestEncodeLatencyUs];
12731404 }
0 commit comments