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); } } }
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; } } }
static void DecoderThreadProc(void* context) { int err; PQUEUED_AUDIO_PACKET packet; while (!PltIsThreadInterrupted(&decoderThread)) { err = LbqWaitForQueueElement(&packetQueue, (void**)&packet); if (err != LBQ_SUCCESS) { // An exit signal was received return; } decodeInputData(packet); free(packet); } }
// Decoder thread proc static void DecoderThreadProc(void* context) { PQUEUED_DECODE_UNIT qdu; while (!PltIsThreadInterrupted(&decoderThread)) { if (!getNextQueuedDecodeUnit(&qdu)) { return; } int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit); freeQueuedDecodeUnit(qdu); if (ret == DR_NEED_IDR) { Limelog("Requesting IDR frame on behalf of DR\n"); requestDecoderRefresh(); } } }
// 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); } }
static void DecoderThreadProc(void* context) { PRTP_PACKET rtp; int length; int err; char *data; unsigned short lastSeq = 0; while (!PltIsThreadInterrupted(&decoderThread)) { err = LbqWaitForQueueElement(&packetQueue, (void**) &data); if (err != LBQ_SUCCESS) { Limelog("Decoder thread terminating\n"); return; } memcpy(&length, data, sizeof(int)); rtp = (PRTP_PACKET) &data[sizeof(int)]; if (length < sizeof(RTP_PACKET)) { // Runt packet goto freeandcontinue; } if (rtp->packetType != 97) { // Not audio goto freeandcontinue; } rtp->sequenceNumber = htons(rtp->sequenceNumber); if (lastSeq != 0 && (unsigned short) (lastSeq + 1) != rtp->sequenceNumber) { Limelog("Received OOS audio data (expected %d, but got %d)\n", lastSeq + 1, rtp->sequenceNumber); callbacks.decodeAndPlaySample(NULL, 0); } lastSeq = rtp->sequenceNumber; callbacks.decodeAndPlaySample((char *) (rtp + 1), length - sizeof(*rtp)); freeandcontinue: free(data); } }
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); }
static void invalidateRefFramesFunc(void* context) { while (!PltIsThreadInterrupted(&invalidateRefFramesThread)) { // Wait for a request to invalidate reference frames PltWaitForEvent(&invalidateRefFramesEvent); PltClearEvent(&invalidateRefFramesEvent); // Sometimes we absolutely need an IDR frame if (idrFrameRequired) { // Empty invalidate reference frames tuples PQUEUED_FRAME_INVALIDATION_TUPLE qfit; while (getNextFrameInvalidationTuple(&qfit)) { free(qfit); } // Send an IDR frame request idrFrameRequired = 0; requestIdrFrame(); } else { // Otherwise invalidate reference frames requestInvalidateReferenceFrames(); } } }
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); } }
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"); } }
/* 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); } }