mirror of
https://github.com/moonlight-stream/moonlight-ios.git
synced 2026-06-18 14:41:14 +00:00
Add a basic stats overlay
This commit is contained in:
@@ -11,6 +11,14 @@
|
|||||||
|
|
||||||
#define CONN_TEST_SERVER "ios.conntest.moonlight-stream.org"
|
#define CONN_TEST_SERVER "ios.conntest.moonlight-stream.org"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CFTimeInterval startTime;
|
||||||
|
CFTimeInterval endTime;
|
||||||
|
int totalFrames;
|
||||||
|
int receivedFrames;
|
||||||
|
int networkDroppedFrames;
|
||||||
|
} video_stats_t;
|
||||||
|
|
||||||
@protocol ConnectionCallbacks <NSObject>
|
@protocol ConnectionCallbacks <NSObject>
|
||||||
|
|
||||||
- (void) connectionStarted;
|
- (void) connectionStarted;
|
||||||
@@ -29,5 +37,7 @@
|
|||||||
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks;
|
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks;
|
||||||
-(void) terminate;
|
-(void) terminate;
|
||||||
-(void) main;
|
-(void) main;
|
||||||
|
-(BOOL) getVideoStats:(video_stats_t*)stats;
|
||||||
|
-(NSString*) getActiveCodecName;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
static NSLock* initLock;
|
static NSLock* initLock;
|
||||||
static OpusMSDecoder* opusDecoder;
|
static OpusMSDecoder* opusDecoder;
|
||||||
static id<ConnectionCallbacks> _callbacks;
|
static id<ConnectionCallbacks> _callbacks;
|
||||||
|
static int lastFrameNumber;
|
||||||
|
static int activeVideoFormat;
|
||||||
|
static video_stats_t currentVideoStats;
|
||||||
|
static video_stats_t lastVideoStats;
|
||||||
|
|
||||||
#define OUTPUT_BUS 0
|
#define OUTPUT_BUS 0
|
||||||
|
|
||||||
@@ -54,6 +58,10 @@ static VideoDecoderRenderer* renderer;
|
|||||||
int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags)
|
int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags)
|
||||||
{
|
{
|
||||||
[renderer setupWithVideoFormat:videoFormat refreshRate:redrawRate];
|
[renderer setupWithVideoFormat:videoFormat refreshRate:redrawRate];
|
||||||
|
lastFrameNumber = 0;
|
||||||
|
activeVideoFormat = videoFormat;
|
||||||
|
memset(¤tVideoStats, 0, sizeof(currentVideoStats));
|
||||||
|
memset(&lastVideoStats, 0, sizeof(lastVideoStats));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +70,33 @@ void DrCleanup(void)
|
|||||||
[renderer cleanup];
|
[renderer cleanup];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-(BOOL) getVideoStats:(video_stats_t*)stats
|
||||||
|
{
|
||||||
|
// We return lastVideoStats because it is a complete 1 second window
|
||||||
|
if (lastVideoStats.endTime != 0) {
|
||||||
|
memcpy(stats, &lastVideoStats, sizeof(*stats));
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No stats yet
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString*) getActiveCodecName
|
||||||
|
{
|
||||||
|
switch (activeVideoFormat)
|
||||||
|
{
|
||||||
|
case VIDEO_FORMAT_H264:
|
||||||
|
return @"H.264";
|
||||||
|
case VIDEO_FORMAT_H265:
|
||||||
|
return @"HEVC";
|
||||||
|
case VIDEO_FORMAT_H265_MAIN10:
|
||||||
|
return @"HEVC Main 10";
|
||||||
|
default:
|
||||||
|
return @"UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||||
{
|
{
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
@@ -72,6 +107,30 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
|||||||
return DR_NEED_IDR;
|
return DR_NEED_IDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CFTimeInterval now = CACurrentMediaTime();
|
||||||
|
if (!lastFrameNumber) {
|
||||||
|
currentVideoStats.startTime = now;
|
||||||
|
lastFrameNumber = decodeUnit->frameNumber;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Flip stats roughly every second
|
||||||
|
if (now - currentVideoStats.startTime > 1.0f) {
|
||||||
|
lastVideoStats = currentVideoStats;
|
||||||
|
|
||||||
|
memset(¤tVideoStats, 0, sizeof(currentVideoStats));
|
||||||
|
currentVideoStats.startTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any frame number greater than m_LastFrameNumber + 1 represents a dropped frame
|
||||||
|
currentVideoStats.networkDroppedFrames += decodeUnit->frameNumber - (lastFrameNumber + 1);
|
||||||
|
currentVideoStats.totalFrames += decodeUnit->frameNumber - (lastFrameNumber + 1);
|
||||||
|
lastFrameNumber = decodeUnit->frameNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVideoStats.receivedFrames++;
|
||||||
|
currentVideoStats.totalFrames++;
|
||||||
|
currentVideoStats.endTime = now;
|
||||||
|
|
||||||
PLENTRY entry = decodeUnit->bufferList;
|
PLENTRY entry = decodeUnit->bufferList;
|
||||||
while (entry != NULL) {
|
while (entry != NULL) {
|
||||||
// Submit parameter set NALUs directly since no copy is required by the decoder
|
// Submit parameter set NALUs directly since no copy is required by the decoder
|
||||||
|
|||||||
@@ -15,4 +15,6 @@
|
|||||||
|
|
||||||
- (void) stopStream;
|
- (void) stopStream;
|
||||||
|
|
||||||
|
- (NSString*) getStatsOverlayText;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -130,4 +130,25 @@
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString*) getStatsOverlayText {
|
||||||
|
video_stats_t stats;
|
||||||
|
|
||||||
|
if (!_connection) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![_connection getVideoStats:&stats]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
float interval = stats.endTime - stats.startTime;
|
||||||
|
return [NSString stringWithFormat:@"Video stream: %dx%d %.2f FPS (Codec: %@)\nIncoming frame rate from network: %.2f FPS\nFrames dropped by your network connection: %.2f%%\n",
|
||||||
|
_config.width,
|
||||||
|
_config.height,
|
||||||
|
stats.totalFrames / interval,
|
||||||
|
[_connection getActiveCodecName],
|
||||||
|
stats.receivedFrames / interval,
|
||||||
|
stats.networkDroppedFrames / interval];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#import "VideoDecoderRenderer.h"
|
#import "VideoDecoderRenderer.h"
|
||||||
#import "StreamManager.h"
|
#import "StreamManager.h"
|
||||||
#import "ControllerSupport.h"
|
#import "ControllerSupport.h"
|
||||||
|
#import "DataManager.h"
|
||||||
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
ControllerSupport *_controllerSupport;
|
ControllerSupport *_controllerSupport;
|
||||||
StreamManager *_streamMan;
|
StreamManager *_streamMan;
|
||||||
NSTimer *_inactivityTimer;
|
NSTimer *_inactivityTimer;
|
||||||
|
NSTimer *_statsUpdateTimer;
|
||||||
UITapGestureRecognizer *_menuGestureRecognizer;
|
UITapGestureRecognizer *_menuGestureRecognizer;
|
||||||
UITapGestureRecognizer *_menuDoubleTapGestureRecognizer;
|
UITapGestureRecognizer *_menuDoubleTapGestureRecognizer;
|
||||||
UITextView *_overlayView;
|
UITextView *_overlayView;
|
||||||
@@ -126,6 +128,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)updateStatsOverlay {
|
||||||
|
NSString* overlayText = [self->_streamMan getStatsOverlayText];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self updateOverlayText:overlayText];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
- (void)updateOverlayText:(NSString*)text {
|
- (void)updateOverlayText:(NSString*)text {
|
||||||
if (_overlayView == nil) {
|
if (_overlayView == nil) {
|
||||||
_overlayView = [[UITextView alloc] init];
|
_overlayView = [[UITextView alloc] init];
|
||||||
@@ -135,7 +145,12 @@
|
|||||||
[_overlayView setUserInteractionEnabled:NO];
|
[_overlayView setUserInteractionEnabled:NO];
|
||||||
[_overlayView setSelectable:NO];
|
[_overlayView setSelectable:NO];
|
||||||
[_overlayView setScrollEnabled:NO];
|
[_overlayView setScrollEnabled:NO];
|
||||||
[_overlayView setTextAlignment:NSTextAlignmentCenter];
|
|
||||||
|
// HACK: If not using stats overlay, center the text
|
||||||
|
if (_statsUpdateTimer == nil) {
|
||||||
|
[_overlayView setTextAlignment:NSTextAlignmentCenter];
|
||||||
|
}
|
||||||
|
|
||||||
[_overlayView setTextColor:[OSColor lightGrayColor]];
|
[_overlayView setTextColor:[OSColor lightGrayColor]];
|
||||||
[_overlayView setBackgroundColor:[OSColor blackColor]];
|
[_overlayView setBackgroundColor:[OSColor blackColor]];
|
||||||
#if TARGET_OS_TV
|
#if TARGET_OS_TV
|
||||||
@@ -159,6 +174,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void) returnToMainFrame {
|
- (void) returnToMainFrame {
|
||||||
|
[_statsUpdateTimer invalidate];
|
||||||
|
_statsUpdateTimer = nil;
|
||||||
|
|
||||||
[self.navigationController popToRootViewControllerAnimated:YES];
|
[self.navigationController popToRootViewControllerAnimated:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +241,15 @@
|
|||||||
self.tipLabel.hidden = YES;
|
self.tipLabel.hidden = YES;
|
||||||
|
|
||||||
[self->_streamView showOnScreenControls];
|
[self->_streamView showOnScreenControls];
|
||||||
|
|
||||||
|
TemporarySettings* settings = [[[DataManager alloc] init] getSettings];
|
||||||
|
if (settings.statsOverlay) {
|
||||||
|
self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f
|
||||||
|
target:self
|
||||||
|
selector:@selector(updateStatsOverlay)
|
||||||
|
userInfo:nil
|
||||||
|
repeats:YES];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +374,11 @@
|
|||||||
- (void)connectionStatusUpdate:(int)status {
|
- (void)connectionStatusUpdate:(int)status {
|
||||||
Log(LOG_W, @"Connection status update: %d", status);
|
Log(LOG_W, @"Connection status update: %d", status);
|
||||||
|
|
||||||
|
// The stats overlay takes precedence over these warnings
|
||||||
|
if (_statsUpdateTimer != nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case CONN_STATUS_OKAY:
|
case CONN_STATUS_OKAY:
|
||||||
|
|||||||
Reference in New Issue
Block a user