Split control data into multiple channels and optimize packet flags based on type of data

This can significantly performance on lossy networks by avoiding HOL blocking.
This commit is contained in:
Cameron Gutman
2023-07-04 14:49:21 -05:00
parent 7d59c6e14e
commit 0095141e08
3 changed files with 174 additions and 27 deletions

View File

@@ -559,12 +559,17 @@ static bool isPacketSentWaitingForAck(ENetPacket* packet) {
return false;
}
static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool reliable) {
static bool sendMessageEnet(short ptype, short paylen, const void* payload, uint8_t channelId, uint32_t flags) {
ENetPacket* enetPacket;
int err;
LC_ASSERT(AppVersionQuad[0] >= 5);
// Only send reliable packets to GFE
if (!IS_SUNSHINE()) {
flags = ENET_PACKET_FLAG_RELIABLE;
}
if (encryptedControlStream) {
PNVCTL_ENCRYPTED_PACKET_HEADER encPacket;
PNVCTL_ENET_PACKET_HEADER_V2 packet;
@@ -572,7 +577,7 @@ static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool
enetPacket = enet_packet_create(NULL,
sizeof(*encPacket) + AES_GCM_TAG_LENGTH + sizeof(*packet) + paylen,
reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED);
flags);
if (enetPacket == NULL) {
return false;
}
@@ -606,7 +611,7 @@ static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool
else {
PNVCTL_ENET_PACKET_HEADER_V1 packet;
enetPacket = enet_packet_create(NULL, sizeof(*packet) + paylen,
reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED);
flags);
if (enetPacket == NULL) {
return false;
}
@@ -625,11 +630,24 @@ static bool sendMessageEnet(short ptype, short paylen, const void* payload, bool
enetPacket->userData = (void*)&packetFreed;
enetPacket->freeCallback = enetPacketFreeCb;
// channelCount == 0 is possible if the peer is disconnected,
// so we need to assign channel ID under the enetMutex to prevent
// racing updates the peer.
if (!IS_SUNSHINE() || peer->channelCount == 0) {
// We always use a single channel for GFE.
channelId = 0;
}
else if (channelId >= peer->channelCount) {
// If this peer doesn't support enough channels, distribute the remaining channels onto the ones
// the peer does support. We don't use the channel to distinguish traffic types, so this is safe.
channelId %= peer->channelCount;
}
// Queue the packet to be sent
err = enet_peer_send(peer, 0, enetPacket);
err = enet_peer_send(peer, channelId, enetPacket);
// Wait until the packet is actually sent to provide backpressure on senders
if (err == 0 && reliable) {
if (err == 0 && (flags & ENET_PACKET_FLAG_RELIABLE)) {
// Try to send the packet
enet_host_service(client, NULL, 0);
@@ -697,13 +715,13 @@ static bool sendMessageTcp(short ptype, short paylen, const void* payload) {
return true;
}
static bool sendMessageAndForget(short ptype, short paylen, const void* payload) {
static bool sendMessageAndForget(short ptype, short paylen, const void* payload, uint8_t channelId, uint32_t flags) {
bool ret;
// Unlike regular sockets, ENet sockets aren't safe to invoke from multiple
// threads at once. We have to synchronize them with a lock.
if (AppVersionQuad[0] >= 5) {
ret = sendMessageEnet(ptype, paylen, payload, true);
ret = sendMessageEnet(ptype, paylen, payload, channelId, flags);
}
else {
ret = sendMessageTcp(ptype, paylen, payload);
@@ -712,9 +730,9 @@ static bool sendMessageAndForget(short ptype, short paylen, const void* payload)
return ret;
}
static bool sendMessageAndDiscardReply(short ptype, short paylen, const void* payload) {
static bool sendMessageAndDiscardReply(short ptype, short paylen, const void* payload, uint8_t channelId, uint32_t flags) {
if (AppVersionQuad[0] >= 5) {
if (!sendMessageEnet(ptype, paylen, payload, true)) {
if (!sendMessageEnet(ptype, paylen, payload, channelId, flags)) {
return false;
}
}
@@ -1099,7 +1117,11 @@ static void lossStatsThreadFunc(void* context) {
while (LbqPollQueueElement(&frameFecStatusQueue, (void**)&queuedFrameStatus) == LBQ_SUCCESS) {
// Send as an unreliable packet, since it's not a critical message
if (!sendMessageEnet(SS_FRAME_FEC_PTYPE, sizeof(queuedFrameStatus->fecStatus), &queuedFrameStatus->fecStatus, false)) {
if (!sendMessageEnet(SS_FRAME_FEC_PTYPE,
sizeof(queuedFrameStatus->fecStatus),
&queuedFrameStatus->fecStatus,
CTRL_CHANNEL_GENERIC,
ENET_PACKET_FLAG_UNSEQUENCED)) {
Limelog("Loss Stats: Sending frame FEC status message failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
free(queuedFrameStatus);
@@ -1111,7 +1133,11 @@ static void lossStatsThreadFunc(void* context) {
}
// Send the message (and don't expect a response)
if (!sendMessageAndForget(0x0200, sizeof(periodicPingPayload), periodicPingPayload)) {
if (!sendMessageAndForget(0x0200,
sizeof(periodicPingPayload),
periodicPingPayload,
CTRL_CHANNEL_GENERIC,
ENET_PACKET_FLAG_RELIABLE)) {
Limelog("Loss Stats: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
@@ -1147,7 +1173,10 @@ static void lossStatsThreadFunc(void* context) {
// Send the message (and don't expect a response)
if (!sendMessageAndForget(packetTypes[IDX_LOSS_STATS],
payloadLengths[IDX_LOSS_STATS], lossStatsPayload)) {
payloadLengths[IDX_LOSS_STATS],
lossStatsPayload,
CTRL_CHANNEL_GENERIC,
0)) {
free(lossStatsPayload);
Limelog("Loss Stats: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
@@ -1186,7 +1215,10 @@ static void requestIdrFrame(void) {
// Send the reference frame invalidation request and read the response
if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES],
payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) {
payloadLengths[IDX_INVALIDATE_REF_FRAMES],
payload,
CTRL_CHANNEL_URGENT,
ENET_PACKET_FLAG_RELIABLE)) {
Limelog("Request IDR Frame: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
@@ -1195,7 +1227,10 @@ static void requestIdrFrame(void) {
else {
// Send IDR frame request and read the response
if (!sendMessageAndDiscardReply(packetTypes[IDX_REQUEST_IDR_FRAME],
payloadLengths[IDX_REQUEST_IDR_FRAME], preconstructedPayloads[IDX_REQUEST_IDR_FRAME])) {
payloadLengths[IDX_REQUEST_IDR_FRAME],
preconstructedPayloads[IDX_REQUEST_IDR_FRAME],
CTRL_CHANNEL_URGENT,
ENET_PACKET_FLAG_RELIABLE)) {
Limelog("Request IDR Frame: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
@@ -1217,7 +1252,9 @@ static void requestInvalidateReferenceFrames(int startFrame, int endFrame) {
// Send the reference frame invalidation request and read the response
if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES],
payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) {
payloadLengths[IDX_INVALIDATE_REF_FRAMES],
payload, CTRL_CHANNEL_URGENT,
ENET_PACKET_FLAG_RELIABLE)) {
Limelog("Request Invaldiate Reference Frames: Transaction failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
@@ -1326,11 +1363,11 @@ int stopControlStream(void) {
}
// Called by the input stream to send a packet for Gen 5+ servers
int sendInputPacketOnControlStream(unsigned char* data, int length) {
int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t channelId, uint32_t flags) {
LC_ASSERT(AppVersionQuad[0] >= 5);
// Send the input data (no reply expected)
if (sendMessageAndForget(packetTypes[IDX_INPUT_DATA], length, data) == 0) {
if (sendMessageAndForget(packetTypes[IDX_INPUT_DATA], length, data, channelId, flags) == 0) {
return -1;
}
@@ -1384,8 +1421,8 @@ int startControlStream(void) {
enet_address_set_address(&address, (struct sockaddr *)&RemoteAddr, RemoteAddrLen);
enet_address_set_port(&address, ControlPortNumber);
// Create a client that can use 1 outgoing connection and 1 channel
client = enet_host_create(address.address.ss_family, NULL, 1, 1, 0, 0);
// Create a client
client = enet_host_create(address.address.ss_family, NULL, 1, CTRL_CHANNEL_MAX + 1, 0, 0);
if (client == NULL) {
stopping = true;
return -1;
@@ -1394,7 +1431,7 @@ int startControlStream(void) {
client->intercept = ignoreDisconnectIntercept;
// Connect to the host
peer = enet_host_connect(client, &address, 1, 0);
peer = enet_host_connect(client, &address, CTRL_CHANNEL_MAX + 1, 0);
if (peer == NULL) {
stopping = true;
enet_host_destroy(client);
@@ -1471,8 +1508,10 @@ int startControlStream(void) {
// Send START A
if (!sendMessageAndDiscardReply(packetTypes[IDX_START_A],
payloadLengths[IDX_START_A],
preconstructedPayloads[IDX_START_A])) {
payloadLengths[IDX_START_A],
preconstructedPayloads[IDX_START_A],
CTRL_CHANNEL_GENERIC,
ENET_PACKET_FLAG_RELIABLE)) {
Limelog("Start A failed: %d\n", (int)LastSocketError());
err = LastSocketFail();
stopping = true;
@@ -1503,8 +1542,10 @@ int startControlStream(void) {
// Send START B
if (!sendMessageAndDiscardReply(packetTypes[IDX_START_B],
payloadLengths[IDX_START_B],
preconstructedPayloads[IDX_START_B])) {
payloadLengths[IDX_START_B],
preconstructedPayloads[IDX_START_B],
CTRL_CHANNEL_GENERIC,
ENET_PACKET_FLAG_RELIABLE)) {
Limelog("Start B failed: %d\n", (int)LastSocketError());
err = LastSocketFail();
stopping = true;

View File

@@ -18,6 +18,10 @@ static float absCurrentPosY;
// Limited by number of bits in activeGamepadMask
#define MAX_GAMEPADS 16
static uint8_t currentPenButtonState;
static uint16_t currentActiveGamepadMask;
static int currentControllerButtonState[MAX_GAMEPADS];
#define CLAMP(val, min, max) (((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)))
#define MAX_INPUT_PACKET_SIZE 128
@@ -46,6 +50,8 @@ static float absCurrentPosY;
// Contains input stream packets
typedef struct _PACKET_HOLDER {
LINKED_BLOCKING_QUEUE_ENTRY entry;
uint32_t enetPacketFlags;
uint8_t channelId;
// The union must be the last member since we abuse the NV_UNICODE_PACKET
// text field to store variable length data which gets split before being
@@ -92,6 +98,10 @@ int initializeInputStream(void) {
needsBatchedScroll = APP_VERSION_AT_LEAST(7, 1, 409) && !IS_SUNSHINE();
batchedScrollDelta = 0;
currentPenButtonState = 0;
currentActiveGamepadMask = 0;
memset(currentControllerButtonState, 0, sizeof(currentControllerButtonState));
// Start with the virtual mouse centered
absCurrentPosX = absCurrentPosY = 0.5f;
return 0;
@@ -208,7 +218,10 @@ static bool sendInputPacket(PPACKET_HOLDER holder) {
// has been removed. We send the plaintext packet through and the control stream code will do
// the encryption.
if (encryptedControlStream) {
err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*)&holder->packet, PACKET_SIZE(holder));
err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*)&holder->packet,
PACKET_SIZE(holder),
holder->channelId,
holder->enetPacketFlags);
if (err < 0) {
Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err);
ListenerCallbacks.connectionTerminated(err);
@@ -256,7 +269,9 @@ static bool sendInputPacket(PPACKET_HOLDER holder) {
}
err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*) encryptedBuffer,
(int) (encryptedSize + sizeof(encryptedLengthPrefix)));
(int)(encryptedSize + sizeof(encryptedLengthPrefix)),
holder->channelId,
holder->enetPacketFlags);
if (err < 0) {
Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err);
ListenerCallbacks.connectionTerminated(err);
@@ -614,6 +629,8 @@ static int sendEnableHaptics(void) {
return -1;
}
holder->channelId = CTRL_CHANNEL_GENERIC;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.haptics.header.size = BE32(sizeof(NV_HAPTICS_PACKET) - sizeof(uint32_t));
holder->packet.haptics.header.magic = LE32(ENABLE_HAPTICS_MAGIC);
holder->packet.haptics.enable = LE16(1);
@@ -716,6 +733,11 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
// These deltas are cumulative, so allow them to be processed out of order
holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED;
holder->packet.mouseMoveRel.header.size = BE32(sizeof(NV_REL_MOUSE_MOVE_PACKET) - sizeof(uint32_t));
if (AppVersionQuad[0] >= 5) {
holder->packet.mouseMoveRel.header.magic = LE32(MOUSE_MOVE_REL_MAGIC_GEN5);
@@ -750,6 +772,11 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
// The latest packet always contains the latest data, so discard older packets upon reordering
holder->enetPacketFlags = 0;
holder->packet.mouseMoveAbs.header.size = BE32(sizeof(NV_ABS_MOUSE_MOVE_PACKET) - sizeof(uint32_t));
holder->packet.mouseMoveAbs.header.magic = LE32(MOUSE_MOVE_ABS_MAGIC);
holder->packet.mouseMoveAbs.x = BE16(x);
@@ -806,6 +833,8 @@ int LiSendMouseButtonEvent(char action, int button) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.mouseButton.header.size = BE32(sizeof(NV_MOUSE_BUTTON_PACKET) - sizeof(uint32_t));
holder->packet.mouseButton.header.magic = (uint8_t)action;
if (AppVersionQuad[0] >= 5) {
@@ -838,6 +867,9 @@ int LiSendKeyboardEvent2(short keyCode, char keyAction, char modifiers, char fla
return -1;
}
holder->channelId = CTRL_CHANNEL_KEYBOARD;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
// For proper behavior, the MODIFIER flag must not be set on the modifier key down event itself
// for the extended modifiers on the right side of the keyboard. If the MODIFIER flag is set,
// GFE will synthesize an errant key down event for the non-extended key, causing that key to be
@@ -915,6 +947,10 @@ int LiSendUtf8TextEvent(const char *text, unsigned int length) {
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_UTF8;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
// Magic + string length
holder->packet.unicode.header.size = BE32(sizeof(uint32_t) + length);
holder->packet.unicode.header.magic = LE32(UTF8_TEXT_EVENT_MAGIC);
@@ -956,6 +992,16 @@ static int sendControllerEventInternal(short controllerNumber, short activeGamep
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
// If the active gamepad mask or button state changes, send a reliable event to ensure delivery.
// For axis-only updates, we use sequenced unreliable packets to only process the latest updates.
holder->enetPacketFlags = (activeGamepadMask != currentActiveGamepadMask || buttonFlags != currentControllerButtonState[controllerNumber]) ?
ENET_PACKET_FLAG_RELIABLE : 0;
currentActiveGamepadMask = activeGamepadMask;
currentControllerButtonState[controllerNumber] = buttonFlags;
if (AppVersionQuad[0] == 3) {
// Generation 3 servers don't support multiple controllers so we send
// the legacy packet
@@ -1065,6 +1111,11 @@ int LiSendHighResScrollEvent(short scrollAmount) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
// These deltas are cumulative, so allow them to be processed out of order
holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED;
holder->packet.scroll.header.size = BE32(sizeof(NV_SCROLL_PACKET) - sizeof(uint32_t));
if (AppVersionQuad[0] >= 5) {
holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC_GEN5);
@@ -1095,6 +1146,11 @@ int LiSendHighResScrollEvent(short scrollAmount) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
// These deltas are cumulative, so allow them to be processed out of order
holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED;
holder->packet.scroll.header.size = BE32(sizeof(NV_SCROLL_PACKET) - sizeof(uint32_t));
if (AppVersionQuad[0] >= 5) {
holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC_GEN5);
@@ -1145,6 +1201,11 @@ int LiSendHighResHScrollEvent(short scrollAmount) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
// These deltas are cumulative, so allow them to be processed out of order
holder->enetPacketFlags = ENET_PACKET_FLAG_UNSEQUENCED;
holder->packet.hscroll.header.size = BE32(sizeof(SS_HSCROLL_PACKET) - sizeof(uint32_t));
holder->packet.hscroll.header.magic = LE32(SS_HSCROLL_MAGIC);
holder->packet.hscroll.scrollAmount = BE16(scrollAmount);
@@ -1181,6 +1242,12 @@ int LiSendTouchEvent(uint8_t eventType, uint32_t pointerId, float x, float y, fl
return -1;
}
holder->channelId = CTRL_CHANNEL_TOUCH;
// Allow move and hover events to be dropped if a newer one arrives, but don't allow
// state changing events like up/down/leave events to be dropped.
holder->enetPacketFlags = TOUCH_EVENT_IS_BATCHABLE(eventType) ? 0 : ENET_PACKET_FLAG_RELIABLE;
holder->packet.touch.header.size = BE32(sizeof(SS_TOUCH_PACKET) - sizeof(uint32_t));
holder->packet.touch.header.magic = LE32(SS_TOUCH_MAGIC);
holder->packet.touch.eventType = eventType;
@@ -1220,6 +1287,13 @@ int LiSendPenEvent(uint8_t eventType, uint8_t toolType, uint8_t penButtons,
return -1;
}
holder->channelId = CTRL_CHANNEL_PEN;
// Allow move and hover events to be dropped if a newer one arrives (if no buttons changed),
// but don't allow state changing events like up/down/leave events to be dropped.
holder->enetPacketFlags = (TOUCH_EVENT_IS_BATCHABLE(eventType) && !(penButtons ^ currentPenButtonState)) ? 0 : ENET_PACKET_FLAG_RELIABLE;
currentPenButtonState = penButtons;
holder->packet.pen.header.size = BE32(sizeof(SS_PEN_PACKET) - sizeof(uint32_t));
holder->packet.pen.header.magic = LE32(SS_PEN_MAGIC);
holder->packet.pen.eventType = eventType;
@@ -1262,6 +1336,10 @@ int LiSendControllerArrivalEvent(uint8_t controllerNumber, uint16_t activeGamepa
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.controllerArrival.header.size = BE32(sizeof(SS_CONTROLLER_ARRIVAL_PACKET) - sizeof(uint32_t));
holder->packet.controllerArrival.header.magic = LE32(SS_CONTROLLER_ARRIVAL_MAGIC);
holder->packet.controllerArrival.controllerNumber = controllerNumber;
@@ -1303,6 +1381,13 @@ int LiSendControllerTouchEvent(uint8_t controllerNumber, uint8_t eventType, uint
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
// Allow move and hover events to be dropped if a newer one arrives, but don't allow
// state changing events like up/down/leave events to be dropped.
holder->enetPacketFlags = TOUCH_EVENT_IS_BATCHABLE(eventType) ? 0 : ENET_PACKET_FLAG_RELIABLE;
holder->packet.controllerTouch.header.size = BE32(sizeof(SS_CONTROLLER_TOUCH_PACKET) - sizeof(uint32_t));
holder->packet.controllerTouch.header.magic = LE32(SS_CONTROLLER_TOUCH_MAGIC);
holder->packet.controllerTouch.controllerNumber = controllerNumber;
@@ -1344,6 +1429,12 @@ int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, fl
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
// Motion events are so rapid that we can just drop any events that are lost in transit
holder->enetPacketFlags = 0;
holder->packet.controllerMotion.header.size = BE32(sizeof(SS_CONTROLLER_MOTION_PACKET) - sizeof(uint32_t));
holder->packet.controllerMotion.header.magic = LE32(SS_CONTROLLER_MOTION_MAGIC);
holder->packet.controllerMotion.controllerNumber = controllerNumber;
@@ -1384,6 +1475,10 @@ int LiSendControllerBatteryEvent(uint8_t controllerNumber, uint8_t batteryState,
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.controllerBattery.header.size = BE32(sizeof(SS_CONTROLLER_BATTERY_PACKET) - sizeof(uint32_t));
holder->packet.controllerBattery.header.magic = LE32(SS_CONTROLLER_BATTERY_MAGIC);
holder->packet.controllerBattery.controllerNumber = controllerNumber;

View File

@@ -52,6 +52,17 @@ extern uint32_t SunshineFeatureFlags;
#define SCM_AV1_MAIN8 0x10000 // Sunshine extension
#define SCM_AV1_MAIN10 0x20000 // Sunshine extension
// ENet channel ID values
#define CTRL_CHANNEL_GENERIC 0x00
#define CTRL_CHANNEL_URGENT 0x01 // IDR and reference frame invalidation requests
#define CTRL_CHANNEL_KEYBOARD 0x02
#define CTRL_CHANNEL_MOUSE 0x03
#define CTRL_CHANNEL_PEN 0x04
#define CTRL_CHANNEL_TOUCH 0x05
#define CTRL_CHANNEL_UTF8 0x06
#define CTRL_CHANNEL_GAMEPAD_BASE 0x10 // 0x10 to 0x20 by controller index
#define CTRL_CHANNEL_MAX 0x20
#ifndef UINT24_MAX
#define UINT24_MAX 0xFFFFFF
#endif
@@ -106,7 +117,7 @@ void connectionReceivedCompleteFrame(int frameIndex);
void connectionSawFrame(int frameIndex);
void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket);
void connectionSendFrameFecStatus(PSS_FRAME_FEC_STATUS fecStatus);
int sendInputPacketOnControlStream(unsigned char* data, int length);
int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t channelId, uint32_t flags);
bool isControlDataInTransit(void);
int performRtspHandshake(PSERVER_INFORMATION serverInfo);