diff --git a/src/Connection.c b/src/Connection.c index d4999e3..e0287fc 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -173,6 +173,18 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre void* audioContext, int arFlags) { int err; + if ((drCallbacks->capabilities & CAPABILITY_PULL_RENDERER) && drCallbacks->submitDecodeUnit) { + Limelog("CAPABILITY_PULL_RENDERER cannot be set with a submitDecodeUnit callback\n"); + err = -1; + goto Cleanup; + } + + if ((drCallbacks->capabilities & CAPABILITY_PULL_RENDERER) && (drCallbacks->capabilities & CAPABILITY_DIRECT_SUBMIT)) { + Limelog("CAPABILITY_PULL_RENDERER and CAPABILITY_DIRECT_SUBMIT cannot be set together\n"); + err = -1; + goto Cleanup; + } + // Replace missing callbacks with placeholders fixupMissingCallbacks(&drCallbacks, &arCallbacks, &clCallbacks); memcpy(&VideoCallbacks, drCallbacks, sizeof(VideoCallbacks)); diff --git a/src/Limelight-internal.h b/src/Limelight-internal.h index 6bae123..eb6649d 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -94,8 +94,8 @@ void requestDecoderRefresh(void); void initializeVideoStream(void); void destroyVideoStream(void); +void notifyKeyFrameReceived(void); int startVideoStream(void* rendererContext, int drFlags); -void submitFrame(PQUEUED_DECODE_UNIT qdu); void stopVideoStream(void); int initializeAudioStream(void); diff --git a/src/Limelight.h b/src/Limelight.h index a94e8a9..5b58a86 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -241,6 +241,12 @@ typedef struct _DECODE_UNIT { // buffer size rather than just assuming it will always be 240. #define CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION 0x10 +// This flag opts the renderer into a pull-based model rather than the default push-based +// callback model. The renderer must invoke the new functions (LiWaitForNextVideoFrame(), +// LiCompleteVideoFrame(), and similar) to receive A/V data. Setting this capability while +// also providing a sample callback is not allowed. +#define CAPABILITY_PULL_RENDERER 0x20 + // If set in the video renderer capabilities field, this macro specifies that the renderer // supports slicing to increase decoding performance. The parameter specifies the desired // number of slices per frame. This capability is only valid on video renderers. @@ -629,6 +635,18 @@ void LiStringifyPortFlags(unsigned int portFlags, const char* separator, char* o #define ML_TEST_RESULT_INCONCLUSIVE 0xFFFFFFFF unsigned int LiTestClientConnectivity(const char* testServer, unsigned short referencePort, unsigned int testPortFlags); +// This family of functions can be used for pull-based video renderers that opt to manage a decoding/rendering +// thread themselves. After successfully calling the WaitFor/Poll variants that dequeue the video frame, you +// must call LiCompleteVideoFrame() to notify that processing is completed. The same DR_* status values +// from drSubmitDecodeUnit() must be passed to LiCompleteVideoFrame() as the drStatus argument. +// +// In order to safely use these functions, you must set CAPABILITY_PULL_RENDERER on the video decoder. +typedef void* VIDEO_FRAME_HANDLE; +bool LiWaitForNextVideoFrame(VIDEO_FRAME_HANDLE* frameHandle, PDECODE_UNIT* decodeUnit); +bool LiPollNextVideoFrame(VIDEO_FRAME_HANDLE* frameHandle, PDECODE_UNIT* decodeUnit); +bool LiPeekNextVideoFrame(PDECODE_UNIT* decodeUnit); +void LiCompleteVideoFrame(VIDEO_FRAME_HANDLE handle, int drStatus); + #ifdef __cplusplus } #endif diff --git a/src/Video.h b/src/Video.h index 95d8fbb..68c5e3c 100644 --- a/src/Video.h +++ b/src/Video.h @@ -7,9 +7,6 @@ typedef struct _QUEUED_DECODE_UNIT { LINKED_BLOCKING_QUEUE_ENTRY entry; } QUEUED_DECODE_UNIT, *PQUEUED_DECODE_UNIT; -void completeQueuedDecodeUnit(PQUEUED_DECODE_UNIT qdu, int drStatus); -bool getNextQueuedDecodeUnit(PQUEUED_DECODE_UNIT* qdu); - #pragma pack(push, 1) #define FLAG_CONTAINS_PIC_DATA 0x1 diff --git a/src/VideoDepacketizer.c b/src/VideoDepacketizer.c index 04846e8..7fbdabb 100644 --- a/src/VideoDepacketizer.c +++ b/src/VideoDepacketizer.c @@ -117,7 +117,7 @@ static void freeDecodeUnitList(PLINKED_BLOCKING_QUEUE_ENTRY entry) { nextEntry = entry->flink; // Complete this with a failure status - completeQueuedDecodeUnit((PQUEUED_DECODE_UNIT)entry->data, DR_CLEANUP); + LiCompleteVideoFrame(entry->data, DR_CLEANUP); entry = nextEntry; } @@ -181,16 +181,53 @@ static bool getSpecialSeq(PBUFFER_DESC current, PBUFFER_DESC candidate) { return false; } -// Get the first decode unit available -bool getNextQueuedDecodeUnit(PQUEUED_DECODE_UNIT* qdu) { - int err = LbqWaitForQueueElement(&decodeUnitQueue, (void**)qdu); - return (err == LBQ_SUCCESS); +bool LiWaitForNextVideoFrame(VIDEO_FRAME_HANDLE* frameHandle, PDECODE_UNIT* decodeUnit) { + PQUEUED_DECODE_UNIT qdu; + + int err = LbqWaitForQueueElement(&decodeUnitQueue, (void**)&qdu); + if (err != LBQ_SUCCESS) { + return false; + } + + *frameHandle = qdu; + *decodeUnit = &qdu->decodeUnit; + return true; +} + +bool LiPollNextVideoFrame(VIDEO_FRAME_HANDLE* frameHandle, PDECODE_UNIT* decodeUnit) { + PQUEUED_DECODE_UNIT qdu; + + int err = LbqPollQueueElement(&decodeUnitQueue, (void**)&qdu); + if (err != LBQ_SUCCESS) { + return false; + } + + *frameHandle = qdu; + *decodeUnit = &qdu->decodeUnit; + return true; +} + +bool LiPeekNextVideoFrame(PDECODE_UNIT* decodeUnit) { + PQUEUED_DECODE_UNIT qdu; + + int err = LbqPeekQueueElement(&decodeUnitQueue, (void**)&qdu); + if (err != LBQ_SUCCESS) { + return false; + } + + *decodeUnit = &qdu->decodeUnit; + return true; } // Cleanup a decode unit by freeing the buffer chain and the holder -void completeQueuedDecodeUnit(PQUEUED_DECODE_UNIT qdu, int drStatus) { +void LiCompleteVideoFrame(VIDEO_FRAME_HANDLE handle, int drStatus) { + PQUEUED_DECODE_UNIT qdu = handle; PLENTRY_INTERNAL lastEntry; + if (qdu->decodeUnit.frameType == FRAME_TYPE_IDR) { + notifyKeyFrameReceived(); + } + if (drStatus == DR_NEED_IDR) { Limelog("Requesting IDR frame on behalf of DR\n"); requestDecoderRefresh(); @@ -353,7 +390,7 @@ static void reassembleFrame(int frameNumber) { } else { // Submit the frame to the decoder - submitFrame(qdu); + LiCompleteVideoFrame(qdu, VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit)); } // Notify the control connection diff --git a/src/VideoStream.c b/src/VideoStream.c index ff41fe9..7960705 100644 --- a/src/VideoStream.c +++ b/src/VideoStream.c @@ -154,24 +154,22 @@ 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); - +void notifyKeyFrameReceived(void) { // Remember that we got a full frame successfully receivedFullFrame = true; } // Decoder thread proc static void DecoderThreadProc(void* context) { - PQUEUED_DECODE_UNIT qdu; while (!PltIsThreadInterrupted(&decoderThread)) { - if (!getNextQueuedDecodeUnit(&qdu)) { + VIDEO_FRAME_HANDLE frameHandle; + PDECODE_UNIT decodeUnit; + + if (!LiWaitForNextVideoFrame(&frameHandle, &decodeUnit)) { return; } - submitFrame(qdu); + LiCompleteVideoFrame(frameHandle, VideoCallbacks.submitDecodeUnit(decodeUnit)); } }