Skip to content

Commit

Permalink
Fixes #6974. Play all the way to the end of the video.
Browse files Browse the repository at this point in the history
Playback is allowed to continue even after the decoder thread reaches
the EOF state, until decoded and buffered frames are drained from the
decoder.  This may represent anywhere from 0.25s to 1s of content.

Thanks to Mark Spieth for providing and maintaining the patch.
  • Loading branch information
stichnot committed Jan 7, 2013
1 parent a336ace commit 3112656
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 45 deletions.
4 changes: 2 additions & 2 deletions mythtv/libs/libmythtv/decoderbase.cpp
Expand Up @@ -31,7 +31,7 @@ DecoderBase::DecoderBase(MythPlayer *parent, const ProgramInfo &pginfo)
lastKey(0), keyframedist(-1), indexOffset(0),
trackTotalDuration(false),

ateof(false), exitafterdecoded(false), transcoding(false),
ateof(kEofStateNone), exitafterdecoded(false), transcoding(false),

hasFullPositionMap(false), recordingHasPositionMap(false),
posmapStarted(false), positionMapType(MARK_UNSET),
Expand Down Expand Up @@ -92,7 +92,7 @@ void DecoderBase::Reset(bool reset_video_data, bool seek_reset, bool reset_file)
if (reset_file)
{
waitingForChange = false;
SetEof(false);
SetEofState(kEofStateNone);
}
}

Expand Down
17 changes: 14 additions & 3 deletions mythtv/libs/libmythtv/decoderbase.h
Expand Up @@ -59,6 +59,14 @@ typedef enum AudioTrackType
} AudioTrackType;
QString toString(AudioTrackType type);

// Eof States
typedef enum
{
kEofStateNone, // no eof
kEofStateDelayed, // decoder eof, but let player drain buffered frames
kEofStateImmediate // true eof
} EofState;

class StreamInfo
{
public:
Expand Down Expand Up @@ -115,8 +123,11 @@ class DecoderBase
char testbuf[kDecoderProbeBufferSize],
int testbufsize = kDecoderProbeBufferSize) = 0;

virtual void SetEof(bool eof) { ateof = eof; }
bool GetEof(void) const { return ateof; }
virtual void SetEofState(EofState eof) { ateof = eof; }
virtual void SetEof(bool eof) {
ateof = eof ? kEofStateDelayed : kEofStateNone;
}
EofState GetEof(void) { return ateof; }

void SetSeekSnap(uint64_t snap) { seeksnap = snap; }
uint64_t GetSeekSnap(void) const { return seeksnap; }
Expand Down Expand Up @@ -289,7 +300,7 @@ class DecoderBase
// indicates whether this is the case.
bool trackTotalDuration;

bool ateof;
EofState ateof;
bool exitafterdecoded;
bool transcoding;

Expand Down
2 changes: 1 addition & 1 deletion mythtv/libs/libmythtv/mythcommflagplayer.cpp
Expand Up @@ -108,7 +108,7 @@ bool MythCommFlagPlayer::RebuildSeekTable(
cout << "\r \r" << flush;

int prevperc = -1;
while (!GetEof())
while (GetEof() == kEofStateNone)
{
if (inuse_timer.elapsed() > 2534)
{
Expand Down
75 changes: 43 additions & 32 deletions mythtv/libs/libmythtv/mythplayer.cpp
Expand Up @@ -2078,6 +2078,7 @@ bool MythPlayer::PrebufferEnoughFrames(int min_buffers)
return false;

if (!(min_buffers ? (videoOutput->ValidVideoFrames() >= min_buffers) :
(GetEof() != kEofStateNone) ||
(videoOutput->hasHWAcceleration() ?
videoOutput->EnoughPrebufferedFrames() :
videoOutput->EnoughDecodedFrames())))
Expand Down Expand Up @@ -2490,7 +2491,7 @@ void MythPlayer::SwitchToProgram(void)
{
OpenDummy();
ResetPlaying();
SetEof(false);
SetEof(kEofStateNone);
delete pginfo;
return;
}
Expand All @@ -2512,13 +2513,13 @@ void MythPlayer::SwitchToProgram(void)
QString("(card type: %1).")
.arg(player_ctx->tvchain->GetCardType(newid)));
LOG(VB_GENERAL, LOG_ERR, player_ctx->tvchain->toString());
SetEof(true);
SetEof(kEofStateImmediate);
SetErrored(tr("Error opening switch program buffer"));
delete pginfo;
return;
}

if (GetEof())
if (GetEof() != kEofStateNone)
{
discontinuity = true;
ResetCaptions();
Expand Down Expand Up @@ -2557,11 +2558,11 @@ void MythPlayer::SwitchToProgram(void)
if (IsErrored())
{
LOG(VB_GENERAL, LOG_ERR, LOC + "SwitchToProgram failed.");
SetEof(true);
SetEof(kEofStateDelayed);
return;
}

SetEof(false);
SetEof(kEofStateNone);

// the bitrate is reset by player_ctx->buffer->OpenFile()...
if (decoder)
Expand All @@ -2588,7 +2589,7 @@ void MythPlayer::FileChangedCallback(void)
player_ctx->buffer->Reset(false, true);
else
player_ctx->buffer->Reset(false, true, true);
SetEof(false);
SetEof(kEofStateNone);
Play();

player_ctx->SetPlayerChangingBuffers(false);
Expand Down Expand Up @@ -2629,7 +2630,7 @@ void MythPlayer::JumpToProgram(void)
{
OpenDummy();
ResetPlaying();
SetEof(false);
SetEof(kEofStateNone);
delete pginfo;
inJumpToProgramPause = false;
return;
Expand All @@ -2654,7 +2655,7 @@ void MythPlayer::JumpToProgram(void)
QString("(card type: %1).")
.arg(player_ctx->tvchain->GetCardType(newid)));
LOG(VB_GENERAL, LOG_ERR, player_ctx->tvchain->toString());
SetEof(true);
SetEof(kEofStateImmediate);
SetErrored(tr("Error opening jump program file buffer"));
delete pginfo;
inJumpToProgramPause = false;
Expand All @@ -2680,7 +2681,7 @@ void MythPlayer::JumpToProgram(void)
return;
}

SetEof(false);
SetEof(kEofStateNone);

// the bitrate is reset by player_ctx->buffer->OpenFile()...
player_ctx->buffer->UpdateRawBitrate(decoder->GetRawBitrate());
Expand Down Expand Up @@ -2831,7 +2832,8 @@ void MythPlayer::EventLoop(void)
player_ctx->tvchain->JumpToNext(true, 1);
JumpToProgram();
}
else if ((!allpaused || GetEof()) && player_ctx->tvchain &&
else if ((!allpaused || GetEof() != kEofStateNone) &&
player_ctx->tvchain &&
(decoder && !decoder->GetWaitForChange()))
{
// Switch to the next program in livetv
Expand Down Expand Up @@ -2891,7 +2893,8 @@ void MythPlayer::EventLoop(void)
}

// Handle end of file
if ((GetEof() && !allpaused) ||
EofState _eof = GetEof();
if ((_eof != kEofStateNone && !allpaused) ||
(!GetEditMode() && framesPlayed >= deleteMap.GetLastFrame()))
{
#ifdef USING_MHEG
Expand All @@ -2908,9 +2911,17 @@ void MythPlayer::EventLoop(void)
return;
}

Pause();
SetPlaying(false);
return;
if (_eof != kEofStateDelayed ||
(videoOutput && videoOutput->ValidVideoFrames() < 1))
{
if (_eof == kEofStateDelayed)
LOG(VB_PLAYBACK, LOG_INFO,
QString("waiting for no video frames %1")
.arg(videoOutput->ValidVideoFrames()));
Pause();
SetPlaying(false);
return;
}
}

// Handle rewind
Expand All @@ -2928,7 +2939,7 @@ void MythPlayer::EventLoop(void)
if (fftime > 0)
{
DoFastForward(fftime, kInaccuracyDefault);
if (GetEof())
if (GetEof() != kEofStateNone)
return;
}
}
Expand Down Expand Up @@ -2993,7 +3004,7 @@ void MythPlayer::EventLoop(void)
if (!(endExitPrompt == 1 && !player_ctx->IsPIP() &&
player_ctx->GetState() == kState_WatchingPreRecorded))
{
SetEof(true);
SetEof(kEofStateDelayed);
}
}
else
Expand Down Expand Up @@ -3100,33 +3111,33 @@ void MythPlayer::DecoderPauseCheck(void)
}

//// FIXME - move the eof ownership back into MythPlayer
bool MythPlayer::GetEof(void)
EofState MythPlayer::GetEof(void)
{
if (is_current_thread(playerThread))
return decoder ? decoder->GetEof() : true;
return decoder ? decoder->GetEof() : kEofStateImmediate;

if (!decoder_change_lock.tryLock(50))
return false;
return kEofStateNone;

bool eof = decoder ? decoder->GetEof() : true;
EofState eof = decoder ? decoder->GetEof() : kEofStateImmediate;
decoder_change_lock.unlock();
return eof;
}

void MythPlayer::SetEof(bool eof)
void MythPlayer::SetEof(EofState eof)
{
if (is_current_thread(playerThread))
{
if (decoder)
decoder->SetEof(eof);
decoder->SetEofState(eof);
return;
}

if (!decoder_change_lock.tryLock(50))
return;

if (decoder)
decoder->SetEof(eof);
decoder->SetEofState(eof);
decoder_change_lock.unlock();
}
//// FIXME end
Expand Down Expand Up @@ -3175,8 +3186,8 @@ void MythPlayer::DecoderLoop(bool pause)
decoder_change_lock.unlock();
}

bool obey_eof = GetEof() &&
!(GetEof() && player_ctx->tvchain && !allpaused);
bool obey_eof = (GetEof() != kEofStateNone) &&
!(player_ctx->tvchain && !allpaused);
if (isDummy || ((decoderPaused || ffrew_skip == 0 || obey_eof) &&
!decodeOneFrame))
{
Expand Down Expand Up @@ -3766,7 +3777,7 @@ void MythPlayer::WaitForSeek(uint64_t frame, uint64_t seeksnap_wanted)
if (!decoder)
return;

SetEof(false);
SetEof(kEofStateNone);
decoder->SetSeekSnap(seeksnap_wanted);

bool islivetvcur = (livetv && player_ctx->tvchain &&
Expand Down Expand Up @@ -4511,7 +4522,7 @@ bool MythPlayer::TranscodeGetNextFrame(
if (!decoder->GetFrame(kDecodeAV))
return false;

if (GetEof())
if (GetEof() != kEofStateNone)
return false;

if (honorCutList && !deleteMap.IsEmpty())
Expand All @@ -4536,7 +4547,7 @@ bool MythPlayer::TranscodeGetNextFrame(
did_ff = 1;
}
}
if (GetEof())
if (GetEof() != kEofStateNone)
return false;
is_key = decoder->IsLastFrameKey();

Expand Down Expand Up @@ -4999,7 +5010,7 @@ bool MythPlayer::SetStream(const QString &stream)
player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
{
// Restore livetv
SetEof(true);
SetEof(kEofStateDelayed);
player_ctx->tvchain->JumpToNext(false, 1);
player_ctx->tvchain->JumpToNext(true, 1);
}
Expand Down Expand Up @@ -5029,7 +5040,7 @@ void MythPlayer::JumpToStream(const QString &stream)
if (!player_ctx->buffer->IsOpen())
{
LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream buffer OpenFile failed");
SetEof(true);
SetEof(kEofStateImmediate);
SetErrored(QObject::tr("Error opening remote stream buffer"));
return;
}
Expand All @@ -5042,7 +5053,7 @@ void MythPlayer::JumpToStream(const QString &stream)
if (OpenFile(120) < 0) // 120 retries ~= 60 seconds
{
LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream OpenFile failed.");
SetEof(true);
SetEof(kEofStateImmediate);
SetErrored(QObject::tr("Error opening remote stream"));
return;
}
Expand All @@ -5058,7 +5069,7 @@ void MythPlayer::JumpToStream(const QString &stream)
.arg(player_ctx->buffer->GetRealFileSize()).arg(decoder->GetRawBitrate())
.arg(totalLength).arg(totalFrames).arg(decoder->GetFPS()) );

SetEof(false);
SetEof(kEofStateNone);

// the bitrate is reset by player_ctx->buffer->OpenFile()...
player_ctx->buffer->UpdateRawBitrate(decoder->GetRawBitrate());
Expand Down
4 changes: 2 additions & 2 deletions mythtv/libs/libmythtv/mythplayer.h
Expand Up @@ -142,7 +142,7 @@ class MTV_PUBLIC MythPlayer
void SetLength(int len) { totalLength = len; }
void SetFramesPlayed(uint64_t played);
void SetVideoFilters(const QString &override);
void SetEof(bool eof);
void SetEof(EofState eof);
void SetPIPActive(bool is_active) { pip_active = is_active; }
void SetPIPVisible(bool is_visible) { pip_visible = is_visible; }

Expand Down Expand Up @@ -198,7 +198,7 @@ class MTV_PUBLIC MythPlayer
bool IsPaused(void) const { return allpaused; }
bool GetRawAudioState(void) const;
bool GetLimitKeyRepeat(void) const { return limitKeyRepeat; }
bool GetEof(void);
EofState GetEof(void);
bool IsErrored(void) const;
bool IsPlaying(uint wait_ms = 0, bool wait_for = true) const;
bool AtNormalSpeed(void) const { return next_normal_speed; }
Expand Down
2 changes: 1 addition & 1 deletion mythtv/programs/mythavtest/main.cpp
Expand Up @@ -107,7 +107,7 @@ class VideoPerformanceTest
break;
}

if (mp->GetEof())
if (mp->GetEof() != kEofStateNone)
{
LOG(VB_GENERAL, LOG_INFO, "End of file.");
break;
Expand Down
2 changes: 1 addition & 1 deletion mythtv/programs/mythcommflag/ClassicCommDetector.cpp
Expand Up @@ -404,7 +404,7 @@ bool ClassicCommDetector::go()

player->ResetTotalDuration();

while (!player->GetEof())
while (player->GetEof() == kEofStateNone)
{
struct timeval startTime;
if (stillRecording)
Expand Down
2 changes: 1 addition & 1 deletion mythtv/programs/mythcommflag/ClassicLogoDetector.cpp
Expand Up @@ -113,7 +113,7 @@ bool ClassicLogoDetector::searchForLogo(MythPlayer* player)

loops = 0;
seekFrame = commDetector->preRoll + seekIncrement;
while(loops < maxLoops && !player->GetEof())
while (loops < maxLoops && player->GetEof() == kEofStateNone)
{
VideoFrame* vf = player->GetRawVideoFrame(seekFrame);

Expand Down
2 changes: 1 addition & 1 deletion mythtv/programs/mythcommflag/CommDetector2.cpp
Expand Up @@ -591,7 +591,7 @@ bool CommDetector2::go(void)
clock.start();
passTime.start();
memset(&getframetime, 0, sizeof(getframetime));
while (!(*currentPass).empty() && !player->GetEof())
while (!(*currentPass).empty() && player->GetEof() == kEofStateNone)
{
struct timeval start, end, elapsedtv;

Expand Down
2 changes: 1 addition & 1 deletion mythtv/programs/mythcommflag/PrePostRollFlagger.cpp
Expand Up @@ -235,7 +235,7 @@ long long PrePostRollFlagger::findBreakInrange(long long startFrame,

long long foundFrame = 0;

while (!player->GetEof())
while (player->GetEof() == kEofStateNone)
{
struct timeval startTime;
if (stillRecording)
Expand Down

0 comments on commit 3112656

Please sign in to comment.