diff --git a/Limelight/Network/HttpManager.h b/Limelight/Network/HttpManager.h index 6eb9ddb..873db76 100644 --- a/Limelight/Network/HttpManager.h +++ b/Limelight/Network/HttpManager.h @@ -8,6 +8,7 @@ #import "HttpResponse.h" #import "HttpRequest.h" +#import "StreamConfiguration.h" @interface HttpManager : NSObject @@ -21,8 +22,8 @@ - (NSURLRequest*) newAppListRequest; - (NSURLRequest*) newServerInfoRequest; - (NSURLRequest*) newHttpServerInfoRequest; -- (NSURLRequest*) newLaunchRequest:(NSString*)appId width:(int)width height:(int)height refreshRate:(int)refreshRate rikey:(NSString*)rikey rikeyid:(int)rikeyid gamepadMask:(int)gamepadMask; -- (NSURLRequest*) newResumeRequestWithRiKey:(NSString*)riKey riKeyId:(int)riKeyId; +- (NSURLRequest*) newLaunchRequest:(StreamConfiguration*)config; +- (NSURLRequest*) newResumeRequest:(StreamConfiguration*)config; - (NSURLRequest*) newQuitAppRequest; - (NSURLRequest*) newAppAssetRequestWithAppId:(NSString*)appId; - (void) executeRequestSynchronously:(HttpRequest*)request; diff --git a/Limelight/Network/HttpManager.m b/Limelight/Network/HttpManager.m index 30ba876..4f50243 100644 --- a/Limelight/Network/HttpManager.m +++ b/Limelight/Network/HttpManager.m @@ -167,14 +167,26 @@ static const NSString* HTTPS_PORT = @"47984"; return [self createRequestFromString:urlString enableTimeout:TRUE]; } -- (NSURLRequest*) newLaunchRequest:(NSString*)appId width:(int)width height:(int)height refreshRate:(int)refreshRate rikey:(NSString*)rikey rikeyid:(int)rikeyid gamepadMask:(int)gamepadMask { - NSString* urlString = [NSString stringWithFormat:@"%@/launch?uniqueid=%@&appid=%@&mode=%dx%dx%d&additionalStates=1&sops=1&rikey=%@&rikeyid=%d&remoteControllersBitmap=%d&gcmap=%d", _baseHTTPSURL, _uniqueId, appId, width, height, refreshRate, rikey, rikeyid, gamepadMask, gamepadMask]; +- (NSURLRequest*) newLaunchRequest:(StreamConfiguration*)config { + NSString* urlString = [NSString stringWithFormat:@"%@/launch?uniqueid=%@&appid=%@&mode=%dx%dx%d&additionalStates=1&sops=%d&rikey=%@&rikeyid=%d%@&localAudioPlayMode=%d&surroundAudioInfo=%d&remoteControllersBitmap=%d&gcmap=%d", + _baseHTTPSURL, _uniqueId, + config.appID, + config.width, config.height, config.frameRate, + config.optimizeGameSettings ? 1 : 0, + [Utils bytesToHex:config.riKey], config.riKeyId, + config.enableHdr ? @"&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0": @"", + config.playAudioOnPC ? 1 : 0, + (config.audioChannelMask << 16) | config.audioChannelCount, + config.gamepadMask, config.gamepadMask]; // This blocks while the app is launching return [self createRequestFromString:urlString enableTimeout:FALSE]; } -- (NSURLRequest*) newResumeRequestWithRiKey:(NSString*)riKey riKeyId:(int)riKeyId { - NSString* urlString = [NSString stringWithFormat:@"%@/resume?uniqueid=%@&rikey=%@&rikeyid=%d", _baseHTTPSURL, _uniqueId, riKey, riKeyId]; +- (NSURLRequest*) newResumeRequest:(StreamConfiguration*)config { + NSString* urlString = [NSString stringWithFormat:@"%@/resume?uniqueid=%@&rikey=%@&rikeyid=%d&surroundAudioInfo=%d", + _baseHTTPSURL, _uniqueId, + [Utils bytesToHex:config.riKey], config.riKeyId, + (config.audioChannelMask << 16) | config.audioChannelCount]; // This blocks while the app is resuming return [self createRequestFromString:urlString enableTimeout:FALSE]; } diff --git a/Limelight/Stream/Connection.m b/Limelight/Stream/Connection.m index 1717fc4..d950462 100644 --- a/Limelight/Stream/Connection.m +++ b/Limelight/Stream/Connection.m @@ -90,7 +90,9 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v // Clear the circular buffer audioBufferWriteIndex = audioBufferReadIndex = 0; - // We only support stereo for now + // We only support stereo for now. + // TODO: Ensure AudioToolbox's channel mapping matches Opus's + // and correct if neccessary. assert(audioConfiguration == AUDIO_CONFIGURATION_STEREO); activeChannelCount = opusConfig->channelCount; @@ -281,9 +283,20 @@ void ClLogMessage(const char* format, ...) _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; + _streamConfig.enableHdr = config.enableHdr; + + switch (config.audioChannelCount) { + case 2: + _streamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO; + break; + case 6: + _streamConfig.audioConfiguration = AUDIO_CONFIGURATION_51_SURROUND; + break; + default: + Log(LOG_E, @"Unknown audio channel count: %d", config.audioChannelCount); + abort(); + } #if TARGET_OS_IPHONE // On iOS 11, we can use HEVC if the server supports encoding it @@ -302,6 +315,9 @@ void ClLogMessage(const char* format, ...) } #endif + // HEVC must be supported when HDR is enabled + assert(!_streamConfig.enableHdr || _streamConfig.supportsHevc); + // Use some of the HEVC encoding efficiency improvements to // reduce bandwidth usage while still gaining some image // quality improvement. diff --git a/Limelight/Stream/StreamConfiguration.h b/Limelight/Stream/StreamConfiguration.h index 787778a..3954980 100644 --- a/Limelight/Stream/StreamConfiguration.h +++ b/Limelight/Stream/StreamConfiguration.h @@ -21,5 +21,10 @@ @property int streamingRemotely; @property NSData* riKey; @property int gamepadMask; +@property BOOL optimizeGameSettings; +@property BOOL playAudioOnPC; +@property int audioChannelCount; +@property int audioChannelMask; +@property BOOL enableHdr; @end diff --git a/Limelight/Stream/StreamConfiguration.m b/Limelight/Stream/StreamConfiguration.m index 19fb3e2..4386f61 100644 --- a/Limelight/Stream/StreamConfiguration.m +++ b/Limelight/Stream/StreamConfiguration.m @@ -9,5 +9,5 @@ #import "StreamConfiguration.h" @implementation StreamConfiguration -@synthesize host, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, streamingRemotely, appName; +@synthesize host, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, streamingRemotely, appName, optimizeGameSettings, playAudioOnPC, audioChannelMask, audioChannelCount, enableHdr; @end diff --git a/Limelight/Stream/StreamManager.m b/Limelight/Stream/StreamManager.m index 06967b6..4c4d14e 100644 --- a/Limelight/Stream/StreamManager.m +++ b/Limelight/Stream/StreamManager.m @@ -104,14 +104,7 @@ - (BOOL) launchApp:(HttpManager*)hMan { HttpResponse* launchResp = [[HttpResponse alloc] init]; - [hMan executeRequestSynchronously:[HttpRequest requestForResponse:launchResp withUrlRequest: - [hMan newLaunchRequest:_config.appID - width:_config.width - height:_config.height - refreshRate:_config.frameRate - rikey:[Utils bytesToHex:_config.riKey] - rikeyid:_config.riKeyId - gamepadMask:_config.gamepadMask]]]; + [hMan executeRequestSynchronously:[HttpRequest requestForResponse:launchResp withUrlRequest:[hMan newLaunchRequest:_config]]]; NSString *gameSession = [launchResp getStringTag:@"gamesession"]; if (launchResp == NULL || ![launchResp isStatusOk]) { [_callbacks launchFailed:@"Failed to launch app"]; @@ -128,9 +121,7 @@ - (BOOL) resumeApp:(HttpManager*)hMan { HttpResponse* resumeResp = [[HttpResponse alloc] init]; - [hMan executeRequestSynchronously:[HttpRequest requestForResponse:resumeResp withUrlRequest: - [hMan newResumeRequestWithRiKey:[Utils bytesToHex:_config.riKey] - riKeyId:_config.riKeyId]]]; + [hMan executeRequestSynchronously:[HttpRequest requestForResponse:resumeResp withUrlRequest:[hMan newResumeRequest:_config]]]; NSString* resume = [resumeResp getStringTag:@"resume"]; if (resumeResp == NULL || ![resumeResp isStatusOk]) { [_callbacks launchFailed:@"Failed to resume app"]; diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index 4874502..9b779bf 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -28,6 +28,8 @@ #import "IdManager.h" #import "ConnectionHelper.h" +#import + @implementation MainFrameViewController { NSOperationQueue* _opQueue; TemporaryHost* _selectedHost; @@ -432,6 +434,27 @@ static NSMutableSet* hostList; _streamConfig.width = [streamSettings.width intValue]; _streamConfig.gamepadMask = [ControllerSupport getConnectedGamepadMask]; _streamConfig.streamingRemotely = [streamSettings.streamingRemotely intValue]; + _streamConfig.optimizeGameSettings = YES; + _streamConfig.playAudioOnPC = NO; + + // TODO: Detect attached surround sound system then address 5.1 TODOs + // in Connection.m + _streamConfig.audioChannelCount = 2; + _streamConfig.audioChannelMask = 0x3; + + // HDR requires HDR10 game, HDR10 display, and HEVC Main10 decoder on the client. + // It additionally requires an HEVC Main10 encoder on the server (GTX 1000+). + // + // It should also be a user preference when supported, since some games may require + // higher peak brightness than the iOS device can support to look correct in HDR mode. + if (@available(iOS 11.3, *)) { + _streamConfig.enableHdr = + app.hdrSupported && // App supported + (app.host.serverCodecModeSupport & 0x200) != 0 && // HEVC Main10 encoding on host PC GPU + VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC) && // Decoder supported + (AVPlayer.availableHDRModes & AVPlayerHDRModeHDR10) != 0 && // Display supported + NO; // TODO: User wants it enabled + } [_appManager stopRetrieving];