/* Initializes the input stream */ int initializeInputStream(char* aesKeyData, int aesKeyDataLength, char* aesIv, int aesIvLength) { if (aesIvLength != OAES_BLOCK_SIZE) { Limelog("AES IV is incorrect length. Should be %d\n", aesIvLength); return -1; } oaesContext = oaes_alloc(); if (oaesContext == NULL) { Limelog("Failed to allocate OpenAES context\n"); return -1; } if (oaes_set_option(oaesContext, OAES_OPTION_CBC, aesIv) != OAES_RET_SUCCESS) { Limelog("Failed to set CBC and IV on OAES context\n"); return -1; } if (oaes_key_import_data(oaesContext, (const unsigned char*)aesKeyData, aesKeyDataLength) != OAES_RET_SUCCESS) { Limelog("Failed to import AES key data\n"); return -1; } LbqInitializeLinkedBlockingQueue(&packetQueue, 30); initialized = 1; return 0; }
static PRTP_QUEUE_ENTRY validateQueueConstraints(PRTP_REORDER_QUEUE queue) { int needsUpdate = 0; // Empty queue is fine if (queue->queueHead == NULL) { return NULL; } // Check that the queue's time constraint is satisfied if (PltGetMillis() - queue->oldestQueuedTimeMs > queue->maxQueueTimeMs) { Limelog("Discarding RTP packet queued for too long\n"); removeEntry(queue, queue->oldestQueuedEntry); free(queue->oldestQueuedEntry->packet); needsUpdate = 1; } // Check that the queue's size constraint is satisfied if (!needsUpdate && queue->queueSize == queue->maxSize) { Limelog("Discarding RTP packet after queue overgrowth\n"); removeEntry(queue, queue->oldestQueuedEntry); free(queue->oldestQueuedEntry->packet); needsUpdate = 1; } if (needsUpdate) { // Recalculate the oldest entry if needed updateOldestQueued(queue); // Return the lowest seq queued return getEntryByLowestSeq(queue); } else { return NULL; } }
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 int resolveHostName(const char *host) { struct addrinfo hints, *res; int err; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_ADDRCONFIG; err = getaddrinfo(host, NULL, &hints, &res); if (err != 0) { Limelog("getaddrinfo() failed: %d\n", err); return err; } if (res == NULL) { Limelog("getaddrinfo() returned success without addresses\n"); return -1; } // Use the first address in the list memcpy(&RemoteAddr, res->ai_addr, res->ai_addrlen); RemoteAddrLen = res->ai_addrlen; freeaddrinfo(res); 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 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"); }
/* Send RTSP message and get response */ static int transactRtspMessage(PRTSP_MESSAGE request, PRTSP_MESSAGE response) { SOCK_RET err; int ret = 0; int offset; char* serializedMessage = NULL; int messageLen; sock = connectTcpSocket(remoteAddr, 48010); if (sock == INVALID_SOCKET) { return ret; } enableNoDelay(sock); serializedMessage = serializeRtspMessage(request, &messageLen); if (serializedMessage == NULL) { closesocket(sock); return ret; } // Send our message err = send(sock, serializedMessage, messageLen, 0); if (err == SOCKET_ERROR) { goto Exit; } // Read the response until the server closes the connection offset = 0; for (;;) { err = recv(sock, &responseBuffer[offset], RTSP_MAX_RESP_SIZE - offset, 0); if (err <= 0) { // Done reading break; } offset += err; // Warn if the RTSP message is too big if (offset == RTSP_MAX_RESP_SIZE) { Limelog("RTSP message too long\n"); goto Exit; } } if (parseRtspMessage(response, responseBuffer, offset) == RTSP_ERROR_SUCCESS) { // Successfully parsed response ret = 1; } else { Limelog("Failed to parse RTSP response\n"); } Exit: if (serializedMessage != NULL) { free(serializedMessage); } closesocket(sock); sock = INVALID_SOCKET; return ret; }
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 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 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); }
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()); } }
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; }
/* 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 decodeInputData(PQUEUED_AUDIO_PACKET packet) { PRTP_PACKET rtp; rtp = (PRTP_PACKET)&packet->data[0]; if (lastSeq != 0 && (unsigned short)(lastSeq + 1) != rtp->sequenceNumber) { Limelog("Received OOS audio data (expected %d, but got %d)\n", lastSeq + 1, rtp->sequenceNumber); AudioCallbacks.decodeAndPlaySample(NULL, 0); } lastSeq = rtp->sequenceNumber; AudioCallbacks.decodeAndPlaySample((char*)(rtp + 1), packet->size - sizeof(*rtp)); }
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"); } }
// 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(); } } }
static int queuePacketToLbq(PQUEUED_AUDIO_PACKET* packet) { int err; err = LbqOfferQueueItem(&packetQueue, *packet, &(*packet)->q.lentry); if (err == LBQ_SUCCESS) { // The LBQ owns the buffer now *packet = NULL; } else if (err == LBQ_BOUND_EXCEEDED) { Limelog("Audio packet queue overflow\n"); freePacketList(LbqFlushQueueItems(&packetQueue)); } else if (err == LBQ_INTERRUPTED) { return 0; } return 1; }
// 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); } }
// This shim callback runs the client's connectionTerminated() callback on a // separate thread. This is neccessary because other internal threads directly // invoke this callback. That can result in a deadlock if the client // calls LiStopConnection() in the callback when the cleanup code // attempts to join the thread that the termination callback (and LiStopConnection) // is running on. static void ClInternalConnectionTerminated(long errorCode) { int err; // Avoid recursion and issuing multiple callbacks if (alreadyTerminated) { return; } alreadyTerminated = 1; // Invoke the termination callback on a separate thread err = PltCreateThread(terminationCallbackThreadFunc, NULL, &terminationCallbackThread); if (err != 0) { // Nothing we can safely do here, so we'll just assert on debug builds Limelog("Failed to create termination thread: %d\n", err); LC_ASSERT(err == 0); } // Close the thread handle since we can never wait on it PltCloseThread(&terminationCallbackThread); }
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); } }
/* Stop the connection by undoing the step at the current stage and those before it */ void LiStopConnection(void) { // Disable termination callbacks now alreadyTerminated = 1; if (stage == STAGE_INPUT_STREAM_START) { Limelog("Stopping input stream..."); stopInputStream(); stage--; Limelog("done\n"); } if (stage == STAGE_AUDIO_STREAM_START) { Limelog("Stopping audio stream..."); stopAudioStream(); stage--; Limelog("done\n"); } if (stage == STAGE_VIDEO_STREAM_START) { Limelog("Stopping video stream..."); stopVideoStream(); stage--; Limelog("done\n"); } if (stage == STAGE_CONTROL_STREAM_START) { Limelog("Stopping control stream..."); stopControlStream(); stage--; Limelog("done\n"); } if (stage == STAGE_INPUT_STREAM_INIT) { Limelog("Cleaning up input stream..."); destroyInputStream(); stage--; Limelog("done\n"); } if (stage == STAGE_AUDIO_STREAM_INIT) { Limelog("Cleaning up audio stream..."); destroyAudioStream(); stage--; Limelog("done\n"); } if (stage == STAGE_VIDEO_STREAM_INIT) { Limelog("Cleaning up video stream..."); destroyVideoStream(); stage--; Limelog("done\n"); } if (stage == STAGE_CONTROL_STREAM_INIT) { Limelog("Cleaning up control stream..."); destroyControlStream(); stage--; Limelog("done\n"); } if (stage == STAGE_RTSP_HANDSHAKE) { Limelog("Terminating RTSP handshake..."); terminateRtspHandshake(); stage--; Limelog("done\n"); } if (stage == STAGE_NAME_RESOLUTION) { // Nothing to do stage--; } if (stage == STAGE_PLATFORM_INIT) { Limelog("Cleaning up platform..."); cleanupPlatform(); stage--; Limelog("done\n"); } LC_ASSERT(stage == STAGE_NONE); }
/* Starts the connection to the streaming machine */ int LiStartConnection(const char* host, PSTREAM_CONFIGURATION streamConfig, PCONNECTION_LISTENER_CALLBACKS clCallbacks, PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks, void* renderContext, int drFlags, int _serverMajorVersion) { int err; ServerMajorVersion = _serverMajorVersion; memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig)); // Replace missing callbacks with placeholders fixupMissingCallbacks(&drCallbacks, &arCallbacks, &clCallbacks); memcpy(&VideoCallbacks, drCallbacks, sizeof(VideoCallbacks)); memcpy(&AudioCallbacks, arCallbacks, sizeof(AudioCallbacks)); // Hook the termination callback so we can avoid issuing a termination callback // after LiStopConnection() is called originalTerminationCallback = clCallbacks->connectionTerminated; memcpy(&ListenerCallbacks, clCallbacks, sizeof(ListenerCallbacks)); ListenerCallbacks.connectionTerminated = ClInternalConnectionTerminated; alreadyTerminated = 0; Limelog("Initializing platform..."); ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT); err = initializePlatform(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_PLATFORM_INIT, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_PLATFORM_INIT); ListenerCallbacks.stageComplete(STAGE_PLATFORM_INIT); Limelog("done\n"); Limelog("Resolving host name..."); ListenerCallbacks.stageStarting(STAGE_NAME_RESOLUTION); err = resolveHostName(host); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_NAME_RESOLUTION); ListenerCallbacks.stageComplete(STAGE_NAME_RESOLUTION); Limelog("done\n"); Limelog("Starting RTSP handshake..."); ListenerCallbacks.stageStarting(STAGE_RTSP_HANDSHAKE); err = performRtspHandshake(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_RTSP_HANDSHAKE, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_RTSP_HANDSHAKE); ListenerCallbacks.stageComplete(STAGE_RTSP_HANDSHAKE); Limelog("done\n"); Limelog("Initializing control stream..."); ListenerCallbacks.stageStarting(STAGE_CONTROL_STREAM_INIT); err = initializeControlStream(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_CONTROL_STREAM_INIT, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_CONTROL_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_CONTROL_STREAM_INIT); Limelog("done\n"); Limelog("Initializing video stream..."); ListenerCallbacks.stageStarting(STAGE_VIDEO_STREAM_INIT); initializeVideoStream(); stage++; LC_ASSERT(stage == STAGE_VIDEO_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_VIDEO_STREAM_INIT); Limelog("done\n"); Limelog("Initializing audio stream..."); ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_INIT); initializeAudioStream(); stage++; LC_ASSERT(stage == STAGE_AUDIO_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_INIT); Limelog("done\n"); Limelog("Initializing input stream..."); ListenerCallbacks.stageStarting(STAGE_INPUT_STREAM_INIT); initializeInputStream(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey), streamConfig->remoteInputAesIv, sizeof(streamConfig->remoteInputAesIv)); stage++; LC_ASSERT(stage == STAGE_INPUT_STREAM_INIT); ListenerCallbacks.stageComplete(STAGE_INPUT_STREAM_INIT); Limelog("done\n"); Limelog("Starting control stream..."); ListenerCallbacks.stageStarting(STAGE_CONTROL_STREAM_START); err = startControlStream(); if (err != 0) { Limelog("failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_CONTROL_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_CONTROL_STREAM_START); ListenerCallbacks.stageComplete(STAGE_CONTROL_STREAM_START); Limelog("done\n"); Limelog("Starting video stream..."); ListenerCallbacks.stageStarting(STAGE_VIDEO_STREAM_START); err = startVideoStream(renderContext, drFlags); if (err != 0) { Limelog("Video stream start failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_VIDEO_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_VIDEO_STREAM_START); ListenerCallbacks.stageComplete(STAGE_VIDEO_STREAM_START); Limelog("done\n"); Limelog("Starting audio stream..."); ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_START); err = startAudioStream(); if (err != 0) { Limelog("Audio stream start failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_AUDIO_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_AUDIO_STREAM_START); ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_START); Limelog("done\n"); Limelog("Starting input stream..."); ListenerCallbacks.stageStarting(STAGE_INPUT_STREAM_START); err = startInputStream(); if (err != 0) { Limelog("Input stream start failed: %d\n", err); ListenerCallbacks.stageFailed(STAGE_INPUT_STREAM_START, err); goto Cleanup; } stage++; LC_ASSERT(stage == STAGE_INPUT_STREAM_START); ListenerCallbacks.stageComplete(STAGE_INPUT_STREAM_START); Limelog("done\n"); ListenerCallbacks.connectionStarted(); Cleanup: return err; }
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); } }
/* Perform RTSP Handshake with the streaming server machine as part of the connection process */ int performRtspHandshake(IP_ADDRESS addr, PSTREAM_CONFIGURATION streamConfigPtr) { struct in_addr inaddr; // Initialize global state remoteAddr = addr; memcpy(&inaddr, &addr, sizeof(addr)); sprintf(rtspTargetUrl, "rtsp://%s", inet_ntoa(inaddr)); currentSeqNumber = 1; hasSessionId = 0; if (serverMajorVersion == 3) { rtspClientVersion = 10; } else { rtspClientVersion = 11; } { RTSP_MESSAGE response; if (!requestOptions(&response)) { Limelog("RTSP OPTIONS request failed\n"); return -1; } if (response.message.response.statusCode != 200) { Limelog("RTSP OPTIONS request failed: %d\n", response.message.response.statusCode); return -1; } freeMessage(&response); } { RTSP_MESSAGE response; if (!requestDescribe(&response)) { Limelog("RTSP DESCRIBE request failed\n"); return -1; } if (response.message.response.statusCode != 200) { Limelog("RTSP DESCRIBE request failed: %d\n", response.message.response.statusCode); return -1; } freeMessage(&response); } { RTSP_MESSAGE response; char* sessionId; if (!setupStream(&response, "streamid=audio")) { Limelog("RTSP SETUP streamid=audio request failed\n"); return -1; } if (response.message.response.statusCode != 200) { Limelog("RTSP SETUP streamid=audio request failed: %d\n", response.message.response.statusCode); return -1; } sessionId = getOptionContent(response.options, "Session"); if (sessionId == NULL) { Limelog("RTSP SETUP streamid=audio is missing session attribute"); return -1; } strcpy(sessionIdString, sessionId); hasSessionId = 1; freeMessage(&response); } { RTSP_MESSAGE response; if (!setupStream(&response, "streamid=video")) { Limelog("RTSP SETUP streamid=video request failed\n"); return -1; } if (response.message.response.statusCode != 200) { Limelog("RTSP SETUP streamid=video request failed: %d\n", response.message.response.statusCode); return -1; } freeMessage(&response); } { RTSP_MESSAGE response; if (!sendVideoAnnounce(&response, streamConfigPtr)) { Limelog("RTSP ANNOUNCE request failed\n"); return -1; } if (response.message.response.statusCode != 200) { Limelog("RTSP ANNOUNCE request failed: %d\n", response.message.response.statusCode); return -1; } freeMessage(&response); } { RTSP_MESSAGE response; if (!playStream(&response, "streamid=video")) { Limelog("RTSP PLAY streamid=video request failed\n"); return -1; } if (response.message.response.statusCode != 200) { Limelog("RTSP PLAY streamid=video failed: %d\n", response.message.response.statusCode); return -1; } freeMessage(&response); } { RTSP_MESSAGE response; if (!playStream(&response, "streamid=audio")) { Limelog("RTSP PLAY streamid=audio request failed\n"); return -1; } if (response.message.response.statusCode != 200) { Limelog("RTSP PLAY streamid=audio failed: %d\n", response.message.response.statusCode); return -1; } freeMessage(&response); } return 0; }
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; }
// 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); } }
/* 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; } } }
int startAudioStream(void* audioContext, int arFlags) { int err; POPUS_MULTISTREAM_CONFIGURATION chosenConfig; if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_STEREO) { chosenConfig = &opusStereoConfig; } else if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND) { if (HighQualitySurroundEnabled) { chosenConfig = &opus51HighSurroundConfig; } else { chosenConfig = &opus51SurroundConfig; } } else { Limelog("Invalid audio configuration: %d\n", StreamConfig.audioConfiguration); return -1; } err = AudioCallbacks.init(StreamConfig.audioConfiguration, chosenConfig, audioContext, arFlags); if (err != 0) { return err; } rtpSocket = bindUdpSocket(RemoteAddr.ss_family, RTP_RECV_BUFFER); if (rtpSocket == INVALID_SOCKET) { err = LastSocketFail(); AudioCallbacks.cleanup(); return err; } AudioCallbacks.start(); err = PltCreateThread(ReceiveThreadProc, NULL, &receiveThread); if (err != 0) { AudioCallbacks.stop(); closeSocket(rtpSocket); AudioCallbacks.cleanup(); return err; } if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) { err = PltCreateThread(DecoderThreadProc, NULL, &decoderThread); if (err != 0) { AudioCallbacks.stop(); PltInterruptThread(&receiveThread); PltJoinThread(&receiveThread); PltCloseThread(&receiveThread); closeSocket(rtpSocket); AudioCallbacks.cleanup(); return err; } } // Don't start pinging (which will cause GFE to start sending us traffic) // until everything else is started. Otherwise we could accumulate a // bunch of audio packets in the socket receive buffer while our audio // backend is starting up and create audio latency. err = PltCreateThread(UdpPingThreadProc, NULL, &udpPingThread); if (err != 0) { AudioCallbacks.stop(); PltInterruptThread(&receiveThread); if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) { // Signal threads waiting on the LBQ LbqSignalQueueShutdown(&packetQueue); PltInterruptThread(&decoderThread); } PltJoinThread(&receiveThread); if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) { PltJoinThread(&decoderThread); } PltCloseThread(&receiveThread); if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) { PltCloseThread(&decoderThread); } closeSocket(rtpSocket); AudioCallbacks.cleanup(); return err; } return 0; }