Skip to content

Commit 979a370

Browse files
committed
Fallback to software encoder when web client is backgrounded
1 parent 652bcfb commit 979a370

22 files changed

Lines changed: 305 additions & 19 deletions

cli/XCWH264Encoder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ typedef void (^XCWH264EncoderOutputHandler)(NSData *sampleData,
2020
- (void)encodePixelBuffer:(CVPixelBufferRef)pixelBuffer;
2121
- (void)requestKeyFrame;
2222
- (void)reconfigureForStreamQualityChange;
23+
- (void)setClientForeground:(BOOL)foreground;
2324
- (NSDictionary *)statsRepresentation;
2425
- (void)invalidate;
2526

cli/XCWH264Encoder.m

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ @implementation XCWH264Encoder {
509509
BOOL _scalingActive;
510510
XCWVideoEncoderMode _encoderMode;
511511
XCWVideoEncoderMode _activeEncoderMode;
512+
BOOL _clientForeground;
512513
BOOL _lowLatencyMode;
513514
BOOL _realtimeStreamMode;
514515
CMVideoCodecType _codecType;
@@ -559,6 +560,7 @@ - (instancetype)initWithOutputHandler:(XCWH264EncoderOutputHandler)outputHandler
559560
_needsKeyFrame = YES;
560561
_encoderMode = XCWVideoEncoderModeFromEnvironment();
561562
_activeEncoderMode = _encoderMode;
563+
_clientForeground = YES;
562564
_lowLatencyMode = (_encoderMode == XCWVideoEncoderModeH264Software) && XCWLowLatencyModeFromEnvironment();
563565
_realtimeStreamMode = XCWRealtimeStreamModeFromEnvironment() || _lowLatencyMode;
564566
_codecType = XCWVideoCodecTypeForMode(_activeEncoderMode);
@@ -621,6 +623,17 @@ - (void)reconfigureForStreamQualityChange {
621623
self->_hardwareFrameIntervalUs = [self initialHardwareFrameIntervalUsLocked];
622624
self->_hardwarePacedFrameCount = 0;
623625
self->_autoSoftwareFallbackUntilUs = 0;
626+
[self updateActiveEncoderModeForClientForegroundLockedAtTimeUs:(uint64_t)(CACurrentMediaTime() * 1000000.0)];
627+
});
628+
}
629+
630+
- (void)setClientForeground:(BOOL)foreground {
631+
dispatch_async(_queue, ^{
632+
if (self->_clientForeground == foreground) {
633+
return;
634+
}
635+
self->_clientForeground = foreground;
636+
[self updateActiveEncoderModeForClientForegroundLockedAtTimeUs:(uint64_t)(CACurrentMediaTime() * 1000000.0)];
624637
});
625638
}
626639

@@ -699,6 +712,7 @@ - (NSDictionary *)statsRepresentation {
699712
@"transportCodec": XCWCodecName(self->_codecType),
700713
@"encoderMode": XCWVideoEncoderModeName(self->_encoderMode),
701714
@"activeEncoderMode": XCWVideoEncoderModeName(self->_activeEncoderMode),
715+
@"clientForeground": @(self->_clientForeground),
702716
@"autoSoftwareFallbackActive": @(autoSoftwareFallbackActive),
703717
@"autoSoftwareFallbackRemainingUs": @(autoSoftwareFallbackRemainingUs),
704718
@"autoSoftwareFallbacks": @(self->_autoSoftwareFallbackCount),
@@ -781,6 +795,7 @@ - (uint64_t)initialHardwareFrameIntervalUsLocked {
781795

782796
- (BOOL)isAutoSoftwareFallbackActiveLocked {
783797
return _encoderMode == XCWVideoEncoderModeAuto &&
798+
_autoSoftwareFallbackUntilUs != 0 &&
784799
_activeEncoderMode == XCWVideoEncoderModeH264Software;
785800
}
786801

@@ -793,38 +808,63 @@ - (void)resetAutoFallbackLatencyStateLocked {
793808
_wasOverloaded = NO;
794809
}
795810

811+
- (void)switchActiveEncoderModeLocked:(XCWVideoEncoderMode)mode {
812+
if (_activeEncoderMode == mode) {
813+
return;
814+
}
815+
_activeEncoderMode = mode;
816+
_codecType = XCWVideoCodecTypeForMode(_activeEncoderMode);
817+
if (_activeEncoderMode == XCWVideoEncoderModeH264Software) {
818+
_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked];
819+
_softwareHealthyFrameCount = 0;
820+
_softwarePacedFrameCount = 0;
821+
} else {
822+
_hardwareFrameIntervalUs = [self initialHardwareFrameIntervalUsLocked];
823+
_hardwarePacedFrameCount = 0;
824+
}
825+
[self invalidateCompressionSessionLocked];
826+
[self resetAutoFallbackLatencyStateLocked];
827+
_needsKeyFrame = YES;
828+
}
829+
830+
- (void)updateActiveEncoderModeForClientForegroundLockedAtTimeUs:(uint64_t)nowUs {
831+
if (!_clientForeground) {
832+
[self switchActiveEncoderModeLocked:XCWVideoEncoderModeH264Software];
833+
return;
834+
}
835+
if (_encoderMode == XCWVideoEncoderModeAuto &&
836+
_autoSoftwareFallbackUntilUs != 0 &&
837+
nowUs < _autoSoftwareFallbackUntilUs) {
838+
[self switchActiveEncoderModeLocked:XCWVideoEncoderModeH264Software];
839+
return;
840+
}
841+
if (_encoderMode == XCWVideoEncoderModeAuto && _autoSoftwareFallbackUntilUs != 0) {
842+
_autoSoftwareFallbackUntilUs = 0;
843+
_autoHardwareRetryCount += 1;
844+
}
845+
[self switchActiveEncoderModeLocked:_encoderMode];
846+
}
847+
796848
- (void)enterAutoSoftwareFallbackLockedAtTimeUs:(uint64_t)nowUs {
797849
if (_encoderMode != XCWVideoEncoderModeAuto ||
798850
_activeEncoderMode == XCWVideoEncoderModeH264Software) {
799851
return;
800852
}
801-
_activeEncoderMode = XCWVideoEncoderModeH264Software;
802-
_codecType = XCWVideoCodecTypeForMode(_activeEncoderMode);
803853
_autoSoftwareFallbackUntilUs = nowUs + XCWAutoHardwareRetryIntervalUs;
804854
_autoSoftwareFallbackCount += 1;
805-
_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked];
806-
_softwareHealthyFrameCount = 0;
807-
_softwarePacedFrameCount = 0;
808-
[self invalidateCompressionSessionLocked];
809-
[self resetAutoFallbackLatencyStateLocked];
810-
_needsKeyFrame = YES;
855+
[self switchActiveEncoderModeLocked:XCWVideoEncoderModeH264Software];
811856
}
812857

813858
- (void)retryAutoHardwareIfNeededLockedAtTimeUs:(uint64_t)nowUs {
814859
if (![self isAutoSoftwareFallbackActiveLocked] ||
860+
!_clientForeground ||
815861
_autoSoftwareFallbackUntilUs == 0 ||
816862
nowUs < _autoSoftwareFallbackUntilUs) {
817863
return;
818864
}
819-
_activeEncoderMode = XCWVideoEncoderModeAuto;
820-
_codecType = XCWVideoCodecTypeForMode(_activeEncoderMode);
821865
_autoSoftwareFallbackUntilUs = 0;
822866
_autoHardwareRetryCount += 1;
823-
_hardwareFrameIntervalUs = [self initialHardwareFrameIntervalUsLocked];
824-
_hardwarePacedFrameCount = 0;
825-
[self invalidateCompressionSessionLocked];
826-
[self resetAutoFallbackLatencyStateLocked];
827-
_needsKeyFrame = YES;
867+
[self switchActiveEncoderModeLocked:XCWVideoEncoderModeAuto];
828868
}
829869

830870
- (uint64_t)activeFrameIntervalUsLocked {

cli/XCWPrivateSimulatorSession.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ typedef void (^XCWPrivateSimulatorEncodedFrameHandler)(NSData *sampleData,
3131
- (void)requestKeyFrameRefresh;
3232
- (void)requestFrameRefresh;
3333
- (void)reconfigureVideoEncoder;
34+
- (void)setClientForeground:(BOOL)foreground;
3435
- (NSDictionary *)videoEncoderStats;
3536
- (id)addEncodedFrameListener:(XCWPrivateSimulatorEncodedFrameHandler)handler;
3637
- (void)removeEncodedFrameListener:(id)token;

cli/XCWPrivateSimulatorSession.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ - (void)reconfigureVideoEncoder {
177177
[self refreshCurrentFrame];
178178
}
179179

180+
- (void)setClientForeground:(BOOL)foreground {
181+
[_videoEncoder setClientForeground:foreground];
182+
if (foreground) {
183+
[_videoEncoder requestKeyFrame];
184+
[self refreshCurrentFrame];
185+
}
186+
}
187+
180188
- (NSDictionary *)videoEncoderStats {
181189
return [_videoEncoder statsRepresentation];
182190
}

cli/native/XCWNativeBridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ bool xcw_native_session_start(void * _Nonnull handle, char * _Nullable * _Nullab
7676
void xcw_native_session_request_refresh(void * _Nonnull handle);
7777
void xcw_native_session_request_keyframe(void * _Nonnull handle);
7878
void xcw_native_session_reconfigure_video_encoder(void * _Nonnull handle);
79+
void xcw_native_session_set_client_foreground(void * _Nonnull handle, bool foreground);
7980
char * _Nullable xcw_native_session_video_encoder_stats(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
8081
int32_t xcw_native_session_rotation_quarter_turns(void * _Nonnull handle);
8182
bool xcw_native_session_send_touch(void * _Nonnull handle, double x, double y, const char * _Nonnull phase, char * _Nullable * _Nullable error_message);

cli/native/XCWNativeBridge.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,12 @@ void xcw_native_session_reconfigure_video_encoder(void *handle) {
937937
}
938938
}
939939

940+
void xcw_native_session_set_client_foreground(void *handle, bool foreground) {
941+
@autoreleasepool {
942+
[XCWNativeSessionFromHandle(handle) setClientForeground:foreground];
943+
}
944+
}
945+
940946
char *xcw_native_session_video_encoder_stats(void *handle, char **error_message) {
941947
@autoreleasepool {
942948
return XCWJSONStringFromObject([XCWNativeSessionFromHandle(handle) videoEncoderStats] ?: @{}, error_message);

cli/native/XCWNativeSession.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
1515
- (void)requestRefresh;
1616
- (void)requestKeyFrame;
1717
- (void)reconfigureVideoEncoder;
18+
- (void)setClientForeground:(BOOL)foreground;
1819
- (NSDictionary *)videoEncoderStats;
1920
- (NSInteger)rotationQuarterTurns;
2021
- (BOOL)sendTouchAtX:(double)x

cli/native/XCWNativeSession.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ - (void)reconfigureVideoEncoder {
102102
[self.session reconfigureVideoEncoder];
103103
}
104104

105+
- (void)setClientForeground:(BOOL)foreground {
106+
[self.session setClientForeground:foreground];
107+
}
108+
105109
- (NSDictionary *)videoEncoderStats {
106110
return [self.session videoEncoderStats];
107111
}

client/src/api/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
export interface EncoderStats {
2+
activeEncoderMode?: string;
23
averageEncodeLatencyUs?: number;
34
averageEncoderLoadPercent?: number;
5+
autoHardwareRetries?: number;
6+
autoSoftwareFallbackActive?: boolean;
7+
autoSoftwareFallbackRemainingUs?: number;
8+
autoSoftwareFallbacks?: number;
9+
clientForeground?: boolean;
410
consecutiveOverBudgetFrames?: number;
511
encoderBudgetUs?: number;
612
encoderLoadPercent?: number;

client/src/features/stream/streamWorkerClient.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ export function sendStreamClientStats(stats: unknown): boolean {
8686
}
8787

8888
export function sendWebRtcStreamControl(options: {
89+
clientId?: string;
8990
forceKeyframe?: boolean;
91+
foreground?: boolean;
9092
snapshot?: boolean;
9193
}): boolean {
9294
return sendDataChannelMessage(
@@ -3018,7 +3020,12 @@ export class StreamWorkerClient {
30183020
return (await this.backend?.collectVisualArtifactSample?.(udid)) ?? null;
30193021
}
30203022

3021-
sendStreamControl(options: { forceKeyframe?: boolean; snapshot?: boolean }) {
3023+
sendStreamControl(options: {
3024+
clientId?: string;
3025+
forceKeyframe?: boolean;
3026+
foreground?: boolean;
3027+
snapshot?: boolean;
3028+
}) {
30223029
return Boolean(
30233030
this.backend?.sendControl?.({ ...options, type: "streamControl" }),
30243031
);

0 commit comments

Comments
 (0)