From 05b9da63746f60a4fe4031d5d1a1f0e46387cef6 Mon Sep 17 00:00:00 2001 From: shogo4405 Date: Sun, 17 Nov 2024 07:36:49 +0900 Subject: [PATCH] backport #1624 to 1.9.x --- .../xcschemes/Example iOS.xcscheme | 2 + Sources/IO/MediaLink.swift | 13 +++-- Sources/Screen/Choreographer.swift | 49 +++++++------------ Sources/Screen/Screen.swift | 2 +- 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/HaishinKit.xcodeproj/xcshareddata/xcschemes/Example iOS.xcscheme b/HaishinKit.xcodeproj/xcshareddata/xcschemes/Example iOS.xcscheme index 5d485a30a..0cbd8e999 100644 --- a/HaishinKit.xcodeproj/xcshareddata/xcschemes/Example iOS.xcscheme +++ b/HaishinKit.xcodeproj/xcshareddata/xcschemes/Example iOS.xcscheme @@ -34,6 +34,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" + disablePerformanceAntipatternChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Sources/IO/MediaLink.swift b/Sources/IO/MediaLink.swift index 5af4bcabd..b9a4d0ad8 100644 --- a/Sources/IO/MediaLink.swift +++ b/Sources/IO/MediaLink.swift @@ -54,6 +54,7 @@ final class MediaLink { private var scheduledAudioBuffers: Atomic = .init(0) private var presentationTimeStampOrigin: CMTime = .invalid private var audioTime = IOAudioTime() + private var duration: TimeInterval = 0.0 func enqueue(_ buffer: CMSampleBuffer) { guard buffer.presentationTimeStamp != .invalid else { @@ -102,14 +103,17 @@ final class MediaLink { }) } - private func duration(_ duraiton: Double) -> Double { + private func duration(_ timestamp: Double) -> Double { + defer { + duration += timestamp + } if playerNode.isPlaying { guard let nodeTime = playerNode.lastRenderTime, let playerTime = playerNode.playerTime(forNodeTime: nodeTime) else { return 0.0 } return TimeInterval(playerTime.sampleTime) / playerTime.sampleRate } - return duraiton + return duration } private func makeBufferkQueue() { @@ -123,11 +127,11 @@ final class MediaLink { extension MediaLink: ChoreographerDelegate { // MARK: ChoreographerDelegate - func choreographer(_ choreographer: some Choreographer, didFrame duration: Double) { + func choreographer(_ choreographer: some Choreographer, didFrame timestamp: TimeInterval, targetTimestamp: TimeInterval) { guard let bufferQueue else { return } - let duration = self.duration(duration) + let duration = self.duration(targetTimestamp - timestamp) var frameCount = 0 while !bufferQueue.isEmpty { guard let first = bufferQueue.head else { @@ -158,6 +162,7 @@ extension MediaLink: Running { hasVideo = false bufferingTime = kMediaLink_bufferingTime isBuffering = true + duration = 0.0 choreographer.startRunning() makeBufferkQueue() isRunning.mutate { $0 = true } diff --git a/Sources/Screen/Choreographer.swift b/Sources/Screen/Choreographer.swift index 5e23a6471..5b1fc7ce2 100644 --- a/Sources/Screen/Choreographer.swift +++ b/Sources/Screen/Choreographer.swift @@ -3,7 +3,6 @@ import Foundation #if os(macOS) import CoreVideo -import Foundation // swiftlint:disable attributes @@ -30,11 +29,12 @@ final class DisplayLink: NSObject { frameInterval = 1.0 / Double(preferredFramesPerSecond) } } - private(set) var duration = 0.0 private(set) var timestamp: CFTimeInterval = 0 + private(set) var targetTimestamp: CFTimeInterval = 0 private var selector: Selector? private var displayLink: CVDisplayLink? private var frameInterval = 0.0 + private var duration: CFTimeInterval = 0 private weak var delegate: NSObject? deinit { @@ -53,25 +53,24 @@ final class DisplayLink: NSObject { guard let self else { return kCVReturnSuccess } - self.duration += inNow.pointee.duration - if frameInterval == 0 || frameInterval < inNow.pointee.timestamp - self.timestamp { - self.timestamp = inNow.pointee.timestamp + if frameInterval == 0 || frameInterval <= inNow.pointee.timestamp - self.timestamp { + self.timestamp = Double(inNow.pointee.timestamp) + self.targetTimestamp = self.timestamp + frameInterval _ = self.delegate?.perform(self.selector, with: self) - self.duration = 0.0 } return kCVReturnSuccess } } func add(to runloop: RunLoop, forMode mode: RunLoop.Mode) { - guard let displayLink = displayLink, !isPaused else { + guard let displayLink, !isPaused else { return } CVDisplayLinkStart(displayLink) } func invalidate() { - guard let displayLink = displayLink, isPaused else { + guard let displayLink, isPaused else { return } CVDisplayLinkStop(displayLink) @@ -81,11 +80,7 @@ final class DisplayLink: NSObject { extension CVTimeStamp { @inlinable @inline(__always) var timestamp: Double { - Double(self.videoTime) / Double(self.videoTimeScale) - } - - @inlinable @inline(__always) var duration: Double { - Double(self.videoRefreshPeriod) / Double(self.videoTimeScale) + Double(self.hostTime) / Double(self.videoTimeScale) } } @@ -97,18 +92,15 @@ typealias DisplayLink = CADisplayLink #endif protocol ChoreographerDelegate: AnyObject { - func choreographer(_ choreographer: some Choreographer, didFrame duration: Double) + func choreographer(_ choreographer: some Choreographer, didFrame timestamp: TimeInterval, targetTimestamp: TimeInterval) } protocol Choreographer: Running { var isPaused: Bool { get set } var delegate: (any ChoreographerDelegate)? { get set } - - func clear() } final class DisplayLinkChoreographer: NSObject, Choreographer { - private static let duration = 0.0 private static let preferredFramesPerSecond = 0 var isPaused: Bool { @@ -121,28 +113,26 @@ final class DisplayLinkChoreographer: NSObject, Choreographer { } weak var delegate: (any ChoreographerDelegate)? var isRunning: Atomic = .init(false) - var preferredFramesPerSecond = DisplayLinkChoreographer.preferredFramesPerSecond - private var duration: Double = DisplayLinkChoreographer.duration - private var displayLink: DisplayLink? { + var preferredFramesPerSecond = DisplayLinkChoreographer.preferredFramesPerSecond { didSet { - oldValue?.invalidate() - guard let displayLink = displayLink else { + guard let displayLink, preferredFramesPerSecond != oldValue else { return } - displayLink.isPaused = true displayLink.preferredFramesPerSecond = preferredFramesPerSecond - displayLink.add(to: .main, forMode: .common) } } - - func clear() { - duration = Self.duration + private var displayLink: DisplayLink? { + didSet { + oldValue?.invalidate() + displayLink?.isPaused = true + displayLink?.preferredFramesPerSecond = preferredFramesPerSecond + displayLink?.add(to: .main, forMode: .common) + } } @objc private func update(displayLink: DisplayLink) { - delegate?.choreographer(self, didFrame: duration) - duration += displayLink.duration + delegate?.choreographer(self, didFrame: displayLink.timestamp, targetTimestamp: displayLink.targetTimestamp) } } @@ -154,7 +144,6 @@ extension DisplayLinkChoreographer: Running { func stopRunning() { displayLink = nil - duration = DisplayLinkChoreographer.duration isRunning.mutate { $0 = false } } } diff --git a/Sources/Screen/Screen.swift b/Sources/Screen/Screen.swift index 0788c6acf..1ca9685cb 100644 --- a/Sources/Screen/Screen.swift +++ b/Sources/Screen/Screen.swift @@ -155,7 +155,7 @@ extension Screen: Running { extension Screen: ChoreographerDelegate { // MARK: ChoreographerDelegate - func choreographer(_ choreographer: some Choreographer, didFrame duration: Double) { + func choreographer(_ choreographer: some Choreographer, didFrame timestamp: TimeInterval, targetTimestamp: TimeInterval) { var pixelBuffer: CVPixelBuffer? pixelBufferPool?.createPixelBuffer(&pixelBuffer) guard let pixelBuffer else {