SOCKET connectTcpSocket(struct sockaddr_storage *dstaddr, SOCKADDR_LEN addrlen, unsigned short port) { SOCKET s; struct sockaddr_in6 addr; int err; #ifdef LC_DARWIN int val; #endif s = socket(dstaddr->ss_family, SOCK_STREAM, IPPROTO_TCP); if (s == INVALID_SOCKET) { Limelog("socket() failed: %d\n", (int)LastSocketError()); return INVALID_SOCKET; } #ifdef LC_DARWIN // Disable SIGPIPE on iOS val = 1; setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (char* )&val, sizeof(val)); #endif memcpy(&addr, dstaddr, sizeof(addr)); addr.sin6_port = htons(port); if (connect(s, (struct sockaddr*) &addr, addrlen) == SOCKET_ERROR) { err = LastSocketError(); Limelog("connect() failed: %d\n", err); closesocket(s); SetLastSocketError(err); return INVALID_SOCKET; } return s; }
static void requestIdrFrame(void) { long long payload[3]; if (ServerMajorVersion == 3) { // Form the payload payload[0] = 0; payload[1] = 0xFFFFF; payload[2] = 0; // Send the reference frame invalidation request and read the response if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES], payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) { Limelog("Request IDR Frame: Transaction failed: %d\n", (int) LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); return; } } 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])) { Limelog("Request IDR Frame: Transaction failed: %d\n", (int) LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); return; } } Limelog("IDR frame request sent\n"); }
// Start the video stream int startVideoStream(void* rendererContext, int drFlags) { int err; // This must be called before the decoder thread starts submitting // decode units LC_ASSERT(NegotiatedVideoFormat != 0); VideoCallbacks.setup(NegotiatedVideoFormat, StreamConfig.width, StreamConfig.height, StreamConfig.fps, rendererContext, drFlags); rtpSocket = bindUdpSocket(RemoteAddr.ss_family, RTP_RECV_BUFFER); if (rtpSocket == INVALID_SOCKET) { return LastSocketError(); } err = PltCreateThread(ReceiveThreadProc, NULL, &receiveThread); if (err != 0) { return err; } if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) { err = PltCreateThread(DecoderThreadProc, NULL, &decoderThread); if (err != 0) { return err; } } if (ServerMajorVersion == 3) { // Connect this socket to open port 47998 for our ping thread firstFrameSocket = connectTcpSocket(&RemoteAddr, RemoteAddrLen, FIRST_FRAME_PORT, FIRST_FRAME_TIMEOUT_SEC); if (firstFrameSocket == INVALID_SOCKET) { return LastSocketError(); } } // Start pinging before reading the first frame so GFE knows where // to send UDP data err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread); if (err != 0) { return err; } if (ServerMajorVersion == 3) { // Read the first frame to start the flow of video err = readFirstFrame(); if (err != 0) { return err; } } return 0; }
static void requestInvalidateReferenceFrames(void) { long long payload[3]; PQUEUED_FRAME_INVALIDATION_TUPLE qfit; LC_ASSERT(VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION); if (!getNextFrameInvalidationTuple(&qfit)) { return; } LC_ASSERT(qfit->startFrame <= qfit->endFrame); payload[0] = qfit->startFrame; payload[1] = qfit->endFrame; payload[2] = 0; // Aggregate all lost frames into one range do { LC_ASSERT(qfit->endFrame >= payload[1]); payload[1] = qfit->endFrame; free(qfit); } while (getNextFrameInvalidationTuple(&qfit)); // Send the reference frame invalidation request and read the response if (!sendMessageAndDiscardReply(packetTypes[IDX_INVALIDATE_REF_FRAMES], payloadLengths[IDX_INVALIDATE_REF_FRAMES], payload)) { Limelog("Request Invaldiate Reference Frames: Transaction failed: %d\n", (int) LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); return; } Limelog("Invalidate reference frame request sent\n"); }
static void UdpPingThreadProc(void* context) { // Ping in ASCII char pingData[] = { 0x50, 0x49, 0x4E, 0x47 }; struct sockaddr_in6 saddr; SOCK_RET err; memcpy(&saddr, &RemoteAddr, sizeof(saddr)); saddr.sin6_port = htons(RTP_PORT); // Send PING every second until we get data back then every 5 seconds after that. while (!PltIsThreadInterrupted(&udpPingThread)) { err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen); if (err != sizeof(pingData)) { Limelog("Audio Ping: sendto() failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); return; } // Send less frequently if we've received data from our peer if (receivedDataFromPeer) { PltSleepMsInterruptible(&udpPingThread, 5000); } else { PltSleepMsInterruptible(&udpPingThread, 1000); } } }
int startAudioStream(void) { int err; callbacks.init(); rtpSocket = bindUdpSocket(); if (rtpSocket == INVALID_SOCKET) { return LastSocketError(); } err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread); if (err != 0) { return err; } err = PltCreateThread(ReceiveThreadProc, NULL, &receiveThread); if (err != 0) { return err; } err = PltCreateThread(DecoderThreadProc, NULL, &decoderThread); if (err != 0) { return err; } callbacks.start(); return 0; }
static void ReceiveThreadProc(void* context) { SOCK_RET err; PRTP_PACKET rtp; int packetSize; char* buffer = NULL; while (!PltIsThreadInterrupted(&receiveThread)) { if (buffer == NULL) { buffer = (char*) malloc(MAX_PACKET_SIZE + sizeof(int)); if (buffer == NULL) { Limelog("Receive thread terminating\n"); listenerCallbacks->connectionTerminated(-1); return; } } err = recv(rtpSocket, &buffer[sizeof(int)], MAX_PACKET_SIZE, 0); if (err <= 0) { Limelog("Receive thread terminating #2\n"); free(buffer); listenerCallbacks->connectionTerminated(LastSocketError()); return; } packetSize = (int)err; if (packetSize < sizeof(RTP_PACKET)) { // Runt packet continue; } rtp = (PRTP_PACKET) &buffer[sizeof(int)]; if (rtp->packetType != 97) { // Not audio continue; } memcpy(buffer, &packetSize, sizeof(int)); err = LbqOfferQueueItem(&packetQueue, buffer); if (err == LBQ_SUCCESS) { // The queue owns the buffer now buffer = NULL; } if (err == LBQ_BOUND_EXCEEDED) { Limelog("Audio packet queue overflow\n"); freePacketList(LbqFlushQueueItems(&packetQueue)); } else if (err == LBQ_INTERRUPTED) { Limelog("Receive thread terminating #2\n"); free(buffer); return; } } }
// UDP Ping proc static void UdpPingThreadProc(void* context) { char pingData[] = { 0x50, 0x49, 0x4E, 0x47 }; struct sockaddr_in6 saddr; SOCK_RET err; memcpy(&saddr, &RemoteAddr, sizeof(saddr)); saddr.sin6_port = htons(RTP_PORT); while (!PltIsThreadInterrupted(&udpPingThread)) { err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen); if (err != sizeof(pingData)) { Limelog("Video Ping: send() failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); return; } PltSleepMs(500); } }
int enableNoDelay(SOCKET s) { int err; int val; val = 1; err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val)); if (err == SOCKET_ERROR) { return LastSocketError(); } return 0; }
static void lossStatsThreadFunc(void* context) { char *lossStatsPayload; BYTE_BUFFER byteBuffer; lossStatsPayload = malloc(payloadLengths[IDX_LOSS_STATS]); if (lossStatsPayload == NULL) { Limelog("Loss Stats: malloc() failed\n"); ListenerCallbacks.connectionTerminated(-1); return; } while (!PltIsThreadInterrupted(&lossStatsThread)) { // Construct the payload BbInitializeWrappedBuffer(&byteBuffer, lossStatsPayload, 0, payloadLengths[IDX_LOSS_STATS], BYTE_ORDER_LITTLE); BbPutInt(&byteBuffer, lossCountSinceLastReport); BbPutInt(&byteBuffer, LOSS_REPORT_INTERVAL_MS); BbPutInt(&byteBuffer, 1000); BbPutLong(&byteBuffer, currentFrame); BbPutInt(&byteBuffer, 0); BbPutInt(&byteBuffer, 0); BbPutInt(&byteBuffer, 0x14); // Send the message (and don't expect a response) if (!sendMessageAndForget(packetTypes[IDX_LOSS_STATS], payloadLengths[IDX_LOSS_STATS], lossStatsPayload)) { free(lossStatsPayload); Limelog("Loss Stats: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); return; } // Clear the transient state lossCountSinceLastReport = 0; // Wait a bit PltSleepMs(LOSS_REPORT_INTERVAL_MS); } free(lossStatsPayload); }
SOCKET bindUdpSocket(int addrfamily) { SOCKET s; struct sockaddr_storage addr; int val; int err; LC_ASSERT(addrfamily == AF_INET || addrfamily == AF_INET6); s = socket(addrfamily, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) { Limelog("socket() failed: %d\n", (int)LastSocketError()); return INVALID_SOCKET; } memset(&addr, 0, sizeof(addr)); addr.ss_family = addrfamily; if (bind(s, (struct sockaddr*) &addr, addrfamily == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) == SOCKET_ERROR) { err = LastSocketError(); Limelog("bind() failed: %d\n", err); closesocket(s); SetLastSocketError(err); return INVALID_SOCKET; } #ifdef LC_DARWIN // Disable SIGPIPE on iOS val = 1; setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (char* )&val, sizeof(val)); #endif // Set the receive buffer to 64KB by default val = 65536; setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*) &val, sizeof(val)); return s; }
void setRecvTimeout(SOCKET s, int timeoutSec) { #if defined(LC_WINDOWS) int val = timeoutSec * 1000; #else struct timeval val; val.tv_sec = timeoutSec; val.tv_usec = 0; #endif if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&val, sizeof(val)) < 0) { Limelog("setsockopt(SO_RCVTIMEO) failed: %d\n", (int)LastSocketError()); } }
/* Starts the control stream */ int startControlStream(void) { int err; ctlSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen, 47995); if (ctlSock == INVALID_SOCKET) { return LastSocketFail(); } enableNoDelay(ctlSock); // Send START A if (!sendMessageAndDiscardReply(packetTypes[IDX_START_A], payloadLengths[IDX_START_A], preconstructedPayloads[IDX_START_A])) { Limelog("Start A failed: %d\n", (int)LastSocketError()); return LastSocketFail(); } // Send START B if (!sendMessageAndDiscardReply(packetTypes[IDX_START_B], payloadLengths[IDX_START_B], preconstructedPayloads[IDX_START_B])) { Limelog("Start B failed: %d\n", (int)LastSocketError()); return LastSocketFail(); } err = PltCreateThread(lossStatsThreadFunc, NULL, &lossStatsThread); if (err != 0) { return err; } err = PltCreateThread(resyncThreadFunc, NULL, &resyncThread); if (err != 0) { return err; } return 0; }
static void resyncThreadFunc(void* context) { long long payload[3]; while (!PltIsThreadInterrupted(&resyncThread)) { // Wait for a resync request PltWaitForEvent(&resyncEvent); // Form the payload payload[0] = 0; payload[1] = 0xFFFFF; payload[2] = 0; // Done capturing the parameters PltClearEvent(&resyncEvent); // Send the resync request and read the response if (!sendMessageAndDiscardReply(packetTypes[IDX_RESYNC], payloadLengths[IDX_RESYNC], payload)) { Limelog("Resync: Transaction failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); return; } Limelog("Resync complete\n"); } }
static void UdpPingThreadProc(void *context) { /* Ping in ASCII */ char pingData[] = { 0x50, 0x49, 0x4E, 0x47 }; struct sockaddr_in saddr; SOCK_RET err; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(RTP_PORT); memcpy(&saddr.sin_addr, &remoteHost, sizeof(remoteHost)); /* Send PING every 500 milliseconds */ while (!PltIsThreadInterrupted(&udpPingThread)) { err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, sizeof(saddr)); if (err != sizeof(pingData)) { Limelog("UDP ping thread terminating #1\n"); listenerCallbacks->connectionTerminated(LastSocketError()); return; } PltSleepMs(500); } }
SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen, unsigned short port, int timeoutSec) { SOCKET s; struct sockaddr_in6 addr; int err; #if defined(LC_DARWIN) || defined(FIONBIO) int val; #endif s = socket(dstaddr->ss_family, SOCK_STREAM, IPPROTO_TCP); if (s == INVALID_SOCKET) { Limelog("socket() failed: %d\n", (int)LastSocketError()); return INVALID_SOCKET; } #ifdef LC_DARWIN // Disable SIGPIPE on iOS val = 1; setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (char*)&val, sizeof(val)); #endif #ifdef FIONBIO // Enable non-blocking I/O for connect timeout support val = 1; ioctlsocket(s, FIONBIO, &val); #endif // Start connection memcpy(&addr, dstaddr, sizeof(addr)); addr.sin6_port = htons(port); err = connect(s, (struct sockaddr*) &addr, addrlen); if (err < 0) { err = (int)LastSocketError(); } #ifdef FIONBIO { struct fd_set writefds, exceptfds; struct timeval tv; FD_ZERO(&writefds); FD_ZERO(&exceptfds); FD_SET(s, &writefds); FD_SET(s, &exceptfds); tv.tv_sec = timeoutSec; tv.tv_usec = 0; // Wait for the connection to complete or the timeout to elapse err = select(s + 1, NULL, &writefds, &exceptfds, &tv); if (err < 0) { // select() failed err = LastSocketError(); Limelog("select() failed: %d\n", err); closeSocket(s); SetLastSocketError(err); return INVALID_SOCKET; } else if (err == 0) { // select() timed out Limelog("select() timed out after %d seconds\n", timeoutSec); closeSocket(s); #if defined(LC_WINDOWS) SetLastSocketError(WSAEWOULDBLOCK); #else SetLastSocketError(EWOULDBLOCK); #endif return INVALID_SOCKET; } else if (FD_ISSET(s, &writefds) || FD_ISSET(s, &exceptfds)) { // The socket was signalled SOCKADDR_LEN len = sizeof(err); getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, &len); if (err != 0 || FD_ISSET(s, &exceptfds)) { // Get the error code err = (err != 0) ? err : LastSocketFail(); } } // Disable non-blocking I/O now that the connection is established val = 0; ioctlsocket(s, FIONBIO, &val); } #endif if (err != 0) { Limelog("connect() failed: %d\n", err); closeSocket(s); SetLastSocketError(err); return INVALID_SOCKET; } return s; }
/* Input thread proc */ static void inputSendThreadProc(void* context) { SOCK_RET err; PPACKET_HOLDER holder; char encryptedBuffer[MAX_INPUT_PACKET_SIZE]; size_t encryptedSize; while (!PltIsThreadInterrupted(&inputSendThread)) { int encryptedLengthPrefix; err = LbqWaitForQueueElement(&packetQueue, (void**) &holder); if (err != LBQ_SUCCESS) { return; } // If it's a multi-controller packet we can do batching if (holder->packet.multiController.header.packetType == htonl(PACKET_TYPE_MULTI_CONTROLLER)) { PPACKET_HOLDER controllerBatchHolder; PNV_MULTI_CONTROLLER_PACKET origPkt; int dirs[6]; memset(dirs, 0, sizeof(dirs)); origPkt = &holder->packet.multiController; for (;;) { PNV_MULTI_CONTROLLER_PACKET newPkt; // Peek at the next packet if (LbqPeekQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) { break; } // If it's not a controller packet, we're done if (controllerBatchHolder->packet.multiController.header.packetType != htonl(PACKET_TYPE_MULTI_CONTROLLER)) { break; } // Check if it's able to be batched newPkt = &controllerBatchHolder->packet.multiController; if (newPkt->buttonFlags != origPkt->buttonFlags || newPkt->controllerNumber != origPkt->controllerNumber || !checkDirs(origPkt->leftTrigger, newPkt->leftTrigger, &dirs[0]) || !checkDirs(origPkt->rightTrigger, newPkt->rightTrigger, &dirs[1]) || !checkDirs(origPkt->leftStickX, newPkt->leftStickX, &dirs[2]) || !checkDirs(origPkt->leftStickY, newPkt->leftStickY, &dirs[3]) || !checkDirs(origPkt->rightStickX, newPkt->rightStickX, &dirs[4]) || !checkDirs(origPkt->rightStickY, newPkt->rightStickY, &dirs[5])) { // Batching not allowed break; } // Remove the batchable controller packet if (LbqPollQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) { break; } // Update the original packet origPkt->leftTrigger = newPkt->leftTrigger; origPkt->rightTrigger = newPkt->rightTrigger; origPkt->leftStickX = newPkt->leftStickX; origPkt->leftStickY = newPkt->leftStickY; origPkt->rightStickX = newPkt->rightStickX; origPkt->rightStickY = newPkt->rightStickY; // Free the batched packet holder free(controllerBatchHolder); } } // If it's a mouse move packet, we can also do batching else if (holder->packet.mouseMove.header.packetType == htonl(PACKET_TYPE_MOUSE_MOVE)) { PPACKET_HOLDER mouseBatchHolder; int totalDeltaX = (short)htons(holder->packet.mouseMove.deltaX); int totalDeltaY = (short)htons(holder->packet.mouseMove.deltaY); for (;;) { int partialDeltaX; int partialDeltaY; // Peek at the next packet if (LbqPeekQueueElement(&packetQueue, (void**)&mouseBatchHolder) != LBQ_SUCCESS) { break; } // If it's not a mouse move packet, we're done if (mouseBatchHolder->packet.mouseMove.header.packetType != htonl(PACKET_TYPE_MOUSE_MOVE)) { break; } partialDeltaX = (short)htons(mouseBatchHolder->packet.mouseMove.deltaX); partialDeltaY = (short)htons(mouseBatchHolder->packet.mouseMove.deltaY); // Check for overflow if (partialDeltaX + totalDeltaX > INT16_MAX || partialDeltaX + totalDeltaX < INT16_MIN || partialDeltaY + totalDeltaY > INT16_MAX || partialDeltaY + totalDeltaY < INT16_MIN) { // Total delta would overflow our 16-bit short break; } // Remove the batchable mouse move packet if (LbqPollQueueElement(&packetQueue, (void**)&mouseBatchHolder) != LBQ_SUCCESS) { break; } totalDeltaX += partialDeltaX; totalDeltaY += partialDeltaY; // Free the batched packet holder free(mouseBatchHolder); } // Update the original packet holder->packet.mouseMove.deltaX = htons((short)totalDeltaX); holder->packet.mouseMove.deltaY = htons((short)totalDeltaY); } encryptedSize = sizeof(encryptedBuffer); err = oaes_encrypt(oaesContext, (const unsigned char*) &holder->packet, holder->packetLength, (unsigned char*) encryptedBuffer, &encryptedSize); free(holder); if (err != OAES_RET_SUCCESS) { Limelog("Input: Encryption failed: %d\n", (int)err); ListenerCallbacks.connectionTerminated(err); return; } // The first 32-bytes of the output are internal OAES stuff that we want to ignore encryptedSize -= OAES_DATA_OFFSET; // Overwrite the last 4 bytes before the encrypted data with the length so // we can send the message all at once. GFE can choke if it gets the header // before the rest of the message. encryptedLengthPrefix = htonl((unsigned long) encryptedSize); memcpy(&encryptedBuffer[OAES_DATA_OFFSET - sizeof(encryptedLengthPrefix)], &encryptedLengthPrefix, sizeof(encryptedLengthPrefix)); // Send the encrypted payload err = send(inputSock, (const char*) &encryptedBuffer[OAES_DATA_OFFSET - sizeof(encryptedLengthPrefix)], (int)(encryptedSize + sizeof(encryptedLengthPrefix)), 0); if (err <= 0) { Limelog("Input: send() failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); return; } } }
// Receive thread proc static void ReceiveThreadProc(void* context) { int err; int bufferSize, receiveSize; char* buffer; int queueStatus; receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE; bufferSize = receiveSize + sizeof(int) + sizeof(RTP_QUEUE_ENTRY); buffer = NULL; while (!PltIsThreadInterrupted(&receiveThread)) { PRTP_PACKET packet; if (buffer == NULL) { buffer = (char*)malloc(bufferSize); if (buffer == NULL) { Limelog("Video Receive: malloc() failed\n"); ListenerCallbacks.connectionTerminated(-1); return; } } err = recvUdpSocket(rtpSocket, buffer, receiveSize); if (err < 0) { Limelog("Video Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketError()); break; } else if (err == 0) { // Receive timed out; try again continue; } memcpy(&buffer[receiveSize], &err, sizeof(int)); // RTP sequence number must be in host order for the RTP queue packet = (PRTP_PACKET)&buffer[0]; packet->sequenceNumber = htons(packet->sequenceNumber); queueStatus = RtpqAddPacket(&rtpQueue, packet, (PRTP_QUEUE_ENTRY)&buffer[receiveSize + sizeof(int)]); if (queueStatus == RTPQ_RET_HANDLE_IMMEDIATELY) { // queueRtpPacket() copies the data it needs to we can reuse the buffer queueRtpPacket(packet, err); } else if (queueStatus == RTPQ_RET_QUEUED_PACKETS_READY) { // The packet queue now has packets ready while ((buffer = (char*)RtpqGetQueuedPacket(&rtpQueue)) != NULL) { memcpy(&err, &buffer[receiveSize], sizeof(int)); queueRtpPacket((PRTP_PACKET)buffer, err); free(buffer); } } else if (queueStatus == RTPQ_RET_QUEUED_NOTHING_READY) { // The queue owns the buffer buffer = NULL; } } if (buffer != NULL) { free(buffer); } }
static void ReceiveThreadProc(void* context) { PRTP_PACKET rtp; PQUEUED_AUDIO_PACKET packet; int queueStatus; int useSelect; int packetsToDrop = 100; packet = NULL; if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) { // SO_RCVTIMEO failed, so use select() to wait useSelect = 1; } else { // SO_RCVTIMEO timeout set for recv() useSelect = 0; } while (!PltIsThreadInterrupted(&receiveThread)) { if (packet == NULL) { packet = (PQUEUED_AUDIO_PACKET)malloc(sizeof(*packet)); if (packet == NULL) { Limelog("Audio Receive: malloc() failed\n"); ListenerCallbacks.connectionTerminated(-1); break; } } packet->size = recvUdpSocket(rtpSocket, &packet->data[0], MAX_PACKET_SIZE, useSelect); if (packet->size < 0) { Limelog("Audio Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError()); ListenerCallbacks.connectionTerminated(LastSocketFail()); break; } else if (packet->size == 0) { // Receive timed out; try again // If we hit this path, there are no queued audio packets on the host PC, // so we don't need to drop anything. packetsToDrop = 0; continue; } if (packet->size < sizeof(RTP_PACKET)) { // Runt packet continue; } rtp = (PRTP_PACKET)&packet->data[0]; if (rtp->packetType != 97) { // Not audio continue; } // We've received data, so we can stop sending our ping packets // as quickly, since we're now just keeping the NAT session open. receivedDataFromPeer = 1; // GFE accumulates audio samples before we are ready to receive them, // so we will drop the first 100 packets to avoid accumulating latency // by sending audio frames to the player faster than they can be played. if (packetsToDrop > 0) { packetsToDrop--; continue; } // RTP sequence number must be in host order for the RTP queue rtp->sequenceNumber = htons(rtp->sequenceNumber); queueStatus = RtpqAddPacket(&rtpReorderQueue, (PRTP_PACKET)packet, &packet->q.rentry); if (RTPQ_HANDLE_NOW(queueStatus)) { if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) { if (!queuePacketToLbq(&packet)) { // An exit signal was received break; } } else { decodeInputData(packet); } } else { if (RTPQ_PACKET_CONSUMED(queueStatus)) { // The queue consumed our packet, so we must allocate a new one packet = NULL; } if (RTPQ_PACKET_READY(queueStatus)) { // If packets are ready, pull them and send them to the decoder while ((packet = (PQUEUED_AUDIO_PACKET)RtpqGetQueuedPacket(&rtpReorderQueue)) != NULL) { if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) { if (!queuePacketToLbq(&packet)) { // An exit signal was received break; } } else { decodeInputData(packet); free(packet); } } // Break on exit if (packet != NULL) { break; } } } } if (packet != NULL) { free(packet); } }