- restore asyncDequeue switch

- tweak frame buffering
This commit is contained in:
True Zhuanjia committed 2026-02-19 01:31:55 +08:00
1 parent c7ffd51cec
commit ccd032834d
9 files changed
+52 -18

No files matched your search

@@ -23,7 +23,7 @@
</entity> </entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class"> <entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="appTheme" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> <attribute name="appTheme" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="asyncFrameDequeue" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/> <attribute name="asyncFrameDequeue" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="asyncNativeTouchPriority" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> <attribute name="asyncNativeTouchPriority" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/> <attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
<attribute name="audioEngine" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> <attribute name="audioEngine" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
+1
View File
@@ -19,6 +19,7 @@
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callback; - (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callback;
- (void) stopStream; - (void) stopStream;
- (void) setNeedRequeuing:(bool)needRequeuing;
- (NSString*) getStatsOverlayText: (uint16_t) overlayLevel; - (NSString*) getStatsOverlayText: (uint16_t) overlayLevel;
+5
View File
@@ -114,6 +114,11 @@
}); });
} }
- (void) setNeedRequeuing:(bool)needRequeuing{
[_videoRenderer.frameQueue clear];
_videoRenderer.needRequeuing = needRequeuing;
}
- (void) stopStream - (void) stopStream
{ {
[_connection terminate]; [_connection terminate];
+2
View File
@@ -18,6 +18,8 @@
@property (atomic, readonly) PlotMetrics decodeMetrics; @property (atomic, readonly) PlotMetrics decodeMetrics;
@property (atomic, readonly) PlotMetrics frameQueueMetrics; @property (atomic, readonly) PlotMetrics frameQueueMetrics;
@property (atomic, assign) bool needRequeuing;
@property (nonatomic, strong) FrameQueue* frameQueue;
@property (nonatomic, strong, readonly) AVSampleBufferDisplayLayer *displayLayer; @property (nonatomic, strong, readonly) AVSampleBufferDisplayLayer *displayLayer;
+13 -9
View File
@@ -52,9 +52,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
VTDecompressionSessionRef _decompressionSession; VTDecompressionSessionRef _decompressionSession;
CADisplayLink *_displayLink; CADisplayLink *_displayLink;
FrameQueue *_frameQueue;
uint8_t queueSize; uint8_t queueSize;
bool isFirstFrame;
NSInteger _maxRefreshRate; NSInteger _maxRefreshRate;
RenderingBackend _renderingBackend; RenderingBackend _renderingBackend;
@@ -145,9 +143,10 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
_framePacingMode = tempSettings.framePacingMode.integerValue; _framePacingMode = tempSettings.framePacingMode.integerValue;
_asyncFrameDequeue = tempSettings.asyncFrameDequeue; _asyncFrameDequeue = tempSettings.asyncFrameDequeue;
NSLog(@"_asyncFrameDequeue %d", _asyncFrameDequeue);
_enableTimebase = false; _enableTimebase = false;
queueSize = tempSettings.frameQueueSize.intValue; queueSize = tempSettings.frameQueueSize.intValue;
isFirstFrame = true; _needRequeuing = true;
isIPhone = [Utils isIPhone]; isIPhone = [Utils isIPhone];
_frameQueue = [FrameQueue sharedInstance]; _frameQueue = [FrameQueue sharedInstance];
@@ -155,7 +154,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
[_frameQueue setHighWaterMark:(int)[tempSettings.frameQueueSize integerValue]]; [_frameQueue setHighWaterMark:(int)[tempSettings.frameQueueSize integerValue]];
[self reinitializeDisplayLayer]; [self reinitializeDisplayLayer];
NSTimeInterval interval = 1.0/tempSettings.framerate.intValue; // NSTimeInterval interval = 1.0/tempSettings.framerate.intValue;
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reinitializeDisplayLayer) selector:@selector(reinitializeDisplayLayer)
@@ -261,7 +260,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
// Check for issues with the SampleBuffer, this should be much less likely since // Check for issues with the SampleBuffer, this should be much less likely since
// AVSB is not actually decoding the frames anymore // AVSB is not actually decoding the frames anymore
if (self->_displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) { if (self->_displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
Log(LOG_E, @"Display layer rendering failed: %@", _displayLayer.error); // Log(LOG_E, @"Display layer rendering failed: %@", _displayLayer.error);
// Recreate the display layer. We are already on the main thread, // Recreate the display layer. We are already on the main thread,
// so this is safe to do right here. // so this is safe to do right here.
@@ -277,10 +276,11 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
#pragma mark DisplayLink - Frame Pacing - Vsync with FrameQueue #pragma mark DisplayLink - Frame Pacing - Vsync with FrameQueue
/*
// This frame pacing method was inspired by the behavior of moonlight-qt's Pacer class, although it has evolved // This frame pacing method was inspired by the behavior of moonlight-qt's Pacer class, although it has evolved
// a few additional features. Incoming frames from Sunshine are asynchronously processed into a queue by the VideoRecv thread. // a few additional features. Incoming frames from Sunshine are asynchronously processed into a queue by the VideoRecv thread.
// DisplayLink calls us every vsync we we try to present the most recent frame. We try to maintain a user-configurable buffer // DisplayLink calls us every vsync we we try to present the most recent frame. We try to maintain a user-configurable buffer
// of 1-5 frames. If the buffer is full, every other frame is dropped which just appears to the user as a lower framerate stream. // of 1-5 frames. If the buffer is full, every other frame is dropped which just appears to the user as a lower framerate stream. */
- (void)renderModeAVSB:(CADisplayLink *)link { - (void)renderModeAVSB:(CADisplayLink *)link {
// NSTimeInterval current = link.targetTimestamp; // NSTimeInterval current = link.targetTimestamp;
// NSLog(@"link %f", link.duration); // NSLog(@"link %f", link.duration);
@@ -303,13 +303,17 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
CFTimeInterval waitFor = targetTime - dl0; CFTimeInterval waitFor = targetTime - dl0;
/*
if (waitFor < 0.001f) { if (waitFor < 0.001f) {
waitFor = isIPhone ? waitFor : 0.0f; NSLog(@"waitFor %f", waitFor);
// waitFor = isIPhone ? waitFor : 0.0f;
waitFor = 0.0f;
} }
*/
if(isFirstFrame ? _frameQueue.count>0 : true){ if(_needRequeuing ? _frameQueue.count>queueSize-1 : true){
// if(true){ // if(true){
if(isFirstFrame) isFirstFrame = false; _needRequeuing = false;
if(_asyncFrameDequeue){ if(_asyncFrameDequeue){
[_frameQueue dequeueWithTimeout:waitFor completion:^(Frame *frame) { [_frameQueue dequeueWithTimeout:waitFor completion:^(Frame *frame) {
if (frame) { if (frame) {
@@ -1168,6 +1168,15 @@ static NSMutableSet* hostList;
_settingsViewExpanded = position != FrontViewPositionLeft; _settingsViewExpanded = position != FrontViewPositionLeft;
if (position == FrontViewPositionLeft) { if (position == FrontViewPositionLeft) {
self.navigationItem.leftBarButtonItems = @[_settingsButton]; self.navigationItem.leftBarButtonItems = @[_settingsButton];
if(streamFrameViewController.streamMan){
NSLog(@"setNeedRequeuing %f", CACurrentMediaTime());
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
[self->streamFrameViewController.streamMan setNeedRequeuing:true];
});
}
} }
else { else {
self.navigationItem.leftBarButtonItems = @[]; self.navigationItem.leftBarButtonItems = @[];
@@ -1227,10 +1236,10 @@ static NSMutableSet* hostList;
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.appThemeStack]; [settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.appThemeStack];
[settingsViewController.renderingBackendSelector setEnabled:!_settingsExpandedInStreamView]; [settingsViewController.renderingBackendSelector setEnabled:!_settingsExpandedInStreamView];
// Enable frame pacing mode selector only if not in stream view AND not in performance mode // Enable frame pacing mode selector only if not in stream view AND not in performance mode
BOOL shouldEnableFramePacing = !_settingsExpandedInStreamView && (settingsViewController.renderingBackendSelector.selectedSegmentIndex != RENDER_METAL); BOOL shouldEnableFramePacingSelector = !_settingsExpandedInStreamView && (settingsViewController.renderingBackendSelector.selectedSegmentIndex != RENDER_METAL);
[settingsViewController.framePacingModeSelector setEnabled:shouldEnableFramePacing]; [settingsViewController.framePacingModeSelector setEnabled:shouldEnableFramePacingSelector];
// [settingsViewController.frameTimebaseSwitch setEnabled:shouldEnableFramePacing]; // [settingsViewController.frameTimebaseSwitch setEnabled:shouldEnableFramePacing];
// [settingsViewController.asyncFrameDequeueSwitch setEnabled:shouldEnableFramePacing]; [settingsViewController.asyncFrameDequeueSwitch setEnabled:shouldEnableFramePacingSelector && settingsViewController.framePacingModeSelector.selectedSegmentIndex == FramePacingModeQueue];
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.frameQueueSizeStack]; [settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.frameQueueSizeStack];
// Disable mic switch if sunshine does not support mic redirection // Disable mic switch if sunshine does not support mic redirection
@@ -982,7 +982,7 @@ BOOL isCustomResolution(int resolutionSelected) {
[self addSetting:self.leftClickDelayStack ofId:@"leftClickDelayStack" withInfoTag:NO withDynamicLabel:YES to:experimentalSection]; [self addSetting:self.leftClickDelayStack ofId:@"leftClickDelayStack" withInfoTag:NO withDynamicLabel:YES to:experimentalSection];
[self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection]; [self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection];
// [self addSetting:self.frameTimebaseStack ofId:@"frameTimebaseStack" withInfoTag:NO withDynamicLabel:NO to:videoSection]; // [self addSetting:self.frameTimebaseStack ofId:@"frameTimebaseStack" withInfoTag:NO withDynamicLabel:NO to:videoSection];
// [self addSetting:self.asyncFrameDequeueStack ofId:@"asyncFrameDequeueStack" withInfoTag:NO withDynamicLabel:NO to:experimentalSection]; [self addSetting:self.asyncFrameDequeueStack ofId:@"asyncFrameDequeueStack" withInfoTag:NO withDynamicLabel:NO to:experimentalSection];
[self addSetting:self.performanceGraphStack ofId:@"performanceGraphStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection]; [self addSetting:self.performanceGraphStack ofId:@"performanceGraphStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection];
[self addDynamicLabelForStack:self.graphOpacityStack]; [self addDynamicLabelForStack:self.graphOpacityStack];
@@ -2292,6 +2292,7 @@ BOOL isCustomResolution(int resolutionSelected) {
// Set pacing method to Queue and disable selector // Set pacing method to Queue and disable selector
[self.framePacingModeSelector setSelectedSegmentIndex:FramePacingModeQueue]; [self.framePacingModeSelector setSelectedSegmentIndex:FramePacingModeQueue];
[self.framePacingModeSelector setEnabled:NO]; [self.framePacingModeSelector setEnabled:NO];
[self setHidden:true forStack:self.asyncFrameDequeueStack];
} else { } else {
// Balanced mode (AVSB renderer) - enable PiP toggle if iOS 15+ // Balanced mode (AVSB renderer) - enable PiP toggle if iOS 15+
if (@available(iOS 15.0, *)) { if (@available(iOS 15.0, *)) {
@@ -2301,6 +2302,7 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.pipSwitch setEnabled:NO]; [self.pipSwitch setEnabled:NO];
} }
[self.framePacingModeSelector setEnabled:YES]; [self.framePacingModeSelector setEnabled:YES];
[self setHidden:false forStack:self.asyncFrameDequeueStack];
} }
// Get the current settings to compare with the new selection // Get the current settings to compare with the new selection
@@ -2331,6 +2333,7 @@ BOOL isCustomResolution(int resolutionSelected) {
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:^(UIAlertAction * _Nonnull action) { handler:^(UIAlertAction * _Nonnull action) {
self.renderingBackendSelector.selectedSegmentIndex = 0; self.renderingBackendSelector.selectedSegmentIndex = 0;
[self renderingBackendChanged:self.renderingBackendSelector];
NSURL *url = [NSURL URLWithString:[LocalizationHelper localizedStringForKey:@"betterPerformanceLink"]]; NSURL *url = [NSURL URLWithString:[LocalizationHelper localizedStringForKey:@"betterPerformanceLink"]];
if ([[UIApplication sharedApplication] canOpenURL:url]) { if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
@@ -2347,7 +2350,8 @@ BOOL isCustomResolution(int resolutionSelected) {
// Hide frame queue size for Off and Legacy modes // Hide frame queue size for Off and Legacy modes
[self setHidden:(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy) forStack:self.frameQueueSizeStack]; [self setHidden:(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy) forStack:self.frameQueueSizeStack];
// [self setHidden:(sender.selectedSegmentIndex != FramePacingModeQueue) forStack:self.frameTimebaseStack]; // [self setHidden:(sender.selectedSegmentIndex != FramePacingModeQueue) forStack:self.frameTimebaseStack];
// [self setHidden:(sender.selectedSegmentIndex != FramePacingModeQueue) forStack:self.asyncFrameDequeueStack]; [self setHidden:(sender.selectedSegmentIndex != FramePacingModeQueue
|| self.renderingBackendSelector.selectedSegmentIndex != 0) forStack:self.asyncFrameDequeueStack];
if(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy){ if(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy){
[self.enableGraphsSwitch setOn:NO]; [self.enableGraphsSwitch setOn:NO];
@@ -30,7 +30,7 @@
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate, AVPictureInPictureControllerDelegate, OnScreenFunctionalButtonDelegate> @interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate, AVPictureInPictureControllerDelegate, OnScreenFunctionalButtonDelegate>
#endif #endif
@property (nonatomic) StreamManager* streamMan; @property (nonatomic, strong) StreamManager* streamMan;
@property (nonatomic) StreamConfiguration* streamConfig; @property (nonatomic) StreamConfiguration* streamConfig;
@property (nonatomic, strong) AVPictureInPictureController *pipController API_AVAILABLE(ios(9.0)); @property (nonatomic, strong) AVPictureInPictureController *pipController API_AVAILABLE(ios(9.0));
@property (nonatomic, strong) AVPictureInPictureControllerContentSource *pipContentSource API_AVAILABLE(ios(15.0)); // Needed for iOS 15+ layer-based PiP @property (nonatomic, strong) AVPictureInPictureControllerContentSource *pipContentSource API_AVAILABLE(ios(15.0)); // Needed for iOS 15+ layer-based PiP
@@ -47,7 +47,6 @@
@implementation StreamFrameViewController { @implementation StreamFrameViewController {
ControllerSupport *_controllerSupport; ControllerSupport *_controllerSupport;
StreamManager *_streamMan;
TemporarySettings *_settings; TemporarySettings *_settings;
OSCProfile* _oscProfile; OSCProfile* _oscProfile;
NSTimer *_inactivityTimer; NSTimer *_inactivityTimer;
@@ -1092,6 +1091,7 @@
- (void)applicationDidBecomeActive:(NSNotification *)notification { - (void)applicationDidBecomeActive:(NSNotification *)notification {
appDidEnterBackgroundWithoutPip = false; appDidEnterBackgroundWithoutPip = false;
[_streamMan setNeedRequeuing:true];
// Stop the background timer, since we're foregrounded again // Stop the background timer, since we're foregrounded again
if (_inactivityTimer != nil) { if (_inactivityTimer != nil) {
Log(LOG_I, @"Stopping inactivity timer after becoming active again"); Log(LOG_I, @"Stopping inactivity timer after becoming active again");
@@ -1338,11 +1338,15 @@
&& _settings.sdrPerformanceWorkaround && _settings.sdrPerformanceWorkaround
&& [Utils hdrSupported] && [Utils hdrSupported]
){ ){
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)); dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if(LiGetCurrentHostDisplayHdrMode()){ if(LiGetCurrentHostDisplayHdrMode()){
NSArray* hdrCommand = [CommandManager.shared extractAutoReleaseButtonStringsFrom:@"WIN+ALT+B"]; NSArray* hdrCommand = [CommandManager.shared extractAutoReleaseButtonStringsFrom:@"WIN+ALT+B"];
[CommandManager.shared sendAutoReleaseComboCommandWithCmdStrings:hdrCommand delay:0.15 index:0 pressOnly:false releaseOnly:false]; [CommandManager.shared sendAutoReleaseComboCommandWithCmdStrings:hdrCommand delay:0.15 index:0 pressOnly:false releaseOnly:false];
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self->_streamMan setNeedRequeuing:true];
});
} }
}); });
} }
@@ -1711,6 +1715,11 @@
Log(LOG_I, @"View size changed, terminating stream"); Log(LOG_I, @"View size changed, terminating stream");
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self->_streamMan setNeedRequeuing:true];
});
double delayInSeconds = 0.2; double delayInSeconds = 0.2;
if (_delayedRemoveExtScreen) { if (_delayedRemoveExtScreen) {
dispatch_block_cancel(_delayedRemoveExtScreen); dispatch_block_cancel(_delayedRemoveExtScreen);