Skip to content

Commit 925b630

Browse files
committed
device: Introduce Drain()
- force draining the videoReassemblyBuffer - pop the drm buffer from the queue AFTER setting the new PTS - video buffers are empty, when the drm buffer queue is empty AND when the current drm buffer was displayed long enough in trickspeed mode (though it is displayed one more time due to m_displayOneFrameThenPause
1 parent e275c9d commit 925b630

9 files changed

Lines changed: 66 additions & 19 deletions

File tree

audio.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,8 @@ bool cSoftHdAudio::CyclicCall(void)
10571057
if (err < 0) {
10581058
if (m_alsa.HandleError(err)) {
10591059
std::lock_guard<std::mutex> lock(m_queueMutex);
1060-
m_eventQueue.push_back(BufferUnderrunEvent{AUDIO});
1060+
if (!m_pDevice->IsDraining())
1061+
m_eventQueue.push_back(BufferUnderrunEvent{AUDIO});
10611062
}
10621063
return false;
10631064
} else if (err == 0) {

audio.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class cSoftHdAudio : public cThread {
6060
void Filter(AVFrame *, AVCodecContext *);
6161
void EnqueueSpdif(const uint16_t *, int, int64_t pts);
6262
bool IsBufferFull(void) { return m_pRingbuffer.FreeBytes() <= AUDIO_MIN_BUFFER_FREE; };
63+
bool IsBufferEmpty(void) { return m_pRingbuffer.UsedBytes() == 0 && m_alsa.GetHwDelayFrames() == 0; };
6364

6465
void FlushBuffers(void);
6566
int GetUsedRingbufferBytes(void);

softhddevice.cpp

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,8 @@ bool cSoftHdDevice::SetPlayMode(ePlayMode play_mode)
649649
m_externalPlayerActive = false;
650650
}
651651

652+
m_draining = false;
653+
652654
switch (play_mode) {
653655
case pmNone:
654656
OnEventReceived(StopEvent{});
@@ -810,6 +812,8 @@ void cSoftHdDevice::Clear(void)
810812
m_pAudio->ResetHwDelayBaseline();
811813
FlushAudio();
812814

815+
m_draining = false;
816+
813817
SetState(BUFFERING);
814818

815819
m_pRender->Resume();
@@ -888,7 +892,7 @@ void cSoftHdDevice::HandleStillPicture(const uchar *data, int size)
888892

889893
m_pVideoStream->PushAvPacket(m_videoReassemblyBuffer.PopAvPacket());
890894
m_pVideoStream->ResetInputPts(); // stillpicture shouldn't trigger having video data
891-
m_pVideoStream->Flush();
895+
m_pVideoStream->Drain();
892896
}
893897

894898
/**
@@ -917,26 +921,41 @@ bool cSoftHdDevice::Poll(__attribute__ ((unused)) cPoller & poller, int timeoutM
917921
return false;
918922
}
919923

924+
#if APIVERSNUM >= 30014
920925
/**
921-
* Flush the device output buffers.
926+
* Force a decoder drain and return true, if all buffers have been played out
922927
*
923-
* @param timeoutMs timeout in ms to become ready
928+
* @return true, if the buffers are empty, false otherwise
924929
*/
925-
bool cSoftHdDevice::Flush(int timeoutMs)
930+
bool cSoftHdDevice::Drain(void)
926931
{
927932
if (IsDetached())
928933
return true;
929934

930-
LOGDEBUG("device: %s: timeout %d ms", __FUNCTION__, timeoutMs);
931-
if (m_pVideoStream->GetAvPacketsFilled()) {
932-
if (timeoutMs) { // let display thread work
933-
usleep(timeoutMs * 1000);
934-
}
935-
return !m_pVideoStream->GetAvPacketsFilled();
935+
// enter drain mode once
936+
if (!m_draining) {
937+
LOGDEBUG("device: %s: start draining", __FUNCTION__);
938+
m_draining = true;
939+
if (!m_videoReassemblyBuffer.IsEmpty())
940+
m_pVideoStream->PushAvPacket(m_videoReassemblyBuffer.PopAvPacket());
941+
m_pVideoStream->Drain();
936942
}
937943

944+
const auto buffersEmpty = [&]() {
945+
return m_playbackMode == AUDIO_ONLY
946+
? m_pAudio->IsBufferEmpty()
947+
: m_pVideoStream->BuffersEmpty();
948+
};
949+
950+
if (!buffersEmpty())
951+
return false;
952+
953+
LOGDEBUG("device: %s: drained, buffers are empty", __FUNCTION__);
954+
m_draining = false;
955+
938956
return true;
939957
}
958+
#endif
940959

941960
/**
942961
* Sets the video display format

softhddevice.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ class cSoftHdDevice : public cDevice, public IEventReceiver, public cStatus {
168168
virtual void Freeze(void);
169169
virtual void StillPicture(const uchar *, int);
170170
virtual bool Poll(cPoller &, int = 0);
171-
virtual bool Flush(int = 0);
172-
171+
#if APIVERSNUM >= 30014
172+
virtual bool Drain(void);
173+
#endif
173174
// Image Grab facilities
174175
virtual uchar *GrabImage(int &, bool, int, int, int);
175176

@@ -246,6 +247,7 @@ class cSoftHdDevice : public cDevice, public IEventReceiver, public cStatus {
246247
void ResetOsdProvider(void) { m_pOsdProvider = nullptr; }
247248
bool IsOsdProviderSet(void) const { return m_pOsdProvider != nullptr; }
248249
void SetStartDetached(void) { m_forceDetached = true; };
250+
bool IsDraining(void) { return m_draining; };
249251

250252
bool IsBufferingThresholdReached(void);
251253
bool IsVideoOnlyPlayback(void) { return m_playbackMode == VIDEO_ONLY; };
@@ -274,6 +276,7 @@ class cSoftHdDevice : public cDevice, public IEventReceiver, public cStatus {
274276
static constexpr int MIN_BUFFER_FILL_LEVEL_THRESHOLD_MS = 450; ///< min buffering threshold in ms
275277

276278
bool m_initialized = false; ///< true, if the plugin had a successful Initialize()
279+
std::atomic<bool> m_draining = false; ///< true, if the device is in draining mode (waiting for empty buffers)
277280
std::atomic<State> m_state = DETACHED; ///< current plugin state, normal plugin start sets detached state
278281
std::mutex m_eventMutex; ///< mutex to protect event queue
279282
bool m_needsMakePrimary = false; ///< true, if device should be made a primary device after attach

videofilter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class cVideoFilter : public cThread {
4545
void Stop(void);
4646
void PushFrame(AVFrame *);
4747
bool IsInputBufferFull(void) { return m_frames.IsFull(); };
48+
bool IsInputBufferEmpty(void) { return m_frames.IsEmpty(); };
4849
int GetNumFramesToFilter(void) { return m_numFramesToFilter; };
4950

5051
private:

videorender.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,13 +725,14 @@ bool cVideoRender::DisplayFrame(void)
725725
m_pDevice->IsVideoOnlyPlayback() ||
726726
IsTrickSpeed() ||
727727
IsStillpicture() ||
728+
m_pDevice->IsDraining() ||
728729
m_schedulePlaybackStartAtPtsMs != AV_NOPTS_VALUE;
729730
if (m_pDevice->VideoStream()->GetAvPacketsFilled() == 0 && !skipBufferUnderrunCheck)
730731
m_eventQueue.push_back(BufferUnderrunEvent{VIDEO});
731732

732733
cDrmBuffer *drmBuffer = nullptr;
733734
if ((!m_videoPlaybackPaused || m_schedulePlaybackStartAtPtsMs != AV_NOPTS_VALUE) && m_framePresentationCounter == 0 && frameTick)
734-
drmBuffer = m_drmBufferQueue.Pop();
735+
drmBuffer = m_drmBufferQueue.Peek();
735736

736737
cDrmBuffer *pipBuffer = m_pipDrmBufferQueue.Pop();
737738

@@ -749,6 +750,7 @@ bool cVideoRender::DisplayFrame(void)
749750
// check if playback shall start
750751
if (PtsToMs(drmBuffer->frame->pts) < m_schedulePlaybackStartAtPtsMs) {
751752
drmBuffer->PresentationFinished();
753+
m_drmBufferQueue.Pop();
752754
return true;
753755
} else {
754756
m_schedulePlaybackStartAtPtsMs = AV_NOPTS_VALUE;
@@ -763,6 +765,7 @@ bool cVideoRender::DisplayFrame(void)
763765
drmBuffer->PresentationFinished();
764766
if (pipBuffer)
765767
pipBuffer->PresentationFinished();
768+
m_drmBufferQueue.Pop();
766769
return true;
767770
}
768771

@@ -790,7 +793,9 @@ bool cVideoRender::DisplayFrame(void)
790793

791794
m_lastFrameWasDropped = false;
792795
m_pCurrentlyDisplayed = drmBuffer;
793-
} else if (m_pCurrentlyDisplayed && !m_drmBufferQueue.IsEmpty() && !m_videoPlaybackPaused) {
796+
797+
m_drmBufferQueue.Pop();
798+
} else if (m_pCurrentlyDisplayed && !m_videoPlaybackPaused) {
794799
// display the current frame again in trick speed mode or for A/V syncing
795800
pageFlipDone = PageFlip(m_pCurrentlyDisplayed, pipBuffer);
796801
} else if ((m_pBufOsd && m_pBufOsd->IsDirty()) || pipBuffer) {

videorender.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ class cVideoRender : public cThread {
242242
void ClearPipDecoderToDisplayQueue(void);
243243
void SetPipSize(bool);
244244

245+
bool PresentationPending(void) { return m_framePresentationCounter != 0 || !m_drmBufferQueue.IsEmpty(); };
246+
245247
protected:
246248
virtual void Action(void);
247249

videostream.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ cVideoStream::~cVideoStream(void)
7373
}
7474

7575
/**
76-
* Flushes the video stream by finalizing any pending data.
76+
* Drains the video stream by finalizing any pending data.
7777
*
7878
* This function completes processing of any remaining PES fragments in the fragmentation
7979
* buffer, then pushes a nullptr packet to the queue to signal a flush operation to the decoder.
8080
*/
81-
void cVideoStream::Flush(void)
81+
void cVideoStream::Drain(void)
8282
{
8383
m_packets.Push(nullptr);
8484
}
@@ -98,6 +98,8 @@ bool cVideoStream::PushAvPacket(AVPacket *avpkt)
9898
if (avpkt->pts != AV_NOPTS_VALUE)
9999
m_inputPts = avpkt->pts;
100100

101+
LOGDEBUG2(L_PACKET, "videostream: %s: push PTS %s", __FUNCTION__, Timestamp2String(avpkt->pts, 90));
102+
101103
return m_packets.Push(avpkt);
102104
}
103105

@@ -409,14 +411,18 @@ void cVideoStream::DecodeInput(void)
409411
// send packet to decoder
410412
} else if (!PacketDropNeeded(avpkt)) {
411413
ret = m_pDecoder->SendPacket(avpkt);
414+
m_isResend = false;
412415

413416
if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
414417
avpkt = m_packets.Pop();
415418
if (avpkt && avpkt->pts != AV_NOPTS_VALUE)
416419
m_lastDecodedPts = avpkt->pts;
417420
av_packet_free(&avpkt);
418-
m_isResend = false;
421+
// a repeated decoder drain was requested, drop the nullptr-packet from the queue
422+
} else if (ret == AVERROR_EOF && !avpkt) {
423+
m_packets.Pop();
419424
} else {
425+
// PacketDropNeeded() is skipped next time, because this packet needs to be resent to the decoder
420426
m_isResend = true;
421427
}
422428

@@ -650,3 +656,11 @@ void cVideoStream::RenderFrame(AVFrame * frame)
650656
m_frameOutput(frame);
651657
}
652658
}
659+
660+
/**
661+
* Return true, if the input AVPacket, videofilter and drm output buffer queues are empty.
662+
*/
663+
bool cVideoStream::BuffersEmpty(void)
664+
{
665+
return m_packets.IsEmpty() && !m_pRender->PresentationPending() && m_videoFilter.IsInputBufferEmpty();
666+
}

videostream.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class cVideoStream : public cThread {
5757
void FlushDecoder(void);
5858
void CloseDecoder(void);
5959
bool PushAvPacket(AVPacket *avpkt);
60-
void Flush(void);
60+
void Drain(void);
6161

6262
// decoding thread
6363
void Stop(void);
@@ -69,6 +69,7 @@ class cVideoStream : public cThread {
6969
void StartDecoder();
7070
size_t GetAvPacketsFilled(void) { return m_packets.Size(); };
7171
bool IsInputBufferFull(void) { return m_packets.Size() >= VIDEO_PACKET_MAX; };
72+
bool BuffersEmpty(void);
7273
enum AVCodecID GetCodecId(void) { return m_codecId; };
7374
void ResetTrickSpeedFramesSentCounter(void) { m_sentTrickPkts = 0; };
7475
bool HasInputPts(void) { return m_inputPts != AV_NOPTS_VALUE; }

0 commit comments

Comments
 (0)