- restore asyncDequeue switch

- tweak frame buffering
This commit is contained in:
True Zhuanjia
2026-02-19 01:31:55 +08:00
parent c7ffd51cec
commit ccd032834d
9 changed files with 52 additions and 18 deletions
@@ -23,7 +23,7 @@
</entity>
<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="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="audioConfig" attributeType="Integer 32" defaultValueString="2" 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;
- (void) stopStream;
- (void) setNeedRequeuing:(bool)needRequeuing;
- (NSString*) getStatsOverlayText: (uint16_t) overlayLevel;
+5
View File
@@ -114,6 +114,11 @@
});
}
- (void) setNeedRequeuing:(bool)needRequeuing{
[_videoRenderer.frameQueue clear];
_videoRenderer.needRequeuing = needRequeuing;
}
- (void) stopStream
{
[_connection terminate];
+2
View File
@@ -18,6 +18,8 @@
@property (atomic, readonly) PlotMetrics decodeMetrics;
@property (atomic, readonly) PlotMetrics frameQueueMetrics;
@property (atomic, assign) bool needRequeuing;
@property (nonatomic, strong) FrameQueue* frameQueue;
@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;
CADisplayLink *_displayLink;
FrameQueue *_frameQueue;
uint8_t queueSize;
bool isFirstFrame;
NSInteger _maxRefreshRate;
RenderingBackend _renderingBackend;
@@ -145,9 +143,10 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
_framePacingMode = tempSettings.framePacingMode.integerValue;
_asyncFrameDequeue = tempSettings.asyncFrameDequeue;
NSLog(@"_asyncFrameDequeue %d", _asyncFrameDequeue);
_enableTimebase = false;
queueSize = tempSettings.frameQueueSize.intValue;
isFirstFrame = true;
_needRequeuing = true;
isIPhone = [Utils isIPhone];
_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]];
[self reinitializeDisplayLayer];
NSTimeInterval interval = 1.0/tempSettings.framerate.intValue;
// NSTimeInterval interval = 1.0/tempSettings.framerate.intValue;
[[NSNotificationCenter defaultCenter] addObserver:self
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
// AVSB is not actually decoding the frames anymore
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,
// 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
/*
// 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.
// 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 {
// NSTimeInterval current = link.targetTimestamp;
// NSLog(@"link %f", link.duration);
@@ -303,13 +303,17 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
CFTimeInterval waitFor = targetTime - dl0;
/*
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(isFirstFrame) isFirstFrame = false;
_needRequeuing = false;
if(_asyncFrameDequeue){
[_frameQueue dequeueWithTimeout:waitFor completion:^(Frame *frame) {
if (frame) {
@@ -1168,6 +1168,15 @@ static NSMutableSet* hostList;
_settingsViewExpanded = position != FrontViewPositionLeft;
if (position == FrontViewPositionLeft) {
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 {
self.navigationItem.leftBarButtonItems = @[];
@@ -1227,10 +1236,10 @@ static NSMutableSet* hostList;
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.appThemeStack];
[settingsViewController.renderingBackendSelector setEnabled:!_settingsExpandedInStreamView];
// Enable frame pacing mode selector only if not in stream view AND not in performance mode
BOOL shouldEnableFramePacing = !_settingsExpandedInStreamView && (settingsViewController.renderingBackendSelector.selectedSegmentIndex != RENDER_METAL);
[settingsViewController.framePacingModeSelector setEnabled:shouldEnableFramePacing];
BOOL shouldEnableFramePacingSelector = !_settingsExpandedInStreamView && (settingsViewController.renderingBackendSelector.selectedSegmentIndex != RENDER_METAL);
[settingsViewController.framePacingModeSelector setEnabled:shouldEnableFramePacingSelector];
// [settingsViewController.frameTimebaseSwitch setEnabled:shouldEnableFramePacing];
// [settingsViewController.asyncFrameDequeueSwitch setEnabled:shouldEnableFramePacing];
[settingsViewController.asyncFrameDequeueSwitch setEnabled:shouldEnableFramePacingSelector && settingsViewController.framePacingModeSelector.selectedSegmentIndex == FramePacingModeQueue];
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.frameQueueSizeStack];
// 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.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection];
// [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 addDynamicLabelForStack:self.graphOpacityStack];
@@ -2292,6 +2292,7 @@ BOOL isCustomResolution(int resolutionSelected) {
// Set pacing method to Queue and disable selector
[self.framePacingModeSelector setSelectedSegmentIndex:FramePacingModeQueue];
[self.framePacingModeSelector setEnabled:NO];
[self setHidden:true forStack:self.asyncFrameDequeueStack];
} else {
// Balanced mode (AVSB renderer) - enable PiP toggle if iOS 15+
if (@available(iOS 15.0, *)) {
@@ -2301,6 +2302,7 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.pipSwitch setEnabled:NO];
}
[self.framePacingModeSelector setEnabled:YES];
[self setHidden:false forStack:self.asyncFrameDequeueStack];
}
// Get the current settings to compare with the new selection
@@ -2331,6 +2333,7 @@ BOOL isCustomResolution(int resolutionSelected) {
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * _Nonnull action) {
self.renderingBackendSelector.selectedSegmentIndex = 0;
[self renderingBackendChanged:self.renderingBackendSelector];
NSURL *url = [NSURL URLWithString:[LocalizationHelper localizedStringForKey:@"betterPerformanceLink"]];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
@@ -2347,7 +2350,8 @@ BOOL isCustomResolution(int resolutionSelected) {
// Hide frame queue size for Off and Legacy modes
[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.asyncFrameDequeueStack];
[self setHidden:(sender.selectedSegmentIndex != FramePacingModeQueue
|| self.renderingBackendSelector.selectedSegmentIndex != 0) forStack:self.asyncFrameDequeueStack];
if(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy){
[self.enableGraphsSwitch setOn:NO];
@@ -30,7 +30,7 @@
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate, AVPictureInPictureControllerDelegate, OnScreenFunctionalButtonDelegate>
#endif
@property (nonatomic) StreamManager* streamMan;
@property (nonatomic, strong) StreamManager* streamMan;
@property (nonatomic) StreamConfiguration* streamConfig;
@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
@@ -47,7 +47,6 @@
@implementation StreamFrameViewController {
ControllerSupport *_controllerSupport;
StreamManager *_streamMan;
TemporarySettings *_settings;
OSCProfile* _oscProfile;
NSTimer *_inactivityTimer;
@@ -1092,6 +1091,7 @@
- (void)applicationDidBecomeActive:(NSNotification *)notification {
appDidEnterBackgroundWithoutPip = false;
[_streamMan setNeedRequeuing:true];
// Stop the background timer, since we're foregrounded again
if (_inactivityTimer != nil) {
Log(LOG_I, @"Stopping inactivity timer after becoming active again");
@@ -1338,11 +1338,15 @@
&& _settings.sdrPerformanceWorkaround
&& [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), ^{
if(LiGetCurrentHostDisplayHdrMode()){
NSArray* hdrCommand = [CommandManager.shared extractAutoReleaseButtonStringsFrom:@"WIN+ALT+B"];
[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");
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;
if (_delayedRemoveExtScreen) {
dispatch_block_cancel(_delayedRemoveExtScreen);