revert: legacy frame pacing changes for VideoDecoderRenderer
This commit is contained in:
1 parent
56729469bf
commit
8547cee1b9
1 file changed
+127
-226
@@ -55,13 +55,6 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
NSInteger _maxRefreshRate;
|
NSInteger _maxRefreshRate;
|
||||||
RenderingBackend _renderingBackend;
|
RenderingBackend _renderingBackend;
|
||||||
|
|
||||||
// Frame pacing mode
|
|
||||||
BOOL _useLegacyPacing;
|
|
||||||
|
|
||||||
// Legacy pacing frame counter and queue management
|
|
||||||
NSInteger _simplePacingFrameCount;
|
|
||||||
NSInteger _enqueuedSampleBuffers;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reinitializeDisplayLayer
|
- (void)reinitializeDisplayLayer
|
||||||
@@ -122,11 +115,11 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
self = [super init];
|
self = [super init];
|
||||||
|
|
||||||
_sq = dispatch_queue_create("com.moonlight.VideoDecoderRenderer",
|
_sq = dispatch_queue_create("com.moonlight.VideoDecoderRenderer",
|
||||||
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0));
|
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0));
|
||||||
|
|
||||||
// Video decoder needs to run at the highest priority since DisplayLink waits on it
|
// Video decoder needs to run at the highest priority since DisplayLink waits on it
|
||||||
_vtq = dispatch_queue_create("com.moonlight.VideoDecoderRenderer.VTDecoder",
|
_vtq = dispatch_queue_create("com.moonlight.VideoDecoderRenderer.VTDecoder",
|
||||||
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0));
|
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0));
|
||||||
|
|
||||||
_view = view;
|
_view = view;
|
||||||
_callbacks = callbacks;
|
_callbacks = callbacks;
|
||||||
@@ -136,22 +129,16 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
|
|
||||||
DataManager* dataMan = [[DataManager alloc] init];
|
DataManager* dataMan = [[DataManager alloc] init];
|
||||||
|
|
||||||
// Check if we should use legacy pacing mode
|
_frameQueue = [FrameQueue sharedInstance];
|
||||||
_useLegacyPacing = [[dataMan getSettings].framePacingMode integerValue] == FramePacingModeLegacy;
|
[_frameQueue start];
|
||||||
|
[_frameQueue setHighWaterMark:(int)[[dataMan getSettings].frameQueueSize integerValue]];
|
||||||
if (!_useLegacyPacing) {
|
|
||||||
// Queue pacing mode uses FrameQueue
|
|
||||||
_frameQueue = [FrameQueue sharedInstance];
|
|
||||||
[_frameQueue start];
|
|
||||||
[_frameQueue setHighWaterMark:(int)[[dataMan getSettings].frameQueueSize integerValue]];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self reinitializeDisplayLayer];
|
[self reinitializeDisplayLayer];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(reinitializeDisplayLayer)
|
selector:@selector(reinitializeDisplayLayer)
|
||||||
name:@"ScreenChanged"
|
name:@"ScreenChanged"
|
||||||
object:nil];
|
object:nil];
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -168,17 +155,11 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
|
|
||||||
DataManager* dataMan = [[DataManager alloc] init];
|
DataManager* dataMan = [[DataManager alloc] init];
|
||||||
if ([[dataMan getSettings].renderingBackend integerValue] == RENDER_AVSB) {
|
if ([[dataMan getSettings].renderingBackend integerValue] == RENDER_AVSB) {
|
||||||
|
// PACING_MODE_VSYNC:
|
||||||
|
// Deliver 1 frame at each vsync interval. Ignores server pts timestamps.
|
||||||
|
// Drop frames intelligently to maintain chosen queue size.
|
||||||
_renderingBackend = RENDER_AVSB;
|
_renderingBackend = RENDER_AVSB;
|
||||||
|
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(renderModeAVSB:)];
|
||||||
if (_useLegacyPacing) {
|
|
||||||
// Legacy pacing mode
|
|
||||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(renderModeLegacy:)];
|
|
||||||
} else {
|
|
||||||
// PACING_MODE_VSYNC:
|
|
||||||
// Deliver 1 frame at each vsync interval. Ignores server pts timestamps.
|
|
||||||
// Drop frames intelligently to maintain chosen queue size.
|
|
||||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(renderModeAVSB:)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
||||||
_displayLink.preferredFrameRateRange = CAFrameRateRangeMake(self->_frameRate, self->_frameRate, self->_frameRate);
|
_displayLink.preferredFrameRateRange = CAFrameRateRangeMake(self->_frameRate, self->_frameRate, self->_frameRate);
|
||||||
@@ -195,6 +176,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
|
|
||||||
|
|
||||||
- (void)setupDecompressionSessionWithAttributes:(NSDictionary *)destinationPixelBufferAttributes {
|
- (void)setupDecompressionSessionWithAttributes:(NSDictionary *)destinationPixelBufferAttributes {
|
||||||
|
// This method is called from within synchronized block, so no additional sync needed here
|
||||||
if (_decompressionSession != NULL) {
|
if (_decompressionSession != NULL) {
|
||||||
VTDecompressionSessionInvalidate(_decompressionSession);
|
VTDecompressionSessionInvalidate(_decompressionSession);
|
||||||
CFRelease(_decompressionSession);
|
CFRelease(_decompressionSession);
|
||||||
@@ -260,33 +242,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
|
|
||||||
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
||||||
|
|
||||||
#pragma mark DisplayLink - Legacy Frame Pacing
|
#pragma mark DisplayLink - Frame Pacing - Vsync with FrameQueue
|
||||||
|
|
||||||
// Legacy frame pacing method from upstream/Integration - direct polling without FrameQueue
|
|
||||||
- (void)renderModeLegacy:(CADisplayLink *)link {
|
|
||||||
VIDEO_FRAME_HANDLE handle;
|
|
||||||
PDECODE_UNIT du;
|
|
||||||
|
|
||||||
while (LiPollNextVideoFrame(&handle, &du)) {
|
|
||||||
LiCompleteVideoFrame(handle, DrSubmitDecodeUnit(du));
|
|
||||||
|
|
||||||
// Calculate the actual display refresh rate
|
|
||||||
double displayRefreshRate = 1 / (link.targetTimestamp - link.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark DisplayLink - Frame Pacing - Vsync with FrameQueue (Queue Pacing)
|
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -338,11 +294,9 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
// we missed a callback
|
// we missed a callback
|
||||||
// Log(LOG_W, @"*** slow frametime %.3f ms", frametime * 1000.0);
|
// Log(LOG_W, @"*** slow frametime %.3f ms", frametime * 1000.0);
|
||||||
}
|
}
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
||||||
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
|
||||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
lastTargetLocal = targetLocal;
|
lastTargetLocal = targetLocal;
|
||||||
|
|
||||||
@@ -410,9 +364,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
}
|
}
|
||||||
- (void)cleanup
|
- (void)cleanup
|
||||||
{
|
{
|
||||||
if (_frameQueue) {
|
[_frameQueue stop];
|
||||||
[_frameQueue stop];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_renderingBackend == RENDER_AVSB) {
|
if (_renderingBackend == RENDER_AVSB) {
|
||||||
[_displayLink invalidate];
|
[_displayLink invalidate];
|
||||||
@@ -448,7 +400,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
// Write the length prefix to the new buffer
|
// Write the length prefix to the new buffer
|
||||||
const int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
const int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
||||||
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
||||||
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
||||||
status = CMBlockBufferReplaceDataBytes(lengthBytes, frameBuffer,
|
status = CMBlockBufferReplaceDataBytes(lengthBytes, frameBuffer,
|
||||||
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
||||||
if (status != noErr) {
|
if (status != noErr) {
|
||||||
@@ -660,9 +612,9 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
// Referenced the VP9 code in Chrome that performs a similar function
|
// Referenced the VP9 code in Chrome that performs a similar function
|
||||||
// https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/mac/vt_config_util.mm;drc=977dc02c431b4979e34c7792bc3d646f649dacb4;l=155
|
// https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/mac/vt_config_util.mm;drc=977dc02c431b4979e34c7792bc3d646f649dacb4;l=155
|
||||||
extensions[(__bridge NSString*)kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms] =
|
extensions[(__bridge NSString*)kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms] =
|
||||||
@{
|
@{
|
||||||
@"av1C" : [self getAv1CodecConfigurationBox:frameData],
|
@"av1C" : [self getAv1CodecConfigurationBox:frameData],
|
||||||
};
|
};
|
||||||
extensions[@"BitsPerComponent"] = @(bitstreamCtx->bit_depth);
|
extensions[@"BitsPerComponent"] = @(bitstreamCtx->bit_depth);
|
||||||
|
|
||||||
#undef SET_EXTENSION
|
#undef SET_EXTENSION
|
||||||
@@ -871,10 +823,10 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
|
|
||||||
CMSampleBufferRef sampleBuffer;
|
CMSampleBufferRef sampleBuffer;
|
||||||
status = CMSampleBufferCreateReady(kCFAllocatorDefault,
|
status = CMSampleBufferCreateReady(kCFAllocatorDefault,
|
||||||
frameBlockBuffer,
|
frameBlockBuffer,
|
||||||
_formatDesc, 1, 1,
|
_formatDesc, 1, 1,
|
||||||
&sampleTiming, 0, NULL,
|
&sampleTiming, 0, NULL,
|
||||||
&sampleBuffer);
|
&sampleBuffer);
|
||||||
if (status != noErr) {
|
if (status != noErr) {
|
||||||
Log(LOG_E, @"CMSampleBufferCreate failed: %d", (int)status);
|
Log(LOG_E, @"CMSampleBufferCreate failed: %d", (int)status);
|
||||||
CFRelease(dataBlockBuffer);
|
CFRelease(dataBlockBuffer);
|
||||||
@@ -898,163 +850,115 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
frameNumber:(int)frameNumber
|
frameNumber:(int)frameNumber
|
||||||
frameType:(int)frameType
|
frameType:(int)frameType
|
||||||
decodeStartTime:(CFTimeInterval)decodeStartTime {
|
decodeStartTime:(CFTimeInterval)decodeStartTime {
|
||||||
// Synchronize access to decompression session to prevent race conditions during background/foreground transitions
|
// Synchronize access to decompression session to prevent race conditions during background/foreground transitions
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
// Check if we need to create/recreate the decompression session
|
// Check if we need to create/recreate the decompression session
|
||||||
BOOL needsNewSession = (frameType == FRAME_TYPE_IDR || _decompressionSession == nil);
|
BOOL needsNewSession = (frameType == FRAME_TYPE_IDR || _decompressionSession == nil);
|
||||||
|
|
||||||
// Also check if the session might have been invalidated by iOS during background
|
// Also check if the session might have been invalidated by iOS during background
|
||||||
if (!needsNewSession && _decompressionSession != nil) {
|
if (!needsNewSession && _decompressionSession != nil) {
|
||||||
Boolean isValid = VTDecompressionSessionCanAcceptFormatDescription(_decompressionSession, _formatDesc);
|
Boolean isValid = VTDecompressionSessionCanAcceptFormatDescription(_decompressionSession, _formatDesc);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
Log(LOG_W, @"Decompression session is invalid, needs recreation");
|
Log(LOG_W, @"Decompression session is invalid, needs recreation");
|
||||||
needsNewSession = YES;
|
needsNewSession = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsNewSession) {
|
if (needsNewSession) {
|
||||||
[self setupDecompressionSession];
|
[self setupDecompressionSession];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_decompressionSession == nil) {
|
if (_decompressionSession == nil) {
|
||||||
Log(LOG_E, @"Failed to create decompression session");
|
Log(LOG_E, @"Failed to create decompression session");
|
||||||
return kVTInvalidSessionErr;
|
return kVTInvalidSessionErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
OSStatus status = VTDecompressionSessionDecodeFrameWithOutputHandler(
|
OSStatus status = VTDecompressionSessionDecodeFrameWithOutputHandler(
|
||||||
_decompressionSession,
|
_decompressionSession,
|
||||||
sampleBuffer,
|
sampleBuffer,
|
||||||
0,
|
0,
|
||||||
NULL,
|
NULL,
|
||||||
^(OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef _Nullable imageBuffer, CMTime presentationTimestamp, CMTime presentationDuration) {
|
^(OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef _Nullable imageBuffer, CMTime presentationTimestamp, CMTime presentationDuration) {
|
||||||
if (status != noErr || !imageBuffer) {
|
if (status != noErr || !imageBuffer) {
|
||||||
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
|
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
|
||||||
Log(LOG_E, @"Decompression session error: %@", error);
|
Log(LOG_E, @"Decompression session error: %@", error);
|
||||||
if (status == kVTInvalidSessionErr) {
|
if (status == kVTInvalidSessionErr) {
|
||||||
// The session was invalidated by the OS. Destroy our reference
|
// The session was invalidated by the OS. Destroy our reference
|
||||||
// so it gets recreated on the next IDR frame.
|
// so it gets recreated on the next IDR frame.
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
if (self->_decompressionSession) {
|
if (self->_decompressionSession) {
|
||||||
VTDecompressionSessionInvalidate(self->_decompressionSession);
|
VTDecompressionSessionInvalidate(self->_decompressionSession);
|
||||||
CFRelease(self->_decompressionSession);
|
CFRelease(self->_decompressionSession);
|
||||||
self->_decompressionSession = nil;
|
self->_decompressionSession = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
LiRequestIdrFrame(); // Request an IDR to restart the stream
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
LiRequestIdrFrame(); // Request an IDR to restart the stream
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CMSampleBufferRef sampleBufferOut = nil;
|
CMSampleBufferRef sampleBufferOut = nil;
|
||||||
CVPixelBufferRef pixelBuffer = nil;
|
CVPixelBufferRef pixelBuffer = nil;
|
||||||
|
|
||||||
// AVSampleBuffer path: package into a SampleBuffer
|
// AVSampleBuffer path: package into a SampleBuffer
|
||||||
if (self->_renderingBackend == RENDER_AVSB) {
|
if (self->_renderingBackend == RENDER_AVSB) {
|
||||||
if (self->_formatDescImageBuffer == NULL || !CMVideoFormatDescriptionMatchesImageBuffer(self->_formatDescImageBuffer, imageBuffer)) {
|
if (self->_formatDescImageBuffer == NULL || !CMVideoFormatDescriptionMatchesImageBuffer(self->_formatDescImageBuffer, imageBuffer)) {
|
||||||
OSStatus res = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, imageBuffer, &(self->_formatDescImageBuffer));
|
OSStatus res = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, imageBuffer, &(self->_formatDescImageBuffer));
|
||||||
if (res != noErr) {
|
if (res != noErr) {
|
||||||
Log(LOG_E, @"Failed to create video format description from imageBuffer");
|
Log(LOG_E, @"Failed to create video format description from imageBuffer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CMSampleTimingInfo sampleTiming = {kCMTimeInvalid, presentationTimestamp, presentationDuration};
|
CMSampleTimingInfo sampleTiming = {kCMTimeInvalid, presentationTimestamp, presentationDuration};
|
||||||
|
|
||||||
OSStatus err =
|
OSStatus err =
|
||||||
CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, imageBuffer, self->_formatDescImageBuffer, &sampleTiming, &sampleBufferOut);
|
CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, imageBuffer, self->_formatDescImageBuffer, &sampleTiming, &sampleBufferOut);
|
||||||
if (err != noErr) {
|
if (err != noErr) {
|
||||||
Log(LOG_E, @"Error creating sample buffer for decompressed image buffer %d", (int)err);
|
Log(LOG_E, @"Error creating sample buffer for decompressed image buffer %d", (int)err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (self->_renderingBackend == RENDER_METAL) {
|
} else if (self->_renderingBackend == RENDER_METAL) {
|
||||||
// Metal path: retain the pixelBuffer here so it survives the dispatch
|
// Metal path: retain the pixelBuffer here so it survives the dispatch
|
||||||
pixelBuffer = CVPixelBufferRetain((CVPixelBufferRef)imageBuffer);
|
pixelBuffer = CVPixelBufferRetain((CVPixelBufferRef)imageBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch onto our higher priority queue
|
// Dispatch onto our higher priority queue
|
||||||
dispatch_async(self->_vtq, ^{
|
dispatch_async(self->_vtq, ^{
|
||||||
if (self->_useLegacyPacing && self->_renderingBackend == RENDER_AVSB) {
|
Frame *frame = nil;
|
||||||
// Legacy pacing: Process all frames normally, but with immediate display timing
|
if (self->_renderingBackend == RENDER_AVSB) {
|
||||||
|
frame = [[Frame alloc] initWithSampleBuffer:sampleBufferOut frameNumber:frameNumber frameType:frameType];
|
||||||
|
} else {
|
||||||
|
frame = [[Frame alloc] initWithPixelBufffer:pixelBuffer frameNumber:frameNumber frameType:frameType pts:presentationTimestamp];
|
||||||
|
[frame setFormatDesc:self->_formatDesc];
|
||||||
|
}
|
||||||
|
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
|
||||||
|
|
||||||
// Set presentation time to current time for immediate display
|
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
||||||
CMTime targetTime = CMTimeMakeWithSeconds(CACurrentMediaTime(), NSEC_PER_SEC);
|
static PlotMetrics frameQueueMetrics = {};
|
||||||
CMSampleBufferSetOutputPresentationTimeStamp(sampleBufferOut, targetTime);
|
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
||||||
|
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
||||||
|
|
||||||
// Dispatch display layer operations to main thread to avoid CATransaction warnings
|
[[ImGuiPlots sharedInstance] observeFloat:PLOT_DROPPED value:framesDropped];
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
if (frameType == FRAME_TYPE_IDR) {
|
|
||||||
// Ensure the layer is visible now
|
|
||||||
self->_displayLayer.hidden = NO;
|
|
||||||
|
|
||||||
// Tell our parent VC to hide the progress indicator
|
// It's important we capture host metrics on the incoming thread, as this frame object
|
||||||
[self->_callbacks videoContentShown];
|
// may have been dropped by the above enqueue
|
||||||
}
|
static CFTimeInterval lastHostFrame = 0.0f;
|
||||||
|
if (lastHostFrame != 0) {
|
||||||
|
[[ImGuiPlots sharedInstance] observeFloat:PLOT_HOST_FRAMETIME value:(frame.pts - lastHostFrame) * 1000.0];
|
||||||
|
}
|
||||||
|
lastHostFrame = frame.pts;
|
||||||
|
|
||||||
if ([self->_displayLayer controlTimebase] == NULL) {
|
// Decode time is not graphed because it is marked as hidden, but we can use the same mechanism for the value used by stats
|
||||||
// On first frame, set timebase
|
static PlotMetrics decodeMetrics = {};
|
||||||
CMTimebaseRef timebase = NULL;
|
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_DECODE value:(CACurrentMediaTime() - decodeStartTime) * 1000.0 plotMetrics:&decodeMetrics];
|
||||||
CMTimebaseCreateWithSourceClock(CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &timebase);
|
[self safeCopyMetricsTo:&self->_decodeMetrics from:&decodeMetrics];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
CMTime pts = CMSampleBufferGetOutputPresentationTimeStamp(sampleBufferOut);
|
return status;
|
||||||
CMTimebaseSetTime(timebase, pts);
|
}
|
||||||
CMTimebaseSetRate(timebase, 1.0);
|
|
||||||
|
|
||||||
[self->_displayLayer setControlTimebase:timebase];
|
|
||||||
if (timebase) {
|
|
||||||
CFRelease(timebase);
|
|
||||||
}
|
|
||||||
Log(LOG_I, @"Setting timebase for legacy pacing to %d / %d", pts.value, pts.timescale);
|
|
||||||
}
|
|
||||||
|
|
||||||
[self->_displayLayer enqueueSampleBuffer:sampleBufferOut];
|
|
||||||
CFRelease(sampleBufferOut);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Queue pacing: use FrameQueue
|
|
||||||
Frame *frame = nil;
|
|
||||||
if (self->_renderingBackend == RENDER_AVSB) {
|
|
||||||
frame = [[Frame alloc] initWithSampleBuffer:sampleBufferOut frameNumber:frameNumber frameType:frameType];
|
|
||||||
} else {
|
|
||||||
frame = [[Frame alloc] initWithPixelBufffer:pixelBuffer frameNumber:frameNumber frameType:frameType pts:presentationTimestamp];
|
|
||||||
[frame setFormatDesc:self->_formatDesc];
|
|
||||||
}
|
|
||||||
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
|
|
||||||
|
|
||||||
// Capture metrics on main thread to avoid UIApplication thread warning
|
|
||||||
int frameQueueCount = [self->_frameQueue count];
|
|
||||||
CFTimeInterval capturedDecodeTime = CACurrentMediaTime() - decodeStartTime;
|
|
||||||
CFTimeInterval hostFramePts = frame.pts;
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
|
||||||
static PlotMetrics frameQueueMetrics = {};
|
|
||||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:frameQueueCount plotMetrics:&frameQueueMetrics];
|
|
||||||
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
|
||||||
|
|
||||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_DROPPED value:framesDropped];
|
|
||||||
|
|
||||||
// It's important we capture host metrics on the incoming thread, as this frame object
|
|
||||||
// may have been dropped by the above enqueue
|
|
||||||
static CFTimeInterval lastHostFrame = 0.0f;
|
|
||||||
if (lastHostFrame != 0) {
|
|
||||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_HOST_FRAMETIME value:(hostFramePts - lastHostFrame) * 1000.0];
|
|
||||||
}
|
|
||||||
lastHostFrame = hostFramePts;
|
|
||||||
|
|
||||||
// Decode time is not graphed because it is marked as hidden, but we can use the same mechanism for the value used by stats
|
|
||||||
static PlotMetrics decodeMetrics = {};
|
|
||||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_DECODE
|
|
||||||
value:capturedDecodeTime * 1000.0
|
|
||||||
plotMetrics:&decodeMetrics];
|
|
||||||
[self safeCopyMetricsTo:&self->_decodeMetrics from:&decodeMetrics];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setHdrMode:(BOOL)enabled {
|
- (void)setHdrMode:(BOOL)enabled {
|
||||||
@@ -1066,10 +970,10 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
if (hasMetadata && hdrMetadata.displayPrimaries[0].x != 0 && hdrMetadata.maxDisplayLuminance != 0) {
|
if (hasMetadata && hdrMetadata.displayPrimaries[0].x != 0 && hdrMetadata.maxDisplayLuminance != 0) {
|
||||||
// This data is all in big-endian
|
// This data is all in big-endian
|
||||||
struct {
|
struct {
|
||||||
vector_ushort2 primaries[3];
|
vector_ushort2 primaries[3];
|
||||||
vector_ushort2 white_point;
|
vector_ushort2 white_point;
|
||||||
uint32_t luminance_max;
|
uint32_t luminance_max;
|
||||||
uint32_t luminance_min;
|
uint32_t luminance_min;
|
||||||
} __attribute__((packed, aligned(4))) mdcv;
|
} __attribute__((packed, aligned(4))) mdcv;
|
||||||
|
|
||||||
// mdcv is in GBR order while SS_HDR_METADATA is in RGB order
|
// mdcv is in GBR order while SS_HDR_METADATA is in RGB order
|
||||||
@@ -1147,16 +1051,13 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
if (_renderingBackend == RENDER_METAL) {
|
if (_renderingBackend == RENDER_METAL) {
|
||||||
stats->renderingBackendString = [NSString stringWithFormat:@"Metal, colorspace: %@", [MetalVideoRenderer currentColorSpace]];
|
stats->renderingBackendString = [NSString stringWithFormat:@"Metal, colorspace: %@", [MetalVideoRenderer currentColorSpace]];
|
||||||
} else {
|
} else {
|
||||||
NSString *pacingMode = _useLegacyPacing ? @"Legacy" : @"Queue";
|
stats->renderingBackendString = @"AVSampleBuffer";
|
||||||
stats->renderingBackendString = [NSString stringWithFormat:@"AVSampleBuffer (%@ pacing)", pacingMode];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_sync(_sq, ^{
|
dispatch_sync(_sq, ^{
|
||||||
memcpy(&stats->decodeMetrics, &_decodeMetrics, sizeof(PlotMetrics));
|
memcpy(&stats->decodeMetrics, &_decodeMetrics, sizeof(PlotMetrics));
|
||||||
if (_frameQueue) {
|
memcpy(&stats->frameQueueMetrics, &_frameQueueMetrics, sizeof(PlotMetrics));
|
||||||
memcpy(&stats->frameQueueMetrics, &_frameQueueMetrics, sizeof(PlotMetrics));
|
[_frameQueue.frameDropMetrics copyMetrics:&stats->frameDropMetrics];
|
||||||
[_frameQueue.frameDropMetrics copyMetrics:&stats->frameDropMetrics];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,7 +1069,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
static int lastTargetRate = 0;
|
static int lastTargetRate = 0;
|
||||||
int targetRate = (int)_maxRefreshRate;
|
int targetRate = (int)_maxRefreshRate;
|
||||||
|
|
||||||
if (_maxRefreshRate <= 60 || _maxRefreshRate == 90 || !_frameQueue) {
|
if (_maxRefreshRate <= 60 || _maxRefreshRate == 90) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user