Port for macOS (#311)

* merged moonlight-mac with moonlight-ios

* reverted to the original project.pbxproj

* cleaned up the code, fixed lots of unnecessary code duplications

* multicontroller support (not tested)

* new class that can be used for further modularization of the MainFrameViewController
This commit is contained in:
Felix Kratz
2018-03-27 08:50:40 +02:00
committed by Cameron Gutman
parent 1c86c4485d
commit 6cc165b589
73 changed files with 5116 additions and 239 deletions
+8
View File
@@ -6,11 +6,19 @@
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
#else
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (readonly, strong) NSPersistentContainer *persistentContainer;
@property (strong, nonatomic) NSWindow *window;
#endif
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
+17 -1
View File
@@ -16,6 +16,7 @@
static NSOperationQueue* mainQueue;
#if TARGET_OS_IPHONE
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UILabel appearance] setFont:[UIFont fontWithName:@"Roboto-Regular" size:[UIFont systemFontSize]]];
@@ -57,7 +58,7 @@ static NSOperationQueue* mainQueue;
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
@@ -76,6 +77,17 @@ static NSOperationQueue* mainQueue;
// Saves changes in the application's managed object context before the application terminates.
[self saveContext];
}
#else
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
[self saveContext];
}
#endif
- (void)saveContext
{
@@ -155,7 +167,11 @@ static NSOperationQueue* mainQueue;
}
- (NSURL*) getStoreURL {
#if TARGET_OS_IPHONE
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Limelight_iOS.sqlite"];
#else
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"moonlight_mac.sqlite"];
#endif
}
@end
+2 -1
View File
@@ -14,7 +14,8 @@
@interface DataManager : NSObject
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls;
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls remote:
(NSInteger)streamingRemotely;
- (NSArray*) getHosts;
- (void) updateHost:(TemporaryHost*)host;
+12 -1
View File
@@ -22,11 +22,20 @@
// HACK: Avoid calling [UIApplication delegate] off the UI thread to keep
// Main Thread Checker happy.
if ([NSThread isMainThread]) {
#if TARGET_OS_IPHONE
_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
#else
_appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
#endif
}
else {
dispatch_sync(dispatch_get_main_queue(), ^{
#if TARGET_OS_IPHONE
_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
#else
_appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
#endif
});
}
@@ -53,7 +62,8 @@
return uid;
}
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls {
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls remote:
(NSInteger) streamingRemotely {
[_managedObjectContext performBlockAndWait:^{
Settings* settingsToSave = [self retrieveSettings];
@@ -63,6 +73,7 @@
settingsToSave.height = [NSNumber numberWithInteger:height];
settingsToSave.width = [NSNumber numberWithInteger:width];
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
settingsToSave.streamingRemotely = [NSNumber numberWithInteger:streamingRemotely];
[self saveData];
}];
+1
View File
@@ -18,5 +18,6 @@
@property (nonatomic, retain) NSNumber * width;
@property (nonatomic, retain) NSNumber * onscreenControls;
@property (nonatomic, retain) NSString * uniqueId;
@property (nonatomic, retain) NSNumber * streamingRemotely;
@end
+1
View File
@@ -17,5 +17,6 @@
@dynamic width;
@dynamic onscreenControls;
@dynamic uniqueId;
@dynamic streamingRemotely;
@end
+1
View File
@@ -19,6 +19,7 @@
@property (nonatomic, retain) NSNumber * width;
@property (nonatomic, retain) NSNumber * onscreenControls;
@property (nonatomic, retain) NSString * uniqueId;
@property (nonatomic, retain) NSNumber * streamingRemotely;
- (id) initFromSettings:(Settings*)settings;
+1
View File
@@ -21,6 +21,7 @@
self.width = settings.width;
self.onscreenControls = settings.onscreenControls;
self.uniqueId = settings.uniqueId;
self.streamingRemotely = settings.streamingRemotely;
return self;
}
+12 -1
View File
@@ -10,6 +10,10 @@
// Swift
#import "Moonlight-Swift.h"
#if TARGET_OS_IPHONE
#else
#import "Gamepad.h"
#endif
@class Controller;
@class OnScreenControls;
@@ -17,8 +21,16 @@
@interface ControllerSupport : NSObject
-(id) init;
#if TARGET_OS_IPHONE
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc;
-(void) cleanup;
-(Controller*) getOscController;
#else
-(void) assignGamepad:(struct Gamepad_device *)gamepad;
-(void) removeGamepad:(struct Gamepad_device *)gamepad;
-(NSMutableDictionary*) getControllers;
#endif
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y;
-(void) updateRightStick:(Controller*)controller x:(short)x y:(short)y;
@@ -32,7 +44,6 @@
-(void) clearButtonFlag:(Controller*)controller flags:(int)flags;
-(void) updateFinished:(Controller*)controller;
-(Controller*) getOscController;
+(int) getConnectedGamepadMask;
+78 -10
View File
@@ -7,7 +7,14 @@
//
#import "ControllerSupport.h"
#if TARGET_OS_IPHONE
#import "OnScreenControls.h"
#else
#import "Gamepad.h"
#import "Control.h"
#endif
#import "DataManager.h"
#include "Limelight.h"
@@ -21,17 +28,19 @@
NSLock *_controllerStreamLock;
NSMutableDictionary *_controllers;
#if TARGET_OS_IPHONE
OnScreenControls *_osc;
bool _oscEnabled;
// This controller object is shared between on-screen controls
// and player 0
Controller *_player0osc;
char _controllerNumbers;
#define EMULATING_SELECT 0x1
#define EMULATING_SPECIAL 0x2
#endif
bool _oscEnabled;
char _controllerNumbers;
}
// UPDATE_BUTTON_FLAG(controller, flag, pressed)
@@ -76,6 +85,7 @@
}
}
#if TARGET_OS_IPHONE
-(void) handleSpecialCombosReleased:(Controller*)controller releasedButtons:(int)releasedButtons
{
if ((controller.emulatingButtonFlags & EMULATING_SELECT) &&
@@ -110,21 +120,23 @@
}
}
#endif
-(void) updateButtonFlags:(Controller*)controller flags:(int)flags
{
@synchronized(controller) {
int releasedButtons = (controller.lastButtonFlags ^ flags) & ~flags;
int pressedButtons = (controller.lastButtonFlags ^ flags) & flags;
controller.lastButtonFlags = flags;
// This must be called before handleSpecialCombosPressed
// because we clear the original button flags there
#if TARGET_OS_IPHONE
int releasedButtons = (controller.lastButtonFlags ^ flags) & ~flags;
int pressedButtons = (controller.lastButtonFlags ^ flags) & flags;
[self handleSpecialCombosReleased:controller releasedButtons:releasedButtons];
[self handleSpecialCombosPressed:controller pressedButtons:pressedButtons];
#endif
}
}
@@ -132,7 +144,9 @@
{
@synchronized(controller) {
controller.lastButtonFlags |= flags;
#if TARGET_OS_IPHONE
[self handleSpecialCombosPressed:controller pressedButtons:flags];
#endif
}
}
@@ -140,7 +154,9 @@
{
@synchronized(controller) {
controller.lastButtonFlags &= ~flags;
#if TARGET_OS_IPHONE
[self handleSpecialCombosReleased:controller releasedButtons:flags];
#endif
}
}
@@ -242,6 +258,7 @@
}
}
#if TARGET_OS_IPHONE
-(void) updateAutoOnScreenControlMode
{
// Auto on-screen control support may not be enabled
@@ -277,6 +294,7 @@
[self updateAutoOnScreenControlMode];
}
#endif
-(void) assignController:(GCController*)controller {
for (int i = 0; i < 4; i++) {
@@ -285,6 +303,7 @@
controller.playerIndex = i;
Controller* limeController;
#if TARGET_OS_IPHONE
if (i == 0) {
// Player 0 shares a controller object with the on-screen controls
limeController = _player0osc;
@@ -292,6 +311,10 @@
limeController = [[Controller alloc] init];
limeController.playerIndex = i;
}
#else
limeController = [[Controller alloc] init];
limeController.playerIndex = i;
#endif
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
@@ -301,9 +324,40 @@
}
}
#if TARGET_OS_IPHONE
-(Controller*) getOscController {
return _player0osc;
}
#else
-(NSMutableDictionary*) getControllers {
return _controllers;
}
-(void) assignGamepad:(struct Gamepad_device *)gamepad {
for (int i = 0; i < 4; i++) {
if (!(_controllerNumbers & (1 << i))) {
_controllerNumbers |= (1 << i);
gamepad->deviceID = i;
NSLog(@"Gamepad device id: %u assigned", gamepad->deviceID);
Controller* limeController;
limeController = [[Controller alloc] init];
limeController.playerIndex = i;
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:i]];
break;
}
}
}
-(void) removeGamepad:(struct Gamepad_device *)gamepad {
_controllerNumbers &= ~(1 << gamepad->deviceID);
Log(LOG_I, @"Unassigning controller index: %ld", (long)gamepad->deviceID);
// Inform the server of the updated active gamepads before removing this controller
[self updateFinished:[_controllers objectForKey:[NSNumber numberWithInteger:gamepad->deviceID]]];
[_controllers removeObjectForKey:[NSNumber numberWithInteger:gamepad->deviceID]];
}
#endif
+(int) getConnectedGamepadMask {
int mask = 0;
@@ -312,6 +366,7 @@
mask |= 1 << i;
}
#if TARGET_OS_IPHONE
DataManager* dataMan = [[DataManager alloc] init];
OnScreenControlsLevel level = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue];
@@ -320,7 +375,7 @@
if (level != OnScreenControlsLevelOff) {
mask |= 1;
}
#endif
return mask;
}
@@ -331,17 +386,27 @@
_controllerStreamLock = [[NSLock alloc] init];
_controllers = [[NSMutableDictionary alloc] init];
_controllerNumbers = 0;
#if TARGET_OS_IPHONE
_player0osc = [[Controller alloc] init];
_player0osc.playerIndex = 0;
DataManager* dataMan = [[DataManager alloc] init];
_oscEnabled = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue] != OnScreenControlsLevelOff;
#else
_oscEnabled = false;
initGamepad(self);
Gamepad_detectDevices();
#endif
Log(LOG_I, @"Number of controllers connected: %ld", (long)[[GCController controllers] count]);
for (GCController* controller in [GCController controllers]) {
[self assignController:controller];
[self registerControllerCallbacks:controller];
#if TARGET_OS_IPHONE
[self updateAutoOnScreenControlMode];
#endif
}
self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
@@ -353,8 +418,10 @@
// Register callbacks on the new controller
[self registerControllerCallbacks:controller];
#if TARGET_OS_IPHONE
// Re-evaluate the on-screen control mode
[self updateAutoOnScreenControlMode];
#endif
}];
self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Log(LOG_I, @"Controller disconnected!");
@@ -367,11 +434,12 @@
// Inform the server of the updated active gamepads before removing this controller
[self updateFinished:[_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]]];
[_controllers removeObjectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
#if TARGET_OS_IPHONE
// Re-evaluate the on-screen control mode
[self updateAutoOnScreenControlMode];
#endif
}];
return self;
}
+2 -1
View File
@@ -6,7 +6,8 @@
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
// This is redundant, as it is part of the prefix header
//#import <UIKit/UIKit.h>
#import "ControllerSupport.h"
@protocol EdgeDetectionDelegate <NSObject>
+7 -1
View File
@@ -5,14 +5,20 @@
//
#import <Availability.h>
#include <TargetConditionals.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <AppKit/AppKit.h>
#endif
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Logger.h"
#endif
#endif
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Automatic">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="17D102" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES">
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
@@ -12,21 +12,22 @@
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="streamingRemotely" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="163"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="135"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="150"/>
</elements>
</model>
+7 -1
View File
@@ -16,10 +16,16 @@
self.statusMessage = @"App asset has no status message";
self.statusCode = -1;
}
#if TARGET_OS_IPHONE
- (UIImage*) getImage {
UIImage* appImage = [[UIImage alloc] initWithData:self.data];
return appImage;
}
#else
- (NSImage*) getImage {
return nil;
}
#endif
@end
+12 -1
View File
@@ -18,16 +18,27 @@ static const double RETRY_DELAY = 2; // seconds
static const int MAX_ATTEMPTS = 5;
- (void) main {
#if TARGET_OS_IPHONE
UIImage* appImage = nil;
#else
NSImage* appImage = nil;
#endif
int attempts = 0;
while (![self isCancelled] && appImage == nil && attempts++ < MAX_ATTEMPTS) {
HttpManager* hMan = [[HttpManager alloc] initWithHost:_host.activeAddress uniqueId:[IdManager getUniqueId] deviceName:deviceName cert:[CryptoManager readCertFromFile]];
AppAssetResponse* appAssetResp = [[AppAssetResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appAssetResp withUrlRequest:[hMan newAppAssetRequestWithAppId:self.app.id]]];
#if TARGET_OS_IPHONE
appImage = [UIImage imageWithData:appAssetResp.data];
self.app.image = UIImagePNGRepresentation(appImage);
#else
#endif
if (![self isCancelled] && appImage == nil) {
[NSThread sleepForTimeInterval:RETRY_DELAY];
+22
View File
@@ -0,0 +1,22 @@
//
// ConnectionHelper.h
// Moonlight macOS
//
// Created by Felix on 22.03.18.
// Copyright © 2018 Felix. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "AppListResponse.h"
#ifndef ConnectionHelper_h
#define ConnectionHelper_h
@interface ConnectionHelper : NSObject
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP deviceName:(NSString*)deviceName cert:(NSData*) cert uniqueID:(NSString*) uniqueId;
@end
#endif /* ConnectionHelper_h */
+40
View File
@@ -0,0 +1,40 @@
//
// ConnectionHelper.m
// Moonlight macOS
//
// Created by Felix on 22.03.18.
// Copyright © 2018 Felix. All rights reserved.
//
#import "ConnectionHelper.h"
#import "ServerInfoResponse.h"
#import "HttpManager.h"
#import "PairManager.h"
#import "DiscoveryManager.h"
@implementation ConnectionHelper
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP deviceName:(NSString*)deviceName cert:(NSData*) cert uniqueID:(NSString*) uniqueId {
HttpManager* hMan = [[HttpManager alloc] initWithHost:hostIP uniqueId:uniqueId deviceName:deviceName cert:cert];
// Try up to 5 times to get the app list
AppListResponse* appListResp;
for (int i = 0; i < 5; i++) {
appListResp = [[AppListResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appListResp withUrlRequest:[hMan newAppListRequest]]];
if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) {
Log(LOG_W, @"Failed to get applist on try %d: %@", i, appListResp.statusMessage);
// Wait for one second then retry
[NSThread sleepForTimeInterval:1];
}
else {
Log(LOG_I, @"App list successfully retreived - took %d tries", i);
return appListResp;
}
}
return nil;
}
@end
+80 -56
View File
@@ -61,7 +61,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
// A frame was lost due to OOM condition
return DR_NEED_IDR;
}
PLENTRY entry = decodeUnit->bufferList;
while (entry != NULL) {
// Submit parameter set NALUs directly since no copy is required by the decoder
@@ -76,10 +76,10 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
memcpy(&data[offset], entry->data, entry->length);
offset += entry->length;
}
entry = entry->next;
}
// This function will take our picture data buffer
return [renderer submitDecodeBuffer:data length:offset bufferType:BUFFER_TYPE_PICDATA];
}
@@ -87,42 +87,47 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int flags)
{
int err;
// We only support stereo for now
assert(audioConfiguration == AUDIO_CONFIGURATION_STEREO);
opusDecoder = opus_decoder_create(opusConfig->sampleRate,
opusConfig->channelCount,
&err);
audioLock = [[NSLock alloc] init];
#if TARGET_OS_IPHONE
// Configure the audio session for our app
NSError *audioSessionError = nil;
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
[audioSession setPreferredSampleRate:opusConfig->sampleRate error:&audioSessionError];
[audioSession setCategory: AVAudioSessionCategoryPlayback error: &audioSessionError];
[audioSession setPreferredOutputNumberOfChannels:opusConfig->channelCount error:&audioSessionError];
[audioSession setPreferredIOBufferDuration:0.005 error:&audioSessionError];
[audioSession setActive: YES error: &audioSessionError];
#endif
OSStatus status;
AudioComponentDescription audioDesc;
audioDesc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IPHONE
audioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
audioDesc.componentFlags = 0;
audioDesc.componentFlagsMask = 0;
audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
status = AudioComponentInstanceNew(AudioComponentFindNext(NULL, &audioDesc), &audioUnit);
if (status) {
Log(LOG_E, @"Unable to instantiate new AudioComponent: %d", (int32_t)status);
return status;
}
AudioStreamBasicDescription audioFormat = {0};
audioFormat.mSampleRate = opusConfig->sampleRate;
audioFormat.mBitsPerChannel = 16;
@@ -133,7 +138,7 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v
audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame;
audioFormat.mFramesPerPacket = audioFormat.mBytesPerPacket / audioFormat.mBytesPerFrame;
audioFormat.mReserved = 0;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
@@ -144,11 +149,11 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v
Log(LOG_E, @"Unable to set audio unit to input: %d", (int32_t)status);
return status;
}
AURenderCallbackStruct callbackStruct = {0};
callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = NULL;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
@@ -159,19 +164,19 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v
Log(LOG_E, @"Unable to set audio unit callback: %d", (int32_t)status);
return status;
}
status = AudioUnitInitialize(audioUnit);
if (status) {
Log(LOG_E, @"Unable to initialize audioUnit: %d", (int32_t)status);
return status;
}
status = AudioOutputUnitStart(audioUnit);
if (status) {
Log(LOG_E, @"Unable to start audioUnit: %d", (int32_t)status);
return status;
}
return status;
}
@@ -181,21 +186,21 @@ void ArCleanup(void)
opus_decoder_destroy(opusDecoder);
opusDecoder = NULL;
}
OSStatus status = AudioOutputUnitStop(audioUnit);
if (status) {
Log(LOG_E, @"Unable to stop audioUnit: %d", (int32_t)status);
}
status = AudioUnitUninitialize(audioUnit);
if (status) {
Log(LOG_E, @"Unable to uninitialize audioUnit: %d", (int32_t)status);
}
#if TARGET_OS_IPHONE
// Audio session is now inactive
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
[audioSession setActive: YES error: nil];
#endif
// This is safe because we're guaranteed that nobody
// is touching this list now
struct AUDIO_BUFFER_QUEUE_ENTRY *entry;
@@ -213,18 +218,18 @@ void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
if (decodedLength > 0) {
// Return of opus_decode is samples per channel
decodedLength *= 4;
struct AUDIO_BUFFER_QUEUE_ENTRY *newEntry = malloc(sizeof(*newEntry) + decodedLength);
if (newEntry != NULL) {
newEntry->next = NULL;
newEntry->length = decodedLength;
newEntry->offset = 0;
memcpy(newEntry->data, decodedPcmBuffer, decodedLength);
[audioLock lock];
if (audioBufferQueueLength > MAX_QUEUE_ENTRIES) {
Log(LOG_W, @"Audio player too slow. Dropping all decoded samples!");
// Clear all values from the buffer queue
struct AUDIO_BUFFER_QUEUE_ENTRY *entry;
while (audioBufferQueue != NULL) {
@@ -234,7 +239,7 @@ void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
free(entry);
}
}
if (audioBufferQueue == NULL) {
audioBufferQueue = newEntry;
}
@@ -246,7 +251,7 @@ void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
lastEntry->next = newEntry;
}
audioBufferQueueLength++;
[audioLock unlock];
}
}
@@ -310,63 +315,82 @@ void ClLogMessage(const char* format, ...)
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks
{
self = [super init];
// Use a lock to ensure that only one thread is initializing
// or deinitializing a connection at a time.
if (initLock == nil) {
initLock = [[NSLock alloc] init];
}
LiInitializeServerInformation(&_serverInfo);
_serverInfo.address = [config.host cStringUsingEncoding:NSUTF8StringEncoding];
_serverInfo.serverInfoAppVersion = [config.appVersion cStringUsingEncoding:NSUTF8StringEncoding];
if (config.gfeVersion != nil) {
_serverInfo.serverInfoGfeVersion = [config.gfeVersion cStringUsingEncoding:NSUTF8StringEncoding];
}
renderer = myRenderer;
_callbacks = callbacks;
LiInitializeStreamConfiguration(&_streamConfig);
_streamConfig.width = config.width;
_streamConfig.height = config.height;
_streamConfig.fps = config.frameRate;
_streamConfig.bitrate = config.bitRate;
// This will activate the remote streaming optimization in moonlight-common if needed
_streamConfig.streamingRemotely = config.streamingRemotely;
#if TARGET_OS_IPHONE
// On iOS 11, we can use HEVC if the server supports encoding it
// and this device has hardware decode for it (A9 and later)
if (@available(iOS 11.0, *)) {
// FIXME: Disabled due to incompatibility with iPhone X causing video
// to freeze. Additionally, RFI is not supported so packet loss recovery
// is worse with HEVC than H.264.
// Streaming with a limited bandwith will result in better quality with HEVC
//_streamConfig.supportsHevc = VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC);
}
#else
if (@available(macOS 10.13, *)) {
if (VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC) || _streamConfig.streamingRemotely != 0)
_streamConfig.supportsHevc = true;
}
#endif
// Use some of the HEVC encoding efficiency improvements to
// reduce bandwidth usage while still gaining some image
// quality improvement.
_streamConfig.hevcBitratePercentageMultiplier = 75;
// FIXME: We should use 1024 when streaming remotely
_streamConfig.packetSize = 1292;
if (config.streamingRemotely) {
// In the case of remotely streaming, we want the best possible qualtity for a limited bandwidth, so we set the multiplier to 0
_streamConfig.hevcBitratePercentageMultiplier = 0;
// When streaming remotely we want to use a packet size of 1024
_streamConfig.packetSize = 1024;
}
else {
_streamConfig.hevcBitratePercentageMultiplier = 75;
_streamConfig.packetSize = 1292;
}
memcpy(_streamConfig.remoteInputAesKey, [config.riKey bytes], [config.riKey length]);
memset(_streamConfig.remoteInputAesIv, 0, 16);
int riKeyId = htonl(config.riKeyId);
memcpy(_streamConfig.remoteInputAesIv, &riKeyId, sizeof(riKeyId));
LiInitializeVideoCallbacks(&_drCallbacks);
_drCallbacks.setup = DrDecoderSetup;
_drCallbacks.submitDecodeUnit = DrSubmitDecodeUnit;
// RFI doesn't work properly with HEVC on iOS 11 with an iPhone SE (at least)
// It doesnt work on macOS either, tested with Network Link Conditioner.
_drCallbacks.capabilities = CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
LiInitializeAudioCallbacks(&_arCallbacks);
_arCallbacks.init = ArInit;
_arCallbacks.cleanup = ArCleanup;
_arCallbacks.decodeAndPlaySample = ArDecodeAndPlaySample;
LiInitializeConnectionCallbacks(&_clCallbacks);
_clCallbacks.stageStarting = ClStageStarting;
_clCallbacks.stageComplete = ClStageComplete;
@@ -376,7 +400,7 @@ void ClLogMessage(const char* format, ...)
_clCallbacks.displayMessage = ClDisplayMessage;
_clCallbacks.displayTransientMessage = ClDisplayTransientMessage;
_clCallbacks.logMessage = ClLogMessage;
return self;
}
@@ -389,27 +413,27 @@ static OSStatus playbackCallback(void *inRefCon,
// Notes: ioData contains buffers (may be more than one!)
// Fill them up as much as you can. Remember to set the size value in each buffer to match how
// much data is in the buffer.
bool ranOutOfData = false;
for (int i = 0; i < ioData->mNumberBuffers; i++) {
ioData->mBuffers[i].mNumberChannels = 2;
if (ranOutOfData) {
ioData->mBuffers[i].mDataByteSize = 0;
continue;
}
if (ioData->mBuffers[i].mDataByteSize != 0) {
int thisBufferOffset = 0;
FillBufferAgain:
// Make sure there's data to write
if (ioData->mBuffers[i].mDataByteSize - thisBufferOffset == 0) {
continue;
}
struct AUDIO_BUFFER_QUEUE_ENTRY *audioEntry = NULL;
[audioLock lock];
if (audioBufferQueue != NULL) {
// Dequeue this entry temporarily
@@ -418,26 +442,26 @@ static OSStatus playbackCallback(void *inRefCon,
audioBufferQueueLength--;
}
[audioLock unlock];
if (audioEntry == NULL) {
// No data left
ranOutOfData = true;
ioData->mBuffers[i].mDataByteSize = thisBufferOffset;
continue;
}
// Figure out how much data we can write
int min = MIN(ioData->mBuffers[i].mDataByteSize - thisBufferOffset, audioEntry->length);
// Copy data to the audio buffer
memcpy(&ioData->mBuffers[i].mData[thisBufferOffset], &audioEntry->data[audioEntry->offset], min);
thisBufferOffset += min;
if (min < audioEntry->length) {
// This entry still has unused data
audioEntry->length -= min;
audioEntry->offset += min;
// Requeue the entry
[audioLock lock];
audioEntry->next = audioBufferQueue;
@@ -448,7 +472,7 @@ static OSStatus playbackCallback(void *inRefCon,
else {
// This entry is fully depleted so free it
free(audioEntry);
// Try to grab another sample to fill this buffer with
goto FillBufferAgain;
}
@@ -456,7 +480,7 @@ static OSStatus playbackCallback(void *inRefCon,
ioData->mBuffers[i].mDataByteSize = thisBufferOffset;
}
}
return noErr;
}
+1
View File
@@ -19,6 +19,7 @@
@property int frameRate;
@property int bitRate;
@property int riKeyId;
@property int streamingRemotely;
@property NSData* riKey;
@property int gamepadMask;
+1 -1
View File
@@ -9,5 +9,5 @@
#import "StreamConfiguration.h"
@implementation StreamConfiguration
@synthesize host, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask;
@synthesize host, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, streamingRemotely;
@end
+5
View File
@@ -12,7 +12,12 @@
@interface StreamManager : NSOperation
#if TARGET_OS_IPHONE
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callback;
#else
- (id) initWithConfig:(StreamConfiguration*)config renderView:(NSView*)view connectionCallbacks:(id<ConnectionCallbacks>)callback;
#endif
- (void) stopStream;
@end
+25 -4
View File
@@ -10,7 +10,11 @@
#import "CryptoManager.h"
#import "HttpManager.h"
#import "Utils.h"
#if TARGET_OS_IPHONE
#import "OnScreenControls.h"
#endif
#import "StreamView.h"
#import "ServerInfoResponse.h"
#import "HttpResponse.h"
@@ -19,11 +23,17 @@
@implementation StreamManager {
StreamConfiguration* _config;
#if TARGET_OS_IPHONE
UIView* _renderView;
#else
NSView* _renderView;
#endif
id<ConnectionCallbacks> _callbacks;
Connection* _connection;
}
#if TARGET_OS_IPHONE
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callbacks {
self = [super init];
_config = config;
@@ -33,7 +43,17 @@
_config.riKeyId = arc4random();
return self;
}
#else
- (id) initWithConfig:(StreamConfiguration*)config renderView:(NSView*)view connectionCallbacks:(id<ConnectionCallbacks>)callbacks {
self = [super init];
_config = config;
_renderView = view;
_callbacks = callbacks;
_config.riKey = [Utils randomBytes:16];
_config.riKeyId = arc4random();
return self;
}
#endif
- (void)main {
[CryptoManager generateKeyPairUsingSSl];
@@ -42,7 +62,7 @@
HttpManager* hMan = [[HttpManager alloc] initWithHost:_config.host
uniqueId:uniqueId
deviceName:@"roth"
deviceName:deviceName
cert:cert];
ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init];
@@ -75,14 +95,15 @@
return;
}
}
#if TARGET_OS_IPHONE
// Set mouse delta factors from the screen resolution and stream size
CGFloat screenScale = [[UIScreen mainScreen] scale];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGSize screenSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
[((StreamView*)_renderView) setMouseDeltaFactors:_config.width / screenSize.width
y:_config.height / screenSize.height];
#endif
// Populate the config's version fields from serverinfo
_config.appVersion = appversion;
_config.gfeVersion = gfeVersion;
+4 -1
View File
@@ -11,8 +11,11 @@
@import AVFoundation;
@interface VideoDecoderRenderer : NSObject
#if TARGET_OS_IPHONE
- (id)initWithView:(UIView*)view;
#else
- (id)initWithView:(NSView*)view;
#endif
- (void)setupWithVideoFormat:(int)videoFormat;
+41 -1
View File
@@ -11,7 +11,11 @@
#include "Limelight.h"
@implementation VideoDecoderRenderer {
#if TARGET_OS_IPHONE
UIView *_view;
#else
NSView *_view;
#endif
AVSampleBufferDisplayLayer* displayLayer;
Boolean waitingForSps, waitingForPps, waitingForVps;
@@ -27,7 +31,12 @@
displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
displayLayer.bounds = _view.bounds;
#if TARGET_OS_IPHONE
displayLayer.backgroundColor = [UIColor blackColor].CGColor;
#else
displayLayer.backgroundColor = [NSColor blackColor].CGColor;
#endif
displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
displayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
@@ -52,7 +61,7 @@
formatDesc = nil;
}
}
#if TARGET_OS_IPHONE
- (id)initWithView:(UIView*)view
{
self = [super init];
@@ -63,6 +72,20 @@
return self;
}
#else
- (id)initWithView:(NSView*)view
{
self = [super init];
_view = view;
[self reinitializeDisplayLayer];
return self;
}
#endif
- (void)setupWithVideoFormat:(int)videoFormat
{
@@ -222,6 +245,8 @@
const size_t parameterSetSizes[] = { [vpsData length], [spsData length], [ppsData length] };
Log(LOG_I, @"Constructing new HEVC format description");
#if TARGET_OS_IPHONE
if (@available(iOS 11.0, *)) {
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
3, /* count of parameter sets */
@@ -235,6 +260,21 @@
// even though we said we couldn't support it. All we can do is abort().
abort();
}
#else
if (@available(macOS 10.13, *)) {
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
3, /* count of parameter sets */
parameterSetPointers,
parameterSetSizes,
NAL_LENGTH_PREFIX_SIZE,
nil,
&formatDesc);
} else {
// This means Moonlight-common-c decided to give us an HEVC stream
// even though we said we couldn't support it. All we can do is abort().
abort();
}
#endif
if (status != noErr) {
Log(LOG_E, @"Failed to create HEVC format description: %d", (int)status);
@@ -26,6 +26,7 @@
#import "ComputerScrollView.h"
#import "TemporaryApp.h"
#import "IdManager.h"
#import "ConnectionHelper.h"
@implementation MainFrameViewController {
NSOperationQueue* _opQueue;
@@ -115,27 +116,10 @@ static NSMutableSet* hostList;
}
Log(LOG_I, @"Using cached app list: %d", usingCachedAppList);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
HttpManager* hMan = [[HttpManager alloc] initWithHost:host.activeAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert];
// Exempt this host from discovery while handling the applist query
[_discMan removeHostFromDiscovery:host];
// Try up to 5 times to get the app list
AppListResponse* appListResp;
for (int i = 0; i < 5; i++) {
appListResp = [[AppListResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appListResp withUrlRequest:[hMan newAppListRequest]]];
if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) {
Log(LOG_W, @"Failed to get applist on try %d: %@", i, appListResp.statusMessage);
// Wait for one second then retry
[NSThread sleepForTimeInterval:1];
}
else {
Log(LOG_I, @"App list successfully retreived - took %d tries", i);
break;
}
}
AppListResponse* appListResp = [ConnectionHelper getAppListForHostWithHostIP:host.activeAddress deviceName:deviceName cert:_cert uniqueID:_uniqueId];
[_discMan addHostToDiscovery:host];
@@ -409,6 +393,7 @@ static NSMutableSet* hostList;
_streamConfig.height = [streamSettings.height intValue];
_streamConfig.width = [streamSettings.width intValue];
_streamConfig.gamepadMask = [ControllerSupport getConnectedGamepadMask];
_streamConfig.streamingRemotely = [streamSettings.streamingRemotely intValue];
[_appManager stopRetrieving];
@@ -15,6 +15,7 @@
@property (strong, nonatomic) IBOutlet UISegmentedControl *framerateSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *resolutionSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *onscreenControlSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *remoteSelector;
- (void) saveSettings;
@@ -57,7 +57,8 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
resolution = 0;
}
NSInteger onscreenControls = [currentSettings.onscreenControls integerValue];
NSInteger streamingRemotely = [currentSettings.streamingRemotely integerValue];
[self.remoteSelector setSelectedSegmentIndex:streamingRemotely];
[self.resolutionSelector setSelectedSegmentIndex:resolution];
[self.resolutionSelector addTarget:self action:@selector(newResolutionFpsChosen) forControlEvents:UIControlEventValueChanged];
[self.framerateSelector setSelectedSegmentIndex:framerate];
@@ -66,6 +67,11 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
[self.bitrateSlider setValue:(_bitrate / BITRATE_INTERVAL) animated:YES];
[self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged];
[self updateBitrateText];
[self.remoteSelector addTarget:self action:@selector(remoteStreamingChanged) forControlEvents:UIControlEventValueChanged];
}
- (void) remoteStreamingChanged {
// This function can be used to reconfigure the settings view to offer more remote streaming options (i.e. reduce the audio frequency to 24kHz, enable/disable the HEVC bitrate multiplier, ...)
}
- (void) newResolutionFpsChosen {
@@ -102,6 +108,10 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
[self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, _bitrate / 1000.]];
}
- (NSInteger) getRemoteOptions {
return [self.remoteSelector selectedSegmentIndex];
}
- (NSInteger) getChosenFrameRate {
return [self.framerateSelector selectedSegmentIndex] == 0 ? 30 : 60;
}
@@ -120,7 +130,9 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
NSInteger height = [self getChosenStreamHeight];
NSInteger width = [self getChosenStreamWidth];
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
[dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height width:width onscreenControls:onscreenControls];
NSInteger streamingRemotely = [self.remoteSelector selectedSegmentIndex];
[dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height width:width onscreenControls:onscreenControls
remote: streamingRemotely];
}
- (void)didReceiveMemoryWarning {