diff --git a/enet b/enet index dea6fb5..78cc9b4 160000 --- a/enet +++ b/enet @@ -1 +1 @@ -Subproject commit dea6fb5414b180908b58c0293c831105b5d124dd +Subproject commit 78cc9b4b03bdd4f95c277391cac6e0951407072b diff --git a/src/Connection.c b/src/Connection.c index d2e8292..bac9a6e 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -377,6 +377,19 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err); goto Cleanup; } + + // Resolve LocalAddr by RemoteAddr. + { + SOCKADDR_LEN localAddrLen; + err = getLocalAddressByUdpConnect(&RemoteAddr, AddrLen, RtspPortNumber, &LocalAddr, &localAddrLen); + if (err != 0) { + Limelog("failed to resolve local addr: %d\n", err); + ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err); + goto Cleanup; + } + LC_ASSERT(localAddrLen == AddrLen); + } + stage++; LC_ASSERT(stage == STAGE_NAME_RESOLUTION); ListenerCallbacks.stageComplete(STAGE_NAME_RESOLUTION); diff --git a/src/ControlStream.c b/src/ControlStream.c index 4280963..fd8e7a7 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -31,11 +31,12 @@ typedef struct _NVCTL_ENCRYPTED_PACKET_HEADER { // encrypted NVCTL_ENET_PACKET_HEADER_V2 and payload data follow } NVCTL_ENCRYPTED_PACKET_HEADER, *PNVCTL_ENCRYPTED_PACKET_HEADER; -typedef struct _QUEUED_FRAME_INVALIDATION_TUPLE { +typedef struct _QUEUED_REFERENCE_FRAME_CONTROL { uint32_t startFrame; uint32_t endFrame; + bool invalidate; // true: RFI(startFrame, endFrame); false: LTR_ACK(startFrame) LINKED_BLOCKING_QUEUE_ENTRY entry; -} QUEUED_FRAME_INVALIDATION_TUPLE, *PQUEUED_FRAME_INVALIDATION_TUPLE; +} QUEUED_REFERENCE_FRAME_CONTROL, *PQUEUED_REFERENCE_FRAME_CONTROL; typedef struct _QUEUED_FRAME_FEC_STATUS { SS_FRAME_FEC_STATUS fecStatus; @@ -82,6 +83,12 @@ typedef struct _QUEUED_ASYNC_CALLBACK { uint8_t left[DS_EFFECT_PAYLOAD_SIZE]; uint8_t right[DS_EFFECT_PAYLOAD_SIZE]; } dsAdaptiveTrigger; + struct { + uint8_t allowKeyboard; + uint8_t allowMouse; + uint8_t allowGamepad; + uint8_t reason; + } setInputPolicy; } data; LINKED_BLOCKING_QUEUE_ENTRY entry; } QUEUED_ASYNC_CALLBACK, *PQUEUED_ASYNC_CALLBACK; @@ -113,7 +120,7 @@ static int lastConnectionStatusUpdate; static uint32_t currentEnetSequenceNumber; static uint64_t firstFrameTimeMs; -static LINKED_BLOCKING_QUEUE invalidReferenceFrameTuples; +static LINKED_BLOCKING_QUEUE referenceFrameControlQueue; static LINKED_BLOCKING_QUEUE frameFecStatusQueue; static LINKED_BLOCKING_QUEUE asyncCallbackQueue; static PLT_EVENT idrFrameRequiredEvent; @@ -139,6 +146,7 @@ static PPLT_CRYPTO_CONTEXT decryptionCtx; #define IDX_SET_MOTION_EVENT 10 #define IDX_SET_RGB_LED 11 #define IDX_DS_ADAPTIVE_TRIGGERS 12 +#define IDX_SET_INPUT_POLICY 13 #define CONTROL_STREAM_TIMEOUT_SEC 10 #define CONTROL_STREAM_LINGER_TIMEOUT_SEC 2 @@ -156,6 +164,7 @@ static const short packetTypesGen3[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen4[] = { 0x0606, // Request IDR frame @@ -170,6 +179,7 @@ static const short packetTypesGen4[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen5[] = { 0x0305, // Start A @@ -184,6 +194,7 @@ static const short packetTypesGen5[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen7[] = { 0x0305, // Start A @@ -198,6 +209,7 @@ static const short packetTypesGen7[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen7Enc[] = { 0x0302, // Request IDR frame @@ -213,6 +225,7 @@ static const short packetTypesGen7Enc[] = { 0x5501, // Set motion event (Sunshine protocol extension) 0x5502, // Set RGB LED (Sunshine protocol extension) 0x5503, // Set Adaptive Triggers (Sunshine protocol extension) + 0x5504, }; static const char requestIdrFrameGen3[] = { 0, 0 }; @@ -300,7 +313,7 @@ static bool supportsIdrFrameRequest; int initializeControlStream(void) { stopping = false; PltCreateEvent(&idrFrameRequiredEvent); - LbqInitializeLinkedBlockingQueue(&invalidReferenceFrameTuples, 20); + LbqInitializeLinkedBlockingQueue(&referenceFrameControlQueue, 20); LbqInitializeLinkedBlockingQueue(&frameFecStatusQueue, 8); // Limits number of frame status reports per periodic ping interval LbqInitializeLinkedBlockingQueue(&asyncCallbackQueue, 30); PltCreateMutex(&enetMutex); @@ -375,7 +388,7 @@ void destroyControlStream(void) { PltDestroyCryptoContext(encryptionCtx); PltDestroyCryptoContext(decryptionCtx); PltCloseEvent(&idrFrameRequiredEvent); - freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&invalidReferenceFrameTuples)); + freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&referenceFrameControlQueue)); freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&frameFecStatusQueue)); freeBasicLbqList(LbqDestroyLinkedBlockingQueue(&asyncCallbackQueue)); @@ -386,12 +399,15 @@ static void queueFrameInvalidationTuple(uint32_t startFrame, uint32_t endFrame) LC_ASSERT(startFrame <= endFrame); if (isReferenceFrameInvalidationEnabled()) { - PQUEUED_FRAME_INVALIDATION_TUPLE qfit; + PQUEUED_REFERENCE_FRAME_CONTROL qfit; qfit = malloc(sizeof(*qfit)); if (qfit != NULL) { - qfit->startFrame = startFrame; - qfit->endFrame = endFrame; - if (LbqOfferQueueItem(&invalidReferenceFrameTuples, qfit, &qfit->entry) == LBQ_BOUND_EXCEEDED) { + *qfit = (QUEUED_REFERENCE_FRAME_CONTROL){ + .startFrame = startFrame, + .endFrame = endFrame, + .invalidate = true, + }; + if (LbqOfferQueueItem(&referenceFrameControlQueue, qfit, &qfit->entry) == LBQ_BOUND_EXCEEDED) { // Too many invalidation tuples, so we need an IDR frame now Limelog("RFI range list reached maximum size limit\n"); free(qfit); @@ -411,7 +427,7 @@ static void queueFrameInvalidationTuple(uint32_t startFrame, uint32_t endFrame) void LiRequestIdrFrame(void) { // Any reference frame invalidation requests should be dropped now. // We require a full IDR frame to recover. - freeBasicLbqList(LbqFlushQueueItems(&invalidReferenceFrameTuples)); + freeBasicLbqList(LbqFlushQueueItems(&referenceFrameControlQueue)); // Request the IDR frame PltSetEvent(&idrFrameRequiredEvent); @@ -423,9 +439,29 @@ void connectionDetectedFrameLoss(uint32_t startFrame, uint32_t endFrame) { } // When we receive a frame, update the number of our current frame -void connectionReceivedCompleteFrame(uint32_t frameIndex) { +// and send ACK control message if the frame is LTR +void connectionReceivedCompleteFrame(uint32_t frameIndex, bool frameIsLTR) { lastGoodFrame = frameIndex; intervalGoodFrameCount++; + + if (frameIsLTR && IS_SUNSHINE() && isReferenceFrameInvalidationEnabled()) { + // Queue LTR frame ACK control message + PQUEUED_REFERENCE_FRAME_CONTROL qfit; + qfit = malloc(sizeof(*qfit)); + if (qfit != NULL) { + *qfit = (QUEUED_REFERENCE_FRAME_CONTROL){ + .startFrame = frameIndex, + .invalidate = false, + }; + if (LbqOfferQueueItem(&referenceFrameControlQueue, qfit, &qfit->entry) == LBQ_BOUND_EXCEEDED) { + // This shouldn't happen and indicates that something has gone wrong with the queue + LC_ASSERT(false); + Limelog("Couldn't queue LTR ACK because the list has reached maximum size limit\n"); + free(qfit); + LiRequestIdrFrame(); + } + } + } } void connectionSendFrameFecStatus(PSS_FRAME_FEC_STATUS fecStatus) { @@ -986,6 +1022,12 @@ static void asyncCallbackThreadFunc(void* context) { queuedCb->data.dsAdaptiveTrigger.left, queuedCb->data.dsAdaptiveTrigger.right); break; + case IDX_SET_INPUT_POLICY: + ListenerCallbacks.setInputPolicy(queuedCb->data.setInputPolicy.allowKeyboard, + queuedCb->data.setInputPolicy.allowMouse, + queuedCb->data.setInputPolicy.allowGamepad, + queuedCb->data.setInputPolicy.reason); + break; default: // Unhandled packet type from queueAsyncCallback() LC_ASSERT(false); @@ -1002,7 +1044,8 @@ static bool needsAsyncCallback(unsigned short packetType) { packetType == packetTypes[IDX_SET_MOTION_EVENT] || packetType == packetTypes[IDX_SET_RGB_LED] || packetType == packetTypes[IDX_HDR_INFO] || - packetType == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS]; + packetType == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS] || + packetType == packetTypes[IDX_SET_INPUT_POLICY]; } static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLength) { @@ -1063,6 +1106,13 @@ static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLe BbGetBytes(&bb, queuedCb->data.dsAdaptiveTrigger.right, DS_EFFECT_PAYLOAD_SIZE); queuedCb->typeIndex = IDX_DS_ADAPTIVE_TRIGGERS; } + else if (ctlHdr->type == packetTypes[IDX_SET_INPUT_POLICY]) { + BbGet8(&bb, &queuedCb->data.setInputPolicy.allowKeyboard); + BbGet8(&bb, &queuedCb->data.setInputPolicy.allowMouse); + BbGet8(&bb, &queuedCb->data.setInputPolicy.allowGamepad); + BbGet8(&bb, &queuedCb->data.setInputPolicy.reason); + queuedCb->typeIndex = IDX_SET_INPUT_POLICY; + } else { // Unhandled packet type from needsAsyncCallback() LC_ASSERT(false); @@ -1512,22 +1562,22 @@ static void requestIdrFrame(void) { } static void requestInvalidateReferenceFrames(uint32_t startFrame, uint32_t endFrame) { - int64_t payload[3]; - LC_ASSERT(startFrame <= endFrame); LC_ASSERT(isReferenceFrameInvalidationEnabled()); - payload[0] = LE64(startFrame); - payload[1] = LE64(endFrame); - payload[2] = 0; + SS_RFI_REQUEST payload = { + .firstFrameIndex = LE32(startFrame), + .lastFrameIndex = LE32(endFrame), + }; // Send the reference frame invalidation request and read the response if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES], sizeof(payload), - payload, CTRL_CHANNEL_URGENT, + &payload, + CTRL_CHANNEL_URGENT, ENET_PACKET_FLAG_RELIABLE, false)) { - Limelog("Request Invaldiate Reference Frames: Transaction failed: %d\n", (int)LastSocketError()); + Limelog("Request Invalidate Reference Frames: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); return; } @@ -1535,32 +1585,65 @@ static void requestInvalidateReferenceFrames(uint32_t startFrame, uint32_t endFr Limelog("Invalidate reference frame request sent (%d to %d)\n", startFrame, endFrame); } -static void invalidateRefFramesFunc(void* context) { +static void confirmLongtermReferenceFrame(uint32_t frameIndex) { + LC_ASSERT(isReferenceFrameInvalidationEnabled()); + + SS_LTR_FRAME_ACK payload = { + .frameIndex = LE32(frameIndex), + }; + + // Send LTR frame ACK and don't wait for response + if (!sendMessageAndForget(SS_LTR_FRAME_ACK_PTYPE, + sizeof(payload), + &payload, + CTRL_CHANNEL_URGENT, + ENET_PACKET_FLAG_RELIABLE, + false)) { + Limelog("LTR frame ACK: Transaction failed: %d\n", (int)LastSocketError()); + ListenerCallbacks.connectionTerminated(LastSocketFail()); + return; + } +} + +static void referenceFrameControlFunc(void* context) { LC_ASSERT(isReferenceFrameInvalidationEnabled()); while (!PltIsThreadInterrupted(&invalidateRefFramesThread)) { - PQUEUED_FRAME_INVALIDATION_TUPLE qfit; - uint32_t startFrame; - uint32_t endFrame; + PQUEUED_REFERENCE_FRAME_CONTROL qfit; + uint32_t invalidateStartFrame; + uint32_t invalidateEndFrame; + bool invalidate = false; - // Wait for a reference frame invalidation request or a request to shutdown - if (LbqWaitForQueueElement(&invalidReferenceFrameTuples, (void**)&qfit) != LBQ_SUCCESS) { + // Wait for a reference frame control message or a request to shutdown + if (LbqWaitForQueueElement(&referenceFrameControlQueue, (void**)&qfit) != LBQ_SUCCESS) { // Bail if we're stopping return; } - startFrame = qfit->startFrame; - endFrame = qfit->endFrame; - - // Aggregate all lost frames into one range do { - LC_ASSERT(qfit->endFrame >= endFrame); - endFrame = qfit->endFrame; + if (qfit->invalidate) { + if (!invalidate) { + invalidateStartFrame = qfit->startFrame; + invalidateEndFrame = qfit->endFrame; + invalidate = true; + } + else { + // Aggregate all lost frames into one range + LC_ASSERT(qfit->endFrame >= invalidateEndFrame); + invalidateEndFrame = qfit->endFrame; + } + } + else { + // Send LTR frame ACK + confirmLongtermReferenceFrame(qfit->startFrame); + } free(qfit); - } while (LbqPollQueueElement(&invalidReferenceFrameTuples, (void**)&qfit) == LBQ_SUCCESS); + } while (LbqPollQueueElement(&referenceFrameControlQueue, (void**)&qfit) == LBQ_SUCCESS); - // Send the reference frame invalidation request - requestInvalidateReferenceFrames(startFrame, endFrame); + if (invalidate) { + // Send the reference frame invalidation request + requestInvalidateReferenceFrames(invalidateStartFrame, invalidateEndFrame); + } } } @@ -1574,8 +1657,8 @@ static void requestIdrFrameFunc(void* context) { return; } - // Any pending reference frame invalidation requests are now redundant - freeBasicLbqList(LbqFlushQueueItems(&invalidReferenceFrameTuples)); + // Any pending RFI requests and LTR frame ACK messages are now redundant + freeBasicLbqList(LbqFlushQueueItems(&referenceFrameControlQueue)); // Request the IDR frame requestIdrFrame(); @@ -1585,7 +1668,7 @@ static void requestIdrFrameFunc(void* context) { // Stops the control stream int stopControlStream(void) { stopping = true; - LbqSignalQueueShutdown(&invalidReferenceFrameTuples); + LbqSignalQueueShutdown(&referenceFrameControlQueue); LbqSignalQueueShutdown(&frameFecStatusQueue); LbqSignalQueueDrain(&asyncCallbackQueue); PltSetEvent(&idrFrameRequiredEvent); @@ -1644,6 +1727,34 @@ int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t chan return 0; } +int LiSendSessionInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason) { + struct { + uint8_t allowKeyboard; + uint8_t allowMouse; + uint8_t allowGamepad; + uint8_t reason; + } payload; + + if (!IS_SUNSHINE() || AppVersionQuad[0] < 7) { + return LI_ERR_UNSUPPORTED; + } + + if (client == NULL || peer == NULL || stopping) { + return -2; + } + + payload.allowKeyboard = allowKeyboard ? 1 : 0; + payload.allowMouse = allowMouse ? 1 : 0; + payload.allowGamepad = allowGamepad ? 1 : 0; + payload.reason = reason; + + if (sendMessageAndForget(0x5504, sizeof(payload), &payload, CTRL_CHANNEL_GENERIC, ENET_PACKET_FLAG_RELIABLE, false) == 0) { + return -1; + } + + return 0; +} + // Called by the input stream to flush queued packets before a batching wait void flushInputOnControlStream(void) { if (AppVersionQuad[0] >= 5) { @@ -1670,7 +1781,10 @@ bool isControlDataInTransit(void) { bool LiGetEstimatedRttInfo(uint32_t* estimatedRtt, uint32_t* estimatedRttVariance) { bool ret = false; - PltLockMutex(&enetMutex); + // We do not acquire enetMutex here because we're just reading metrics + // and observing a torn write every once in a while is totally fine. + // The peer pointer points to memory reserved inside the client object, + // so it's guaranteed that it will never go away underneath us. if (peer != NULL && peer->state == ENET_PEER_STATE_CONNECTED) { if (estimatedRtt != NULL) { *estimatedRtt = peer->roundTripTime; @@ -1682,7 +1796,6 @@ bool LiGetEstimatedRttInfo(uint32_t* estimatedRtt, uint32_t* estimatedRttVarianc ret = true; } - PltUnlockMutex(&enetMutex); return ret; } @@ -1970,7 +2083,7 @@ int startControlStream(void) { // Only create the reference frame invalidation thread if RFI is enabled if (isReferenceFrameInvalidationEnabled()) { - err = PltCreateThread("InvRefFrames", invalidateRefFramesFunc, NULL, &invalidateRefFramesThread); + err = PltCreateThread("InvRefFrames", referenceFrameControlFunc, NULL, &invalidateRefFramesThread); if (err != 0) { stopping = true; PltSetEvent(&idrFrameRequiredEvent); diff --git a/src/FakeCallbacks.c b/src/FakeCallbacks.c index 29fdf18..50095a0 100644 --- a/src/FakeCallbacks.c +++ b/src/FakeCallbacks.c @@ -41,6 +41,7 @@ static void fakeClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTrigger static void fakeClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz) {} static void fakeClSetAdaptiveTriggers(uint16_t controllerNumber, uint8_t eventFlags, uint8_t typeLeft, uint8_t typeRight, uint8_t *left, uint8_t *right) {}; static void fakeClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b) {} +static void fakeClSetInputPolicy(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason) {} static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .stageStarting = fakeClStageStarting, @@ -56,6 +57,7 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .setMotionEventState = fakeClSetMotionEventState, .setControllerLED = fakeClSetControllerLED, .setAdaptiveTriggers = fakeClSetAdaptiveTriggers, + .setInputPolicy = fakeClSetInputPolicy, }; void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks, @@ -146,5 +148,8 @@ void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_REND if ((*clCallbacks)->setAdaptiveTriggers == NULL) { (*clCallbacks)->setAdaptiveTriggers = fakeClSetAdaptiveTriggers; } + if ((*clCallbacks)->setInputPolicy == NULL) { + (*clCallbacks)->setInputPolicy = fakeClSetInputPolicy; + } } } diff --git a/src/InputStream.c b/src/InputStream.c index 06913ff..e8e60a4 100644 --- a/src/InputStream.c +++ b/src/InputStream.c @@ -724,9 +724,14 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { // Queue a packet holder if this is the only pending relative mouse event if (!currentRelativeMouseState.dirty) { + // Set the dirty flag to claim ownership of inserting the packet holder + // and unlock to allow other threads to enqueue or process input. + currentRelativeMouseState.dirty = true; + PltUnlockMutex(&batchedInputMutex); + holder = allocatePacketHolder(0); if (holder == NULL) { - PltUnlockMutex(&batchedInputMutex); + currentRelativeMouseState.dirty = false; return -1; } @@ -747,22 +752,21 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { // Remaining fields are set in the input thread based on the latest currentRelativeMouseState values err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); - if (err == LBQ_SUCCESS) { - currentRelativeMouseState.dirty = true; - } - else { + if (err != LBQ_SUCCESS) { LC_ASSERT(err == LBQ_BOUND_EXCEEDED); Limelog("Input queue reached maximum size limit\n"); freePacketHolder(holder); + + // We weren't able to insert the entry, so let the next call try again + currentRelativeMouseState.dirty = false; } } else { // There's already a packet holder queued to send this event + PltUnlockMutex(&batchedInputMutex); err = 0; } - PltUnlockMutex(&batchedInputMutex); - return err; } @@ -785,9 +789,14 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer // Queue a packet holder if this is the only pending absolute mouse event if (!currentAbsoluteMouseState.dirty) { + // Set the dirty flag to claim ownership of inserting the packet holder + // and unlock to allow other threads to enqueue or process input. + currentAbsoluteMouseState.dirty = true; + PltUnlockMutex(&batchedInputMutex); + holder = allocatePacketHolder(0); if (holder == NULL) { - PltUnlockMutex(&batchedInputMutex); + currentAbsoluteMouseState.dirty = false; return -1; } @@ -803,22 +812,21 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer // Remaining fields are set in the input thread based on the latest currentAbsoluteMouseState values err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); - if (err == LBQ_SUCCESS) { - currentAbsoluteMouseState.dirty = true; - } - else { + if (err != LBQ_SUCCESS) { LC_ASSERT(err == LBQ_BOUND_EXCEEDED); Limelog("Input queue reached maximum size limit\n"); freePacketHolder(holder); + + // We weren't able to insert the entry, so let the next call try again + currentAbsoluteMouseState.dirty = false; } } else { // There's already a packet holder queued to send this event + PltUnlockMutex(&batchedInputMutex); err = 0; } - PltUnlockMutex(&batchedInputMutex); - // This is not thread safe, but it's not a big deal because callers that want to // use LiSendRelativeMotionAsMousePositionEvent() must not mix these function // without synchronization (otherwise the state of the cursor on the host is @@ -1536,9 +1544,14 @@ int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, fl // Queue a packet holder if this is the only pending sensor event if (!currentGamepadSensorState[controllerNumber][motionType - 1].dirty) { + // Set the dirty flag to claim ownership of inserting the packet holder + // and unlock to allow other threads to enqueue or process input. + currentGamepadSensorState[controllerNumber][motionType - 1].dirty = true; + PltUnlockMutex(&batchedInputMutex); + holder = allocatePacketHolder(0); if (holder == NULL) { - PltUnlockMutex(&batchedInputMutex); + currentGamepadSensorState[controllerNumber][motionType - 1].dirty = false; return -1; } @@ -1554,22 +1567,21 @@ int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, fl // Remaining fields are set in the input thread based on the latest currentGamepadSensorState values err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); - if (err == LBQ_SUCCESS) { - currentGamepadSensorState[controllerNumber][motionType - 1].dirty = true; - } - else { + if (err != LBQ_SUCCESS) { LC_ASSERT(err == LBQ_BOUND_EXCEEDED); Limelog("Input queue reached maximum size limit\n"); freePacketHolder(holder); + + // We weren't able to insert the entry, so let the next call try again + currentGamepadSensorState[controllerNumber][motionType - 1].dirty = false; } } else { // There's already a packet holder queued to send this event + PltUnlockMutex(&batchedInputMutex); err = 0; } - PltUnlockMutex(&batchedInputMutex); - return err; } diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h index dfd4758..e1314c5 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -55,7 +55,7 @@ extern uint32_t EncryptionFeaturesEnabled; // ENet channel ID values #define CTRL_CHANNEL_GENERIC 0x00 -#define CTRL_CHANNEL_URGENT 0x01 // IDR and reference frame invalidation requests +#define CTRL_CHANNEL_URGENT 0x01 // IDR, LTR ACK and RFI #define CTRL_CHANNEL_KEYBOARD 0x02 #define CTRL_CHANNEL_MOUSE 0x03 #define CTRL_CHANNEL_PEN 0x04 @@ -119,7 +119,7 @@ int startControlStream(void); int stopControlStream(void); void destroyControlStream(void); void connectionDetectedFrameLoss(uint32_t startFrame, uint32_t endFrame); -void connectionReceivedCompleteFrame(uint32_t frameIndex); +void connectionReceivedCompleteFrame(uint32_t frameIndex, bool frameIsLTR); void connectionSawFrame(uint32_t frameIndex); void connectionSendFrameFecStatus(PSS_FRAME_FEC_STATUS fecStatus); int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t channelId, uint32_t flags, bool moreData); diff --git a/src/Limelight.h b/src/Limelight.h index ac87a4b..ba4e5c5 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -483,6 +483,8 @@ typedef void(*ConnListenerSetAdaptiveTriggers)(uint16_t controllerNumber, uint8_ // This callback is invoked to set a controller's RGB LED (if present). typedef void(*ConnListenerSetControllerLED)(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b); +typedef void(*ConnListenerSetInputPolicy)(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason); + typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerStageStarting stageStarting; ConnListenerStageComplete stageComplete; @@ -497,6 +499,7 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerSetMotionEventState setMotionEventState; ConnListenerSetControllerLED setControllerLED; ConnListenerSetAdaptiveTriggers setAdaptiveTriggers; + ConnListenerSetInputPolicy setInputPolicy; } CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS; // Use this function to zero the connection callbacks when allocated on the stack or heap @@ -837,6 +840,12 @@ int LiSendHighResScrollEvent(short scrollAmount); int LiSendHScrollEvent(signed char scrollClicks); int LiSendHighResHScrollEvent(short scrollAmount); +#define LI_SESSION_INPUT_POLICY_REASON_STREAM_START 0x00 +#define LI_SESSION_INPUT_POLICY_REASON_USER_TOGGLE 0x01 +#define LI_SESSION_INPUT_POLICY_REASON_HOST_ACK 0x02 +#define LI_SESSION_INPUT_POLICY_REASON_HOST_OVERRIDE 0x03 +int LiSendSessionInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason); + // This function returns a time in microseconds with an implementation-defined epoch. // It should only ever be compared with the return value from a previous call to itself. uint64_t LiGetMicroseconds(void); diff --git a/src/Platform.c b/src/Platform.c index 30ae48b..d68e692 100644 --- a/src/Platform.c +++ b/src/Platform.c @@ -86,9 +86,7 @@ void* ThreadProc(void* context) { ctx->entry(ctx->context); -#if defined(__vita__) -free(ctx); -#endif + free(ctx); #if defined(LC_WINDOWS) || defined(__vita__) || defined(__WIIU__) || defined(__3DS__) return 0; @@ -288,11 +286,14 @@ int PltCreateThread(const char* name, ThreadEntry entry, void* context, PLT_THRE pthread_attr_init(&attr); +#ifdef __vita__ pthread_attr_setstacksize(&attr, 0x100000); +#endif ctx->name = name; int err = pthread_create(&thread->thread, &attr, ThreadProc, ctx); + pthread_attr_destroy(&attr); if (err != 0) { free(ctx); return err; diff --git a/src/PlatformSockets.c b/src/PlatformSockets.c index 56f9b9c..41e7dd8 100644 --- a/src/PlatformSockets.c +++ b/src/PlatformSockets.c @@ -568,6 +568,40 @@ Exit: return s; } +int getLocalAddressByUdpConnect(const struct sockaddr_storage* targetAddr, SOCKADDR_LEN targetAddrLen, unsigned short targetPort, + struct sockaddr_storage* localAddr, SOCKADDR_LEN* localAddrLen) { + SOCKET udpSocket; + LC_SOCKADDR connAddr; + + LC_ASSERT(targetPort != 0); + + udpSocket = createSocket(targetAddr->ss_family, SOCK_DGRAM, IPPROTO_UDP, false); + if (udpSocket == INVALID_SOCKET) { + return LastSocketError(); + } + + memcpy(&connAddr, targetAddr, targetAddrLen); + SET_PORT(&connAddr, RtspPortNumber); + + if (connect(udpSocket, (struct sockaddr*)&connAddr, targetAddrLen) < 0) { + int err = LastSocketError(); + Limelog("UDP connect() failed: %d\n", err); + closeSocket(udpSocket); + return err; + } + + *localAddrLen = sizeof(*localAddr); + if (getsockname(udpSocket, (struct sockaddr*)localAddr, localAddrLen) < 0) { + int err = LastSocketError(); + Limelog("getsockname() failed: %d\n", err); + closeSocket(udpSocket); + return err; + } + + closeSocket(udpSocket); + return 0; +} + // See TCP_MAXSEG note in connectTcpSocket() above for more information. // TCP_NODELAY must be enabled on the socket for this function to work! int sendMtuSafe(SOCKET s, char* buffer, int size) { diff --git a/src/PlatformSockets.h b/src/PlatformSockets.h index 78ae4be..db47d83 100644 --- a/src/PlatformSockets.h +++ b/src/PlatformSockets.h @@ -107,6 +107,8 @@ void addrToUrlSafeString(struct sockaddr_storage* addr, char* string, size_t str SOCKET createSocket(int addressFamily, int socketType, int protocol, bool nonBlocking); SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen, unsigned short port, int timeoutSec); +int getLocalAddressByUdpConnect(const struct sockaddr_storage* targetAddr, SOCKADDR_LEN targetAddrLen, unsigned short targetPort, + struct sockaddr_storage* localAddr, SOCKADDR_LEN* localAddrLen); int sendMtuSafe(SOCKET s, char* buffer, int size); SOCKET bindUdpSocket(int addressFamily, struct sockaddr_storage* localAddr, SOCKADDR_LEN addrLen, int bufferSize, int socketQosType); int enableNoDelay(SOCKET s); diff --git a/src/RtpVideoQueue.c b/src/RtpVideoQueue.c index c34d2a1..dfbed48 100644 --- a/src/RtpVideoQueue.c +++ b/src/RtpVideoQueue.c @@ -382,9 +382,9 @@ cleanup_packets: // Check all NV_VIDEO_PACKET fields except FEC stuff which differs in the recovered packet LC_ASSERT_VT(nvPacket->flags == droppedNvPacket->flags); + LC_ASSERT_VT(nvPacket->extraFlags == droppedNvPacket->extraFlags); LC_ASSERT_VT(nvPacket->frameIndex == droppedNvPacket->frameIndex); LC_ASSERT_VT(nvPacket->streamPacketIndex == droppedNvPacket->streamPacketIndex); - LC_ASSERT_VT(nvPacket->reserved == droppedNvPacket->reserved); LC_ASSERT_VT(!queue->multiFecCapable || nvPacket->multiFecBlocks == droppedNvPacket->multiFecBlocks); // Check the data itself - use memcmp() and only loop if an error is detected diff --git a/src/RtspConnection.c b/src/RtspConnection.c index d8f1b35..52ce2b1 100644 --- a/src/RtspConnection.c +++ b/src/RtspConnection.c @@ -477,18 +477,6 @@ static bool transactRtspMessageTcp(PRTSP_MESSAGE request, PRTSP_MESSAGE response // Decrypt (if necessary) and deserialize the RTSP response ret = unsealRtspMessage(responseBuffer, offset, response); - // Fetch the local address for this socket if it's not populated yet - if (LocalAddr.ss_family == 0) { - SOCKADDR_LEN addrLen = (SOCKADDR_LEN)sizeof(LocalAddr); - if (getsockname(sock, (struct sockaddr*)&LocalAddr, &addrLen) < 0) { - Limelog("Failed to get local address: %d\n", LastSocketError()); - memset(&LocalAddr, 0, sizeof(LocalAddr)); - } - else { - LC_ASSERT(addrLen == AddrLen); - } - } - Exit: if (serializedMessage != NULL) { free(serializedMessage); diff --git a/src/Video.h b/src/Video.h index 3df2eae..737d23c 100644 --- a/src/Video.h +++ b/src/Video.h @@ -22,11 +22,13 @@ typedef struct _ENC_VIDEO_HEADER { #define FLAG_EOF 0x2 #define FLAG_SOF 0x4 +#define NV_VIDEO_PACKET_EXTRA_FLAG_LTR_FRAME 0x1 + typedef struct _NV_VIDEO_PACKET { uint32_t streamPacketIndex; uint32_t frameIndex; uint8_t flags; - uint8_t reserved; + uint8_t extraFlags; uint8_t multiFecFlags; uint8_t multiFecBlocks; uint32_t fecInfo; @@ -67,4 +69,20 @@ typedef struct _SS_FRAME_FEC_STATUS { uint8_t multiFecBlockCount; } SS_FRAME_FEC_STATUS, *PSS_FRAME_FEC_STATUS; +// Fields are little-endian +#define SS_LTR_FRAME_ACK_PTYPE 0x0350 +typedef struct _SS_LTR_FRAME_ACK { + uint32_t frameIndex; + uint32_t reserved; +} SS_LTR_FRAME_ACK, *PSS_LTR_FRAME_ACK; + +// Fields are little-endian +#define SS_RFI_REQUEST_PTYPE 0x0301 +typedef struct _SS_RFI_REQUEST { + uint32_t firstFrameIndex; + uint32_t reserved1; + uint32_t lastFrameIndex; + uint32_t reserved2[3]; +} SS_RFI_REQUEST, *PSS_RFI_REQUEST; + #pragma pack(pop) diff --git a/src/VideoDepacketizer.c b/src/VideoDepacketizer.c index 364dcec..43f2df9 100644 --- a/src/VideoDepacketizer.c +++ b/src/VideoDepacketizer.c @@ -466,7 +466,7 @@ static bool isIdrFrameStart(PBUFFER_DESC buffer) { } // Reassemble the frame with the given frame number -static void reassembleFrame(int frameNumber) { +static void reassembleFrame(int frameNumber, bool frameIsLTR) { if (nalChainHead != NULL) { QUEUED_DECODE_UNIT qduDS; PQUEUED_DECODE_UNIT qdu; @@ -539,7 +539,7 @@ static void reassembleFrame(int frameNumber) { } // Notify the control connection - connectionReceivedCompleteFrame(frameNumber); + connectionReceivedCompleteFrame(frameNumber, frameIsLTR); // Clear frame drops consecutiveFrameDrops = 0; @@ -748,6 +748,7 @@ static void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length, BUFFER_DESC currentPos; uint32_t frameIndex; uint8_t flags; + uint8_t extraFlags; bool firstPacket, lastPacket; uint32_t streamPacketIndex; uint8_t fecCurrentBlockNumber; @@ -765,6 +766,7 @@ static void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length, fecLastBlockNumber = (videoPacket->multiFecBlocks >> 6) & 0x3; frameIndex = videoPacket->frameIndex; flags = videoPacket->flags; + extraFlags = videoPacket->extraFlags; firstPacket = isFirstPacket(flags, fecCurrentBlockNumber); lastPacket = (flags & FLAG_EOF) && fecCurrentBlockNumber == fecLastBlockNumber; @@ -1119,7 +1121,7 @@ static void processRtpPayload(PNV_VIDEO_PACKET videoPacket, int length, } } - reassembleFrame(frameIndex); + reassembleFrame(frameIndex, extraFlags & NV_VIDEO_PACKET_EXTRA_FLAG_LTR_FRAME); } }