Add backwards compatibility for GFE 2.1.x

This commit is contained in:
Cameron Gutman 2015-02-01 20:16:08 -05:00
parent 7883ce67cd
commit 0fa1a02e0a
7 changed files with 197 additions and 43 deletions

View File

@ -8,6 +8,8 @@ static CONNECTION_LISTENER_CALLBACKS originalCallbacks;
// This is used for debug prints so it's not declared static // This is used for debug prints so it's not declared static
PLATFORM_CALLBACKS platformCallbacks; PLATFORM_CALLBACKS platformCallbacks;
int serverMajorVersion;
static int alreadyTerminated; static int alreadyTerminated;
/* Connection stages */ /* Connection stages */
@ -148,8 +150,10 @@ void LiCompleteThreadStart(void)
/* Starts the connection to the streaming machine */ /* Starts the connection to the streaming machine */
int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, PPLATFORM_CALLBACKS plCallbacks, PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, PPLATFORM_CALLBACKS plCallbacks,
void* renderContext, int drFlags) { void* renderContext, int drFlags, int _serverMajorVersion) {
int err; int err;
serverMajorVersion = _serverMajorVersion;
memcpy(&originalCallbacks, clCallbacks, sizeof(originalCallbacks)); memcpy(&originalCallbacks, clCallbacks, sizeof(originalCallbacks));
memcpy(&platformCallbacks, plCallbacks, sizeof(platformCallbacks)); memcpy(&platformCallbacks, plCallbacks, sizeof(platformCallbacks));

View File

@ -20,22 +20,59 @@ static PCONNECTION_LISTENER_CALLBACKS listenerCallbacks;
static int lossCountSinceLastReport = 0; static int lossCountSinceLastReport = 0;
static long currentFrame = 0; static long currentFrame = 0;
#define PTYPE_START_STREAM_A 0x0606 #define IDX_START_A 0
#define PPAYLEN_START_STREAM_A 2 #define IDX_START_B 1
static const char PPAYLOAD_START_STREAM_A[PPAYLEN_START_STREAM_A] = { 0, 0 }; #define IDX_RESYNC 2
#define IDX_LOSS_STATS 3
#define PTYPE_START_STREAM_B 0x0609 static const short packetTypesGen3[] = {
#define PPAYLEN_START_STREAM_B 1 0x140b, // Start A
static const char PPAYLOAD_START_STREAM_B[PPAYLEN_START_STREAM_B] = { 0 }; 0x1410, // Start B
0x1404, // Resync
0x140c, // Loss Stats
0x1417, // Frame Stats (unused)
};
static const short packetTypesGen4[] = {
0x0606, // Start A
0x0609, // Start B
0x0604, // Resync
0x060a, // Loss Stats
0x0611, // Frame Stats (unused)
};
#define PTYPE_RESYNC 0x0604 static const char startAGen3[] = {0};
#define PPAYLEN_RESYNC 24 static const int startBGen3[] = {0, 0, 0, 0xa};
#define PTYPE_LOSS_STATS 0x060a static const char startAGen4[] = {0, 0};
#define PPAYLEN_LOSS_STATS 32 static const char startBGen4[] = {0};
#define PTYPE_FRAME_STATS 0x0611 static const short payloadLengthsGen3[] = {
#define PPAYLEN_FRAME_STATS 64 sizeof(startAGen3), // Start A
sizeof(startBGen3), // Start B
24, // Resync
32, // Loss Stats
64, // Frame Stats
};
static const short payloadLengthsGen4[] = {
sizeof(startAGen4), // Start A
sizeof(startBGen4), // Start B
24, // Resync
32, // Loss Stats
64, // Frame Stats
};
static const char* preconstructedPayloadsGen3[] = {
startAGen3,
(char*)startBGen3
};
static const char* preconstructedPayloadsGen4[] = {
startAGen4,
startBGen4
};
static short *packetTypes;
static short *payloadLengths;
static char **preconstructedPayloads;
#define LOSS_REPORT_INTERVAL_MS 50 #define LOSS_REPORT_INTERVAL_MS 50
@ -47,6 +84,17 @@ int initializeControlStream(IP_ADDRESS addr, PSTREAM_CONFIGURATION streamConfigP
host = addr; host = addr;
listenerCallbacks = clCallbacks; listenerCallbacks = clCallbacks;
if (serverMajorVersion == 3) {
packetTypes = (short*)packetTypesGen3;
payloadLengths = (short*)payloadLengthsGen3;
preconstructedPayloads = (char**)preconstructedPayloadsGen3;
}
else {
packetTypes = (short*)packetTypesGen4;
payloadLengths = (short*)payloadLengthsGen4;
preconstructedPayloads = (char**)preconstructedPayloadsGen4;
}
return 0; return 0;
} }
@ -156,12 +204,12 @@ static int sendMessageAndDiscardReply(short ptype, short paylen, const void* pay
} }
static void lossStatsThreadFunc(void* context) { static void lossStatsThreadFunc(void* context) {
char lossStatsPayload[PPAYLEN_LOSS_STATS]; char lossStatsPayload[payloadLengths[IDX_LOSS_STATS]];
BYTE_BUFFER byteBuffer; BYTE_BUFFER byteBuffer;
while (!PltIsThreadInterrupted(&lossStatsThread)) { while (!PltIsThreadInterrupted(&lossStatsThread)) {
// Construct the payload // Construct the payload
BbInitializeWrappedBuffer(&byteBuffer, lossStatsPayload, 0, PPAYLEN_LOSS_STATS, BYTE_ORDER_LITTLE); BbInitializeWrappedBuffer(&byteBuffer, lossStatsPayload, 0, payloadLengths[IDX_LOSS_STATS], BYTE_ORDER_LITTLE);
BbPutInt(&byteBuffer, lossCountSinceLastReport); BbPutInt(&byteBuffer, lossCountSinceLastReport);
BbPutInt(&byteBuffer, LOSS_REPORT_INTERVAL_MS); BbPutInt(&byteBuffer, LOSS_REPORT_INTERVAL_MS);
BbPutInt(&byteBuffer, 1000); BbPutInt(&byteBuffer, 1000);
@ -171,8 +219,8 @@ static void lossStatsThreadFunc(void* context) {
BbPutInt(&byteBuffer, 0x14); BbPutInt(&byteBuffer, 0x14);
// Send the message (and don't expect a response) // Send the message (and don't expect a response)
if (!sendMessageAndForget(PTYPE_LOSS_STATS, if (!sendMessageAndForget(packetTypes[IDX_LOSS_STATS],
PPAYLEN_LOSS_STATS, lossStatsPayload)) { payloadLengths[IDX_LOSS_STATS], lossStatsPayload)) {
Limelog("Loss stats thread terminating #1\n"); Limelog("Loss stats thread terminating #1\n");
listenerCallbacks->connectionTerminated(LastSocketError()); listenerCallbacks->connectionTerminated(LastSocketError());
return; return;
@ -202,7 +250,7 @@ static void resyncThreadFunc(void* context) {
PltClearEvent(&resyncEvent); PltClearEvent(&resyncEvent);
// Send the resync request and read the response // Send the resync request and read the response
if (!sendMessageAndDiscardReply(PTYPE_RESYNC, PPAYLEN_RESYNC, payload)) { if (!sendMessageAndDiscardReply(packetTypes[IDX_RESYNC], payloadLengths[IDX_RESYNC], payload)) {
Limelog("Resync thread terminating #1\n"); Limelog("Resync thread terminating #1\n");
listenerCallbacks->connectionTerminated(LastSocketError()); listenerCallbacks->connectionTerminated(LastSocketError());
return; return;
@ -242,16 +290,16 @@ int startControlStream(void) {
enableNoDelay(ctlSock); enableNoDelay(ctlSock);
// Send START A // Send START A
if (!sendMessageAndDiscardReply(PTYPE_START_STREAM_A, if (!sendMessageAndDiscardReply(packetTypes[IDX_START_A],
PPAYLEN_START_STREAM_A, payloadLengths[IDX_START_A],
PPAYLOAD_START_STREAM_A)) { preconstructedPayloads[IDX_START_A])) {
return LastSocketError(); return LastSocketError();
} }
// Send START B // Send START B
if (!sendMessageAndDiscardReply(PTYPE_START_STREAM_B, if (!sendMessageAndDiscardReply(packetTypes[IDX_START_B],
PPAYLEN_START_STREAM_B, payloadLengths[IDX_START_B],
PPAYLOAD_START_STREAM_B)) { preconstructedPayloads[IDX_START_B])) {
return LastSocketError(); return LastSocketError();
} }

View File

@ -6,11 +6,10 @@
#include "PlatformThreads.h" #include "PlatformThreads.h"
#include "Video.h" #include "Video.h"
/* GFE 2.2.2+ RTSP/SDP version code */ extern int serverMajorVersion;
#define RTSP_CLIENT_VERSION 11
#define RTSP_CLIENT_VERSION_S "11"
char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress, int *length); char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress,
int rtspClientVersion, int *length);
int initializeControlStream(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks); int initializeControlStream(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks);
int startControlStream(void); int startControlStream(void);

View File

@ -103,7 +103,7 @@ typedef struct _PLATFORM_CALLBACKS {
int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, int LiStartConnection(IP_ADDRESS host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks,
PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, PPLATFORM_CALLBACKS plCallbacks, PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, PPLATFORM_CALLBACKS plCallbacks,
void* renderContext, int drFlags); void* renderContext, int drFlags, int _serverMajorVersion);
void LiStopConnection(void); void LiStopConnection(void);
const char* LiGetStageName(int stage); const char* LiGetStageName(int stage);

View File

@ -10,6 +10,7 @@ static char rtspTargetUrl[256];
static char sessionIdString[16]; static char sessionIdString[16];
static int hasSessionId; static int hasSessionId;
static char responseBuffer[RTSP_MAX_RESP_SIZE]; static char responseBuffer[RTSP_MAX_RESP_SIZE];
static int rtspClientVersion;
/* Create RTSP Option */ /* Create RTSP Option */
static POPTION_ITEM createOptionItem(char* option, char* content) static POPTION_ITEM createOptionItem(char* option, char* content)
@ -60,14 +61,16 @@ static int addOption(PRTSP_MESSAGE msg, char* option, char* content)
static int initializeRtspRequest(PRTSP_MESSAGE msg, char* command, char* target) static int initializeRtspRequest(PRTSP_MESSAGE msg, char* command, char* target)
{ {
char sequenceNumberStr[16]; char sequenceNumberStr[16];
char clientVersionStr[16];
// FIXME: Hacked CSeq attribute due to RTSP parser bug // FIXME: Hacked CSeq attribute due to RTSP parser bug
createRtspRequest(msg, NULL, 0, command, target, "RTSP/1.0", createRtspRequest(msg, NULL, 0, command, target, "RTSP/1.0",
0, NULL, NULL, 0); 0, NULL, NULL, 0);
sprintf(sequenceNumberStr, "%d", currentSeqNumber++); sprintf(sequenceNumberStr, "%d", currentSeqNumber++);
sprintf(clientVersionStr, "%d", rtspClientVersion);
if (!addOption(msg, "CSeq", sequenceNumberStr) || if (!addOption(msg, "CSeq", sequenceNumberStr) ||
!addOption(msg, "X-GS-ClientVersion", RTSP_CLIENT_VERSION_S)) { !addOption(msg, "X-GS-ClientVersion", clientVersionStr)) {
freeMessage(msg); freeMessage(msg);
return 0; return 0;
} }
@ -247,7 +250,8 @@ static int sendVideoAnnounce(PRTSP_MESSAGE response, PSTREAM_CONFIGURATION strea
} }
memcpy(&sdpAddr, &remoteAddr, sizeof(remoteAddr)); memcpy(&sdpAddr, &remoteAddr, sizeof(remoteAddr));
request.payload = getSdpPayloadForStreamConfig(streamConfig, sdpAddr, &payloadLength); request.payload = getSdpPayloadForStreamConfig(streamConfig, sdpAddr,
rtspClientVersion, &payloadLength);
if (request.payload == NULL) { if (request.payload == NULL) {
goto FreeMessage; goto FreeMessage;
} }
@ -278,6 +282,13 @@ int performRtspHandshake(IP_ADDRESS addr, PSTREAM_CONFIGURATION streamConfigPtr)
sprintf(rtspTargetUrl, "rtsp://%s", inet_ntoa(inaddr)); sprintf(rtspTargetUrl, "rtsp://%s", inet_ntoa(inaddr));
currentSeqNumber = 1; currentSeqNumber = 1;
hasSessionId = 0; hasSessionId = 0;
if (serverMajorVersion == 3) {
rtspClientVersion = 10;
}
else {
rtspClientVersion = 11;
}
{ {
RTSP_MESSAGE response; RTSP_MESSAGE response;

View File

@ -88,6 +88,62 @@ static int addAttributeString(PSDP_OPTION *head, char* name, const char* payload
return addAttributeBinary(head, name, payload, (int)strlen(payload)); return addAttributeBinary(head, name, payload, (int)strlen(payload));
} }
static int addGen3Options(PSDP_OPTION *head, char* addrStr) {
int payloadInt;
int err = 0;
err |= addAttributeString(head, "x-nv-general.serverAddress", addrStr);
payloadInt = htonl(0x42774141);
err |= addAttributeBinary(head,
"x-nv-general.featureFlags", &payloadInt, sizeof(payloadInt));
payloadInt = htonl(0x41514141);
err |= addAttributeBinary(head,
"x-nv-video[0].transferProtocol", &payloadInt, sizeof(payloadInt));
err |= addAttributeBinary(head,
"x-nv-video[1].transferProtocol", &payloadInt, sizeof(payloadInt));
err |= addAttributeBinary(head,
"x-nv-video[2].transferProtocol", &payloadInt, sizeof(payloadInt));
err |= addAttributeBinary(head,
"x-nv-video[3].transferProtocol", &payloadInt, sizeof(payloadInt));
payloadInt = htonl(0x42414141);
err |= addAttributeBinary(head,
"x-nv-video[0].rateControlMode", &payloadInt, sizeof(payloadInt));
payloadInt = htonl(0x42514141);
err |= addAttributeBinary(head,
"x-nv-video[1].rateControlMode", &payloadInt, sizeof(payloadInt));
err |= addAttributeBinary(head,
"x-nv-video[2].rateControlMode", &payloadInt, sizeof(payloadInt));
err |= addAttributeBinary(head,
"x-nv-video[3].rateControlMode", &payloadInt, sizeof(payloadInt));
err |= addAttributeString(head, "x-nv-vqos[0].bw.flags", "14083");
err |= addAttributeString(head, "x-nv-vqos[0].videoQosMaxConsecutiveDrops", "0");
err |= addAttributeString(head, "x-nv-vqos[1].videoQosMaxConsecutiveDrops", "0");
err |= addAttributeString(head, "x-nv-vqos[2].videoQosMaxConsecutiveDrops", "0");
err |= addAttributeString(head, "x-nv-vqos[3].videoQosMaxConsecutiveDrops", "0");
return err;
}
static int addGen4Options(PSDP_OPTION *head, char* addrStr) {
char payloadStr[92];
int err = 0;
sprintf(payloadStr, "rtsp://%s:48010", addrStr);
err |= addAttributeString(head, "x-nv-general.serverAddress", payloadStr);
err |= addAttributeString(head, "x-nv-video[0].rateControlMode", "4");
err |= addAttributeString(head, "x-nv-vqos[0].bw.flags", "51");
return err;
}
static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress) { static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress) {
PSDP_OPTION optionHead; PSDP_OPTION optionHead;
char payloadStr[92]; char payloadStr[92];
@ -95,9 +151,6 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct
optionHead = NULL; optionHead = NULL;
err = 0; err = 0;
sprintf(payloadStr, "rtsp://%s:48010", inet_ntoa(targetAddress));
err |= addAttributeString(&optionHead, "x-nv-general.serverAddress", payloadStr);
sprintf(payloadStr, "%d", streamConfig->width); sprintf(payloadStr, "%d", streamConfig->width);
err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportWd", payloadStr); err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportWd", payloadStr);
@ -121,9 +174,6 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct
err |= addAttributeString(&optionHead, "x-nv-video[0].timeoutLengthMs", "7000"); err |= addAttributeString(&optionHead, "x-nv-video[0].timeoutLengthMs", "7000");
err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0"); err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0");
// This flags value will mean that resolution won't change as bitrate falls
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.flags", "51");
// Lock the bitrate since we're not scaling resolution so the picture doesn't get too bad // Lock the bitrate since we're not scaling resolution so the picture doesn't get too bad
if (streamConfig->height >= 1080 && streamConfig->fps >= 60) { if (streamConfig->height >= 1080 && streamConfig->fps >= 60) {
if (streamConfig->bitrate < 10000) { if (streamConfig->bitrate < 10000) {
@ -167,6 +217,13 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct
// FIXME: Remote optimizations // FIXME: Remote optimizations
err |= addAttributeString(&optionHead, "x-nv-vqos[0].qosTrafficType", "5"); err |= addAttributeString(&optionHead, "x-nv-vqos[0].qosTrafficType", "5");
err |= addAttributeString(&optionHead, "x-nv-aqos.qosTrafficType", "4"); err |= addAttributeString(&optionHead, "x-nv-aqos.qosTrafficType", "4");
if (serverMajorVersion == 3) {
err |= addGen3Options(&optionHead, inet_ntoa(targetAddress));
}
else {
err |= addGen4Options(&optionHead, inet_ntoa(targetAddress));
}
if (err == 0) { if (err == 0) {
return optionHead; return optionHead;
@ -177,22 +234,24 @@ static PSDP_OPTION getAttributesList(PSTREAM_CONFIGURATION streamConfig, struct
} }
/* Populate the SDP header with required information */ /* Populate the SDP header with required information */
static int fillSdpHeader(char* buffer, struct in_addr targetAddress) { static int fillSdpHeader(char* buffer, struct in_addr targetAddress, int rtspClientVersion) {
return sprintf(buffer, return sprintf(buffer,
"v=0\r\n" "v=0\r\n"
"o=android 0 "RTSP_CLIENT_VERSION_S" IN IPv4 %s\r\n" "o=android 0 %d IN IPv4 %s\r\n"
"s=NVIDIA Streaming Client\r\n", inet_ntoa(targetAddress)); "s=NVIDIA Streaming Client\r\n", rtspClientVersion, inet_ntoa(targetAddress));
} }
/* Populate the SDP tail with required information */ /* Populate the SDP tail with required information */
static int fillSdpTail(char* buffer) { static int fillSdpTail(char* buffer) {
return sprintf(buffer, return sprintf(buffer,
"t=0 0\r\n" "t=0 0\r\n"
"m=video 47998 \r\n"); "m=video %d \r\n",
serverMajorVersion < 4 ? 47996 : 47998);
} }
/* Get the SDP attributes for the stream config */ /* Get the SDP attributes for the stream config */
char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress, int *length) { char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in_addr targetAddress,
int rtspClientVersion, int *length) {
PSDP_OPTION attributeList; PSDP_OPTION attributeList;
int offset; int offset;
char* payload; char* payload;
@ -209,7 +268,7 @@ char* getSdpPayloadForStreamConfig(PSTREAM_CONFIGURATION streamConfig, struct in
return NULL; return NULL;
} }
offset = fillSdpHeader(payload, targetAddress); offset = fillSdpHeader(payload, targetAddress, rtspClientVersion);
offset += fillSerializedAttributeList(&payload[offset], attributeList); offset += fillSerializedAttributeList(&payload[offset], attributeList);
offset += fillSdpTail(&payload[offset]); offset += fillSdpTail(&payload[offset]);

View File

@ -6,6 +6,7 @@
#define FIRST_FRAME_MAX 1500 #define FIRST_FRAME_MAX 1500
#define RTP_PORT 47998 #define RTP_PORT 47998
#define FIRST_FRAME_PORT 47996
static DECODER_RENDERER_CALLBACKS callbacks; static DECODER_RENDERER_CALLBACKS callbacks;
static STREAM_CONFIGURATION configuration; static STREAM_CONFIGURATION configuration;
@ -13,6 +14,7 @@ static IP_ADDRESS remoteHost;
static PCONNECTION_LISTENER_CALLBACKS listenerCallbacks; static PCONNECTION_LISTENER_CALLBACKS listenerCallbacks;
static SOCKET rtpSocket = INVALID_SOCKET; static SOCKET rtpSocket = INVALID_SOCKET;
static SOCKET firstFrameSocket = INVALID_SOCKET;
static PLT_THREAD udpPingThread; static PLT_THREAD udpPingThread;
static PLT_THREAD receiveThread; static PLT_THREAD receiveThread;
@ -108,6 +110,17 @@ static void DecoderThreadProc(void* context) {
} }
} }
/* Read the first frame of the video stream */
int readFirstFrame(void) {
// All that matters is that we close this socket.
// This starts the flow of video on Gen 3 servers.
closesocket(firstFrameSocket);
firstFrameSocket = INVALID_SOCKET;
return 0;
}
/* Terminate the video stream */ /* Terminate the video stream */
void stopVideoStream(void) { void stopVideoStream(void) {
callbacks.stop(); callbacks.stop();
@ -116,6 +129,10 @@ void stopVideoStream(void) {
PltInterruptThread(&receiveThread); PltInterruptThread(&receiveThread);
PltInterruptThread(&decoderThread); PltInterruptThread(&decoderThread);
if (firstFrameSocket != INVALID_SOCKET) {
closesocket(firstFrameSocket);
firstFrameSocket = INVALID_SOCKET;
}
if (rtpSocket != INVALID_SOCKET) { if (rtpSocket != INVALID_SOCKET) {
closesocket(rtpSocket); closesocket(rtpSocket);
rtpSocket = INVALID_SOCKET; rtpSocket = INVALID_SOCKET;
@ -156,12 +173,28 @@ int startVideoStream(void* rendererContext, int drFlags) {
return err; return err;
} }
if (serverMajorVersion == 3) {
// Connect this socket to open port 47998 for our ping thread
firstFrameSocket = connectTcpSocket(remoteHost, FIRST_FRAME_PORT);
if (firstFrameSocket == INVALID_SOCKET) {
return LastSocketError();
}
}
// Start pinging before reading the first frame so GFE knows where // Start pinging before reading the first frame so GFE knows where
// to send UDP data // to send UDP data
err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread); err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread);
if (err != 0) { if (err != 0) {
return err; return err;
} }
if (serverMajorVersion == 3) {
// Read the first frame to start the flow of video
err = readFirstFrame();
if (err != 0) {
return err;
}
}
return 0; return 0;
} }