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

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;
}

View File

@@ -19,6 +19,7 @@
@property int frameRate;
@property int bitRate;
@property int riKeyId;
@property int streamingRemotely;
@property NSData* riKey;
@property int gamepadMask;

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

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

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;

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;

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);