diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index 4fcdccb6..87169173 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -1000,6 +1000,7 @@ + diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard index b0836e15..1f851f56 100644 --- a/VoidLink/Base.lproj/iPhone.storyboard +++ b/VoidLink/Base.lproj/iPhone.storyboard @@ -1695,6 +1695,7 @@ + diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h index af86acc2..ffe6a8be 100644 --- a/VoidLink/Database/DataManager.h +++ b/VoidLink/Database/DataManager.h @@ -43,6 +43,7 @@ typedef NS_ENUM(NSInteger, GyroMode) { }; typedef NS_ENUM(NSInteger, FramePacingMode) { + FramePacingModeOff, FramePacingModeLegacy, FramePacingModeQueue }; diff --git a/VoidLink/Stream/Connection.m b/VoidLink/Stream/Connection.m index a1047c25..1c8cb8c6 100644 --- a/VoidLink/Stream/Connection.m +++ b/VoidLink/Stream/Connection.m @@ -493,9 +493,10 @@ void ClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t LiInitializeVideoCallbacks(&_drCallbacks); _drCallbacks.setup = DrDecoderSetup; _drCallbacks.cleanup = DrCleanup; - // Use pull renderer for legacy frame pacing, direct submit for modern frame pacing + // Use pull renderer for legacy and off frame pacing, direct submit for queue-based frame pacing DataManager* dataMan = [[DataManager alloc] init]; - if ([[dataMan getSettings].framePacingMode integerValue] == FramePacingModeLegacy) { + FramePacingMode framePacingMode = [[dataMan getSettings].framePacingMode integerValue]; + if (framePacingMode == FramePacingModeLegacy || framePacingMode == FramePacingModeOff) { _drCallbacks.capabilities = CAPABILITY_PULL_RENDERER | CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC | CAPABILITY_REFERENCE_FRAME_INVALIDATION_AV1; diff --git a/VoidLink/Stream/StreamManager.m b/VoidLink/Stream/StreamManager.m index 4ebce872..05bad08d 100644 --- a/VoidLink/Stream/StreamManager.m +++ b/VoidLink/Stream/StreamManager.m @@ -189,13 +189,13 @@ float interval = stats.endTime - stats.startTime; - // Check if we're using legacy pacing mode + // Get frame pacing mode DataManager* dataMan = [[DataManager alloc] init]; - BOOL isLegacyPacing = [[dataMan getSettings].framePacingMode integerValue] == FramePacingModeLegacy; - + FramePacingMode framePacingMode = [[dataMan getSettings].framePacingMode integerValue]; + // Calculate FPS differently based on pacing mode float fps; - if (isLegacyPacing) { + if (framePacingMode == FramePacingModeLegacy || framePacingMode == FramePacingModeOff) { fps = stats.totalFrames / interval; } else { float scalePlotMetrics = stats.frameDropMetrics.nsamples > 0 ? ((float)stats.frameDropMetrics.nsamples / stats.totalFrames) : 1.0f; @@ -210,10 +210,14 @@ stats.networkDroppedFrames / interval, latencyString]; else { - if (isLegacyPacing) { + if (framePacingMode == FramePacingModeLegacy || framePacingMode == FramePacingModeOff) { NSString* rendererWithPacing = stats.renderingBackendString; if ([stats.renderingBackendString isEqualToString:@"AVSampleBuffer"]) { - rendererWithPacing = @"AVSampleBuffer (Legacy Pacing)"; + if (framePacingMode == FramePacingModeOff) { + rendererWithPacing = @"AVSampleBuffer (No Pacing)"; + } else { + rendererWithPacing = @"AVSampleBuffer (Legacy Pacing)"; + } } return [LocalizationHelper localizedStringForKey:@"Video stream: %dx%d %.2f FPS (Codec: %@)\n" diff --git a/VoidLink/Stream/VideoDecoderRenderer.m b/VoidLink/Stream/VideoDecoderRenderer.m index b7bf00c0..0609eaa1 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.m +++ b/VoidLink/Stream/VideoDecoderRenderer.m @@ -55,7 +55,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, NSInteger _maxRefreshRate; RenderingBackend _renderingBackend; - BOOL _useLegacyPacing; + FramePacingMode _framePacingMode; } - (void)reinitializeDisplayLayer @@ -130,7 +130,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, DataManager* dataMan = [[DataManager alloc] init]; - _useLegacyPacing = [[dataMan getSettings].framePacingMode integerValue] == FramePacingModeLegacy; + _framePacingMode = [[dataMan getSettings].framePacingMode integerValue]; _frameQueue = [FrameQueue sharedInstance]; [_frameQueue start]; @@ -162,8 +162,8 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, // Choose the appropriate selector based on frame pacing mode SEL displayLinkSelector; - if (_useLegacyPacing) { - // Legacy frame pacing: use simple displayLinkCallback + if (_framePacingMode == FramePacingModeLegacy || _framePacingMode == FramePacingModeOff) { + // Legacy frame pacing or Off mode: use simple displayLinkCallback displayLinkSelector = @selector(displayLinkCallback:); } else { // PACING_MODE_VSYNC: @@ -332,21 +332,22 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); while (LiPollNextVideoFrame(&handle, &du)) { LiCompleteVideoFrame(handle, DrSubmitDecodeUnit(du)); - /* - // Calculate the actual display refresh rate - double displayRefreshRate = 1 / (_displayLink.targetTimestamp - _displayLink.timestamp); - - // Only pace frames if the display refresh rate is >= 90% of our stream frame rate. - // Battery saver, accessibility settings, or device thermals can cause the actual - // refresh rate of the display to drop below the physical maximum. - if (displayRefreshRate >= _frameRate * 0.9f) { - // Keep one pending frame to smooth out gaps due to - // network jitter at the cost of 1 frame of latency - if (LiGetPendingVideoFrames() == 1) { - break; + // Skip frame pacing logic if frame pacing is off + if (_framePacingMode != FramePacingModeOff) { + // Calculate the actual display refresh rate + double displayRefreshRate = 1 / (_displayLink.targetTimestamp - _displayLink.timestamp); + + // Only pace frames if the display refresh rate is >= 90% of our stream frame rate. + // Battery saver, accessibility settings, or device thermals can cause the actual + // refresh rate of the display to drop below the physical maximum. + if (displayRefreshRate >= _frameRate * 0.9f) { + // Keep one pending frame to smooth out gaps due to + // network jitter at the cost of 1 frame of latency + if (LiGetPendingVideoFrames() == 1) { + break; + } } } - */ } } @@ -858,7 +859,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); CMSampleBufferRef sampleBuffer; CMTime presentationTimeStamp; - if (_useLegacyPacing) { + if (_framePacingMode == FramePacingModeLegacy || _framePacingMode == FramePacingModeOff) { presentationTimeStamp = CMTimeMake(du->presentationTimeUs / 1000, 1000); } else { presentationTimeStamp = CMTimeMake((int64_t)du->rtpTimestamp, 90000); @@ -883,7 +884,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); return DR_NEED_IDR; } - if (_useLegacyPacing) { + if (_framePacingMode == FramePacingModeLegacy || _framePacingMode == FramePacingModeOff) { // Enqueue the next frame [self->_displayLayer enqueueSampleBuffer:sampleBuffer]; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 35b2250c..5bd20cac 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -1745,16 +1745,17 @@ BOOL isCustomResolution(int resolutionSelected) { } - (void)framePacingModeChanged:(UISegmentedControl *)sender { - [self setHidden:sender.selectedSegmentIndex == FramePacingModeLegacy forStack:self.frameQueueSizeStack]; + // Hide frame queue size for Off and Legacy modes + [self setHidden:(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy) forStack:self.frameQueueSizeStack]; [videoSection updateViewForFoldState]; - if(sender.selectedSegmentIndex == FramePacingModeLegacy){ + if(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy){ [self.enableGraphsSwitch setOn:NO]; [self findDynamicLabelFromStack:_graphOpacityStack].hidden = YES; } [self.enableGraphsSwitch setEnabled:sender.selectedSegmentIndex == FramePacingModeQueue]; [self.graphOpacityStepper setEnabled:self.enableGraphsSwitch.isOn]; - [self setHidden:sender.selectedSegmentIndex == FramePacingModeLegacy forStack:self.performanceGraphStack]; + [self setHidden:(sender.selectedSegmentIndex == FramePacingModeOff || sender.selectedSegmentIndex == FramePacingModeLegacy) forStack:self.performanceGraphStack]; [otherSection updateViewForFoldState]; /* diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index 2cbf8056..772ede18 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -32,7 +32,37 @@ } }, "2Dp-Qg-chi.segmentTitles[0]" : { - "comment" : "Class = \"UISegmentedControl\"; 2Dp-Qg-chi.segmentTitles[0] = \"Legacy\"; ObjectID = \"2Dp-Qg-chi\";", + "comment" : "Class = \"UISegmentedControl\"; 2Dp-Qg-chi.segmentTitles[0] = \"Off\"; ObjectID = \"2Dp-Qg-chi\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Off" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "关闭" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "關閉" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "關閉" + } + } + } + }, + "2Dp-Qg-chi.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; 2Dp-Qg-chi.segmentTitles[1] = \"Legacy\"; ObjectID = \"2Dp-Qg-chi\";", "extractionState" : "extracted_with_value", "localizations" : { "en" : { @@ -61,8 +91,8 @@ } } }, - "2Dp-Qg-chi.segmentTitles[1]" : { - "comment" : "Class = \"UISegmentedControl\"; 2Dp-Qg-chi.segmentTitles[1] = \"Queue Buffering\"; ObjectID = \"2Dp-Qg-chi\";", + "2Dp-Qg-chi.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; 2Dp-Qg-chi.segmentTitles[2] = \"Queue Buffering\"; ObjectID = \"2Dp-Qg-chi\";", "extractionState" : "extracted_with_value", "localizations" : { "en" : { diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings index 8115fdda..a1355f6e 100644 --- a/VoidLink/mul.lproj/iPhone.xcstrings +++ b/VoidLink/mul.lproj/iPhone.xcstrings @@ -1058,7 +1058,38 @@ } }, "CC6-pu-Uoh.segmentTitles[0]" : { - "comment" : "Class = \"UISegmentedControl\"; CC6-pu-Uoh.segmentTitles[0] = \"Legacy\"; ObjectID = \"CC6-pu-Uoh\";", + "comment" : "Class = \"UISegmentedControl\"; CC6-pu-Uoh.segmentTitles[0] = \"Off\"; ObjectID = \"CC6-pu-Uoh\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Off" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "关闭" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "關閉" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "關閉" + } + } + + } + }, + "CC6-pu-Uoh.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; CC6-pu-Uoh.segmentTitles[1] = \"Legacy\"; ObjectID = \"CC6-pu-Uoh\";", "extractionState" : "extracted_with_value", "localizations" : { "en" : { @@ -1087,8 +1118,8 @@ } } }, - "CC6-pu-Uoh.segmentTitles[1]" : { - "comment" : "Class = \"UISegmentedControl\"; CC6-pu-Uoh.segmentTitles[1] = \"Queue Buffering\"; ObjectID = \"CC6-pu-Uoh\";", + "CC6-pu-Uoh.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; CC6-pu-Uoh.segmentTitles[2] = \"Queue Buffering\"; ObjectID = \"CC6-pu-Uoh\";", "extractionState" : "extracted_with_value", "localizations" : { "en" : {