diff --git a/src/AudioStream.c b/src/AudioStream.c index 1d04c49..761535b 100644 --- a/src/AudioStream.c +++ b/src/AudioStream.c @@ -181,8 +181,6 @@ static void ReceiveThreadProc(void* context) { } if (!receivedDataFromPeer) { - // 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; Limelog("Received first audio packet after %d ms\n", waitingForAudioMs); } diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h index a4fd1a4..dc43cd1 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -84,6 +84,7 @@ void requestDecoderRefresh(void); void initializeVideoStream(void); void destroyVideoStream(void); int startVideoStream(void* rendererContext, int drFlags); +void submitFrame(PQUEUED_DECODE_UNIT qdu); void stopVideoStream(void); void initializeAudioStream(void); diff --git a/src/Limelight.h b/src/Limelight.h index 992bd43..1fad525 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -363,6 +363,11 @@ typedef void(*ConnListenerConnectionTerminated)(int errorCode); // firewall or port forwarding rules. #define ML_ERROR_NO_VIDEO_TRAFFIC -100 +// This error is passed to ConnListenerConnectionTerminated() if a fully formed +// frame could not be received after waiting several seconds. It likely indicates +// an extremely unstable connection or a bitrate that is far too high. +#define ML_ERROR_NO_VIDEO_FRAME -101 + // This callback is invoked to log debug message typedef void(*ConnListenerLogMessage)(const char* format, ...); diff --git a/src/VideoDepacketizer.c b/src/VideoDepacketizer.c index 4d7b877..26fb8e3 100644 --- a/src/VideoDepacketizer.c +++ b/src/VideoDepacketizer.c @@ -297,9 +297,8 @@ static void reassembleFrame(int frameNumber) { } } else { - int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit); - - completeQueuedDecodeUnit(qdu, ret); + // Submit the frame to the decoder + submitFrame(qdu); } // Notify the control connection diff --git a/src/VideoStream.c b/src/VideoStream.c index 65ed58d..cd3262e 100644 --- a/src/VideoStream.c +++ b/src/VideoStream.c @@ -21,6 +21,8 @@ static PLT_THREAD receiveThread; static PLT_THREAD decoderThread; static int receivedDataFromPeer; +static uint64_t firstDataTimeMs; +static int receivedFullFrame; // We can't request an IDR frame until the depacketizer knows // that a packet was lost. This timeout bounds the time that @@ -33,6 +35,8 @@ void initializeVideoStream(void) { initializeVideoDepacketizer(StreamConfig.packetSize); RtpfInitializeQueue(&rtpQueue); //TODO RTP_QUEUE_DELAY receivedDataFromPeer = 0; + firstDataTimeMs = 0; + receivedFullFrame = 0; } // Clean up the video stream @@ -120,10 +124,20 @@ static void ReceiveThreadProc(void* context) { } if (!receivedDataFromPeer) { - // 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; Limelog("Received first video packet after %d ms\n", waitingForVideoMs); + + firstDataTimeMs = PltGetMillis(); + } + + if (!receivedFullFrame) { + uint64_t now = PltGetMillis(); + + if (now - firstDataTimeMs >= FIRST_FRAME_TIMEOUT_SEC * 1000) { + Limelog("Terminating connection due to lack of a successful video frame\n"); + ListenerCallbacks.connectionTerminated(ML_ERROR_NO_VIDEO_FRAME); + break; + } } // Convert fields to host byte-order @@ -145,6 +159,15 @@ static void ReceiveThreadProc(void* context) { } } +void submitFrame(PQUEUED_DECODE_UNIT qdu) { + // Pass the frame to the decoder + int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit); + completeQueuedDecodeUnit(qdu, ret); + + // Remember that we got a full frame successfully + receivedFullFrame = 1; +} + // Decoder thread proc static void DecoderThreadProc(void* context) { PQUEUED_DECODE_UNIT qdu; @@ -153,9 +176,7 @@ static void DecoderThreadProc(void* context) { return; } - int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit); - - completeQueuedDecodeUnit(qdu, ret); + submitFrame(qdu); } }