Skip to content

Commit

Permalink
Improved interlaced detection
Browse files Browse the repository at this point in the history
- the basic premise here is that we don't want to setup deinterlacers
unnecessarily (to save resources and avoid confusion in the logs etc)
and that FFmpeg is pretty good these days at getting interlaced flags
correct

- so don't use detection based on size/frame rate on stream changes, set
the initial state as interlaced but with the scan tracker as progressive
(if that makes sense). The scan is immediately corrected on detection of
a progressive frame and no deinterlacers are created. We set to
interlaced only to account for those cases where the frame rate is
detected incorrectly on startup (usually 29.97 as 59.98) and we
initially think the file is progressive.
- testing with my 'test suite' of curious files and formats, this gets it
correct for all but 1 of about 120 files - which is better than NVDECs
detection. The one misdetected file is clearly an interlaced NTSC stream
that is not flagged as such - all playes get it wrong. NVDEC doubles the
frame rate but doesn't deinterlace it.
  • Loading branch information
mark-kendall committed Jun 9, 2019
1 parent 8c9ff99 commit 9609a51
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 22 deletions.
32 changes: 19 additions & 13 deletions mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
Expand Up @@ -1429,8 +1429,10 @@ float AvFormatDecoder::normalized_fps(AVStream *stream, AVCodecContext *enc)
fps = codec_fps;
else if (container_fps < 121.0 && container_fps > 3.0)
fps = container_fps;
// certain H.264 interlaced streams are detected at 2x using estimated (i.e. wrong)
else if (estimated_fps < 121.0 && estimated_fps > 3.0)
fps = estimated_fps;
// but average is less reliable as it does not account for issues like repeat frames
else if (avg_fps < 121.0 && avg_fps > 3.0)
fps = avg_fps;
else
Expand Down Expand Up @@ -3268,33 +3270,35 @@ void AvFormatDecoder::MpegPreProcessPkt(AVStream *stream, AVPacket *pkt)
SequenceHeader *seq = reinterpret_cast<SequenceHeader*>(
const_cast<uint8_t*>(bufptr));

uint width = seq->width() >> context->lowres;
uint height = seq->height() >> context->lowres;
int width = static_cast<int>(seq->width()) >> context->lowres;
int height = static_cast<int>(seq->height()) >> context->lowres;
m_current_aspect = seq->aspect(context->codec_id ==
AV_CODEC_ID_MPEG1VIDEO);
if (stream->sample_aspect_ratio.num)
m_current_aspect = av_q2d(stream->sample_aspect_ratio) *
width / height;
m_current_aspect = static_cast<float>(av_q2d(stream->sample_aspect_ratio) *
width / height);
if (aspect_override > 0.0F)
m_current_aspect = aspect_override;
float seqFPS = seq->fps();

bool changed =
(seqFPS > static_cast<float>(m_fps)+0.01F) ||
(seqFPS < static_cast<float>(m_fps)-0.01F);
changed |= (width != (uint)m_current_width );
changed |= (height != (uint)m_current_height);
changed |= (width != m_current_width );
changed |= (height != m_current_height);

if (changed)
{
if (m_private_dec)
m_private_dec->Reset();

// N.B. we now set the default scan to kScan_Ignore as interlaced detection based on frame
// size and rate is extremely error prone and FFmpeg gets it right far more often.
// as for H.264, if a decoder deinterlacer is in operation - the stream must be progressive
bool doublerate = false;
bool decoderdeint = m_mythcodecctx->IsDeinterlacing(doublerate);
m_parent->SetVideoParams(width, height, seqFPS, m_current_aspect,
decoderdeint ? kScan_Progressive : kScan_Detect);
m_parent->SetVideoParams(width, height, static_cast<double>(seqFPS), m_current_aspect,
decoderdeint ? kScan_Progressive : kScan_Ignore);

m_current_width = width;
m_current_height = height;
Expand All @@ -3314,9 +3318,8 @@ void AvFormatDecoder::MpegPreProcessPkt(AVStream *stream, AVPacket *pkt)
float avFPS = normalized_fps(stream, context);
if ((seqFPS > avFPS+0.01F) || (seqFPS < avFPS-0.01F))
{
LOG(VB_PLAYBACK, LOG_INFO, LOC +
QString("avFPS(%1) != seqFPS(%2)")
.arg(avFPS).arg(seqFPS));
LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("avFPS(%1) != seqFPS(%2)")
.arg(static_cast<double>(avFPS)).arg(static_cast<double>(seqFPS)));
}
}

Expand Down Expand Up @@ -3365,7 +3368,7 @@ int AvFormatDecoder::H264PreProcessPkt(AVStream *stream, AVPacket *pkt)

while (buf < buf_end)
{
buf += m_h264_parser->addBytes(buf, buf_end - buf, 0);
buf += m_h264_parser->addBytes(buf, static_cast<unsigned int>(buf_end - buf), 0);

if (m_h264_parser->stateChanged())
{
Expand Down Expand Up @@ -3401,10 +3404,13 @@ int AvFormatDecoder::H264PreProcessPkt(AVStream *stream, AVPacket *pkt)
if (m_private_dec)
m_private_dec->Reset();

// N.B. we now set the default scan to kScan_Ignore as interlaced detection based on frame
// size and rate is extremely error prone and FFmpeg gets it right far more often.
// N.B. if a decoder deinterlacer is in use - the stream must be progressive
bool doublerate = false;
bool decoderdeint = m_mythcodecctx->IsDeinterlacing(doublerate);
m_parent->SetVideoParams(width, height, seqFPS, m_current_aspect, decoderdeint ? kScan_Progressive : kScan_Detect);
m_parent->SetVideoParams(width, height, seqFPS, m_current_aspect,
decoderdeint ? kScan_Progressive : kScan_Ignore);

m_current_width = width;
m_current_height = height;
Expand Down
2 changes: 1 addition & 1 deletion mythtv/libs/libmythtv/mythmediacodeccontext.cpp
Expand Up @@ -120,7 +120,7 @@ void MythMediaCodecContext::PostProcessFrame(AVCodecContext*, VideoFrame* Frame)
if (!Frame)
return;

Frame->deinterlace_inuse = Frame->interlaced_frame ? (DEINT_BASIC | DEINT_DRIVER) : DEINT_NONE;
Frame->deinterlace_inuse = DEINT_BASIC | DEINT_DRIVER;
Frame->deinterlace_inuse2x = 0;
Frame->interlaced_frame = 0;
Frame->interlaced_reversed = 0;
Expand Down
8 changes: 8 additions & 0 deletions mythtv/libs/libmythtv/mythnvdeccontext.cpp
Expand Up @@ -230,6 +230,14 @@ void MythNVDECContext::SetDeinterlacing(AVCodecContext *Context,
if (!Context)
return;

// Don't enable for anything that cannot be interlaced
// We could use frame rate here but initial frame rate detection is not always accurate
// and we lose little by enabling deinterlacing. NVDEC will not deinterlace a
// progressive stream and any CUDA capable video card has memory to spare
// (assuming it even sets up deinterlacing for a progressive stream)
if (Context->height == 720) // 720P
return;

MythDeintType deinterlacer = DEINT_NONE;
MythDeintType singlepref = DEINT_HIGH | DEINT_DRIVER;
MythDeintType doublepref = DoubleRate ? DEINT_HIGH | DEINT_DRIVER : DEINT_NONE;
Expand Down
20 changes: 12 additions & 8 deletions mythtv/libs/libmythtv/mythplayer.cpp
Expand Up @@ -644,7 +644,7 @@ FrameScanType MythPlayer::detectInterlace(FrameScanType newScan,
{
QString dbg = QString("detectInterlace(") + toQString(newScan) +
QString(", ") + toQString(scan) + QString(", ") +
QString("%1").arg(fps) + QString(", ") +
QString("%1").arg(static_cast<double>(fps)) + QString(", ") +
QString("%1").arg(video_height) + QString(") ->");

if (kScan_Ignore != newScan || kScan_Detect == scan)
Expand All @@ -662,7 +662,7 @@ FrameScanType MythPlayer::detectInterlace(FrameScanType newScan,
scan = newScan;
};

LOG(VB_PLAYBACK, LOG_INFO, LOC + dbg+toQString(scan));
LOG(VB_PLAYBACK, LOG_INFO, LOC + dbg +toQString(scan));

return scan;
}
Expand All @@ -682,7 +682,7 @@ void MythPlayer::AutoDeint(VideoFrame *frame, bool allow_lock)
if (m_scan_tracker < 0)
{
LOG(VB_PLAYBACK, LOG_INFO, LOC +
QString("interlaced frame seen after %1 progressive frames")
QString("Interlaced frame seen after %1 progressive frames")
.arg(abs(m_scan_tracker)));
m_scan_tracker = 2;
if (allow_lock)
Expand All @@ -699,7 +699,7 @@ void MythPlayer::AutoDeint(VideoFrame *frame, bool allow_lock)
if (m_scan_tracker > 0)
{
LOG(VB_PLAYBACK, LOG_INFO, LOC +
QString("progressive frame seen after %1 interlaced frames")
QString("Progressive frame seen after %1 interlaced frames")
.arg(m_scan_tracker));
m_scan_tracker = 0;
}
Expand Down Expand Up @@ -2661,14 +2661,18 @@ void MythPlayer::VideoStart(void)
int fr_int = (1000000.0 / video_frame_rate / static_cast<double>(temp_speed));
int rf_int = MythDisplay::GetDisplayInfo(fr_int).Rate();

// Default to Interlaced playback to allocate the deinterlacer structures
// Default to interlaced playback but set the tracker to progressive
// Enable autodetection of interlaced/progressive from video stream
// And initialoze m_scan_tracker to 2 which will immediately switch to
// progressive if the first frame is progressive in AutoDeint().
// Previously we set to interlaced and the scan tracker to 2 but this
// mis-'detected' a number of streams as interlaced when they are progressive.
// This significantly reduces the number of errors and also ensures we do not
// needlessly setup deinterlacers - which may consume significant resources.
// We set to interlaced for those streams whose frame rate is initially detected
// as e.g. 59.9 when it is actually 29.97 interlaced.
m_scan = kScan_Interlaced;
m_scan_locked = false;
m_double_framerate = false;
m_scan_tracker = 2;
m_scan_tracker = -2;

if (player_ctx->IsPIP() && FlagIsSet(kVideoIsNull))
{
Expand Down

0 comments on commit 9609a51

Please sign in to comment.