Skip to content

Commit

Permalink
AvFormatDecoder: Refactor OpenFile for better live tv support
Browse files Browse the repository at this point in the history
- default, non-livetv behaviour should be largely unchanged
- livetv startup in testing is now faster and more reliable

- codec initialisation is now nested in 2 loops
- the inner loop attempts to open the input stream for up to 5seconds,
resetting the ringbuffer after each attempt to ensure all available data
is used for the scan.
- when complete, the second/outer loop will try to ensure the necessary
stream parameters are available - again forcing a retry when it fails.
The AVFormatContext is closed before trying again to avoid errors
regarding re-opening codecs.
- certain audio parameter checks are disabled as they often fail
(problem here is often the Audio Description Channel) but subsequent
initialisation is fine.
  • Loading branch information
mark-kendall committed Oct 11, 2019
1 parent 961704c commit 76bca2a
Showing 1 changed file with 134 additions and 155 deletions.
289 changes: 134 additions & 155 deletions mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,69 +157,78 @@ int get_avf_buffer(struct AVCodecContext *c, AVFrame *pic, int flags);
int get_avf_buffer_dxva2(struct AVCodecContext *c, AVFrame *pic, int flags);
#endif

static int determinable_frame_size(struct AVCodecContext *avctx)
{
if (/*avctx->codec_id == AV_CODEC_ID_AAC ||*/
avctx->codec_id == AV_CODEC_ID_MP1 ||
avctx->codec_id == AV_CODEC_ID_MP2 ||
avctx->codec_id == AV_CODEC_ID_MP3/* ||
avctx->codec_id == AV_CODEC_ID_CELT*/)
return 1;
return 0;
}
// currently unused
//static int determinable_frame_size(struct AVCodecContext *avctx)
//{
// if (/*avctx->codec_id == AV_CODEC_ID_AAC ||*/
// avctx->codec_id == AV_CODEC_ID_MP1 ||
// avctx->codec_id == AV_CODEC_ID_MP2 ||
// avctx->codec_id == AV_CODEC_ID_MP3/* ||
// avctx->codec_id == AV_CODEC_ID_CELT*/)
// return 1;
// return 0;
//}

#define FAIL(errmsg) do { \
LOG(VB_PLAYBACK, LOG_INFO, LOC + (errmsg)); \
return false; \
} while (false)

static int has_codec_parameters(AVStream *st)
static bool StreamHasRequiredParameters(AVStream *Stream)
{
AVCodecContext *avctx = nullptr;

#define FAIL(errmsg) do { \
LOG(VB_PLAYBACK, LOG_DEBUG, LOC + (errmsg)); \
return 0; \
} while (false)

switch (st->codecpar->codec_type)
switch (Stream->codecpar->codec_type)
{
case AVMEDIA_TYPE_AUDIO:
avctx = gCodecMap->getCodecContext(st);
if (!avctx)
FAIL("No codec for audio stream");
if (!avctx->frame_size && determinable_frame_size(avctx))
FAIL("unspecified frame size");
if (avctx->sample_fmt == AV_SAMPLE_FMT_NONE)
FAIL("unspecified sample format");
if (!avctx->sample_rate)
FAIL("unspecified sample rate");
if (!avctx->channels)
FAIL("unspecified number of channels");
if (!st->nb_decoded_frames && avctx->codec_id == AV_CODEC_ID_DTS)
FAIL("no decodable DTS frames");
break;
// We fail on video first as this is generally the most serious error
// and if we have video, we usually have everything else
case AVMEDIA_TYPE_VIDEO:
avctx = gCodecMap->getCodecContext(st);
avctx = gCodecMap->getCodecContext(Stream);
if (!avctx)
FAIL("No codec for video stream");
if (!avctx->width)
FAIL("unspecified size");
if (avctx->pix_fmt == AV_PIX_FMT_NONE)
FAIL("unspecified pixel format");
if (!Stream->codecpar->width || !Stream->codecpar->height)
FAIL("Unspecified video size");
if (Stream->codecpar->format == AV_PIX_FMT_NONE)
FAIL("Unspecified video pixel format");
if (avctx->codec_id == AV_CODEC_ID_RV30 || avctx->codec_id == AV_CODEC_ID_RV40)
if (!st->sample_aspect_ratio.num && !avctx->sample_aspect_ratio.num && !st->codec_info_nb_frames)
FAIL("no frame in rv30/40 and no sar");
if (!Stream->sample_aspect_ratio.num && !avctx->sample_aspect_ratio.num && !Stream->codec_info_nb_frames)
FAIL("No frame in rv30/40 and no sar");
break;
case AVMEDIA_TYPE_AUDIO:
avctx = gCodecMap->getCodecContext(Stream);
if (!avctx)
FAIL("No codec for audio stream");

// These checks are currently disabled as they continually fail but
// codec initialisation is fine - which just delays live tv startup.
// The worst offender appears to be audio description channel...

//if (!Stream->codecpar->frame_size && determinable_frame_size(avctx))
// FAIL("Unspecified audio frame size");
//if (Stream->codecpar->format == AV_SAMPLE_FMT_NONE)
// FAIL("Unspecified audio sample format");
//if (!Stream->codecpar->sample_rate)
// FAIL("Unspecified audio sample rate");
//if (!Stream->codecpar->channels)
// FAIL("Unspecified number of audio channels");
if (!Stream->nb_decoded_frames && avctx->codec_id == AV_CODEC_ID_DTS)
FAIL("No decodable DTS frames");
break;

case AVMEDIA_TYPE_SUBTITLE:
if (st->codecpar->codec_id == AV_CODEC_ID_HDMV_PGS_SUBTITLE && !st->codecpar->width)
FAIL("unspecified size");
if (Stream->codecpar->codec_id == AV_CODEC_ID_HDMV_PGS_SUBTITLE && !Stream->codecpar->width)
FAIL("Unspecified subtitle size");
break;
case AVMEDIA_TYPE_DATA:
if(st->codecpar->codec_id == AV_CODEC_ID_NONE) return 1;
if (Stream->codecpar->codec_id == AV_CODEC_ID_NONE)
return true;
break;
default:
break;
}

if (st->codecpar->codec_id == AV_CODEC_ID_NONE)
FAIL("unknown codec");
return 1;
if (Stream->codecpar->codec_id == AV_CODEC_ID_NONE)
FAIL("Unknown codec");
return true;
}

static void myth_av_log(void *ptr, int level, const char* fmt, va_list vl)
Expand Down Expand Up @@ -698,7 +707,7 @@ void AvFormatDecoder::SeekReset(long long newKey, uint skipFrames,
// Discard all the queued up decoded frames
if (discardFrames)
{
bool releaseall = m_mythcodecctx && m_mythcodecctx->DecoderWillResetOnFlush();
bool releaseall = m_mythcodecctx ? m_mythcodecctx->DecoderWillResetOnFlush() : false;
m_parent->DiscardVideoFrames(doflush, doflush && releaseall);
}

Expand Down Expand Up @@ -901,8 +910,8 @@ void AvFormatDecoder::InitByteContext(bool forceseek)

// We can always seek during LiveTV
m_ic->pb->seekable = !streamed || forceseek;
LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Buffer size: %1 streamed %2 seekable %3")
.arg(buf_size).arg(streamed).arg(m_ic->pb->seekable));
LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Buffer size: %1 Streamed %2 Seekable %3 Available %4")
.arg(buf_size).arg(streamed).arg(m_ic->pb->seekable).arg(ringBuffer->GetReadBufAvail()));
}

extern "C" void HandleStreamChange(void *data)
Expand Down Expand Up @@ -971,21 +980,17 @@ int AvFormatDecoder::OpenFile(RingBuffer *rbuffer, bool novideo,
AVProbeData probe;
memset(&probe, 0, sizeof(AVProbeData));
probe.filename = filename;
probe.buf = (unsigned char *)testbuf;
probe.buf = reinterpret_cast<unsigned char *>(testbuf);
if (testbufsize + AVPROBE_PADDING_SIZE <= kDecoderProbeBufferSize)
probe.buf_size = testbufsize;
else
probe.buf_size = kDecoderProbeBufferSize - AVPROBE_PADDING_SIZE;

memset(probe.buf + probe.buf_size, 0, AVPROBE_PADDING_SIZE);

LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "OpenFile -- begin");

fmt = av_probe_input_format(&probe, static_cast<int>(true));
if (!fmt)
{
LOG(VB_GENERAL, LOG_ERR, LOC +
QString("Probe failed for file: \"%1\".").arg(filename));
LOG(VB_GENERAL, LOG_ERR, LOC + QString("Probe failed for '%1'").arg(filename));
return -1;
}

Expand All @@ -1001,17 +1006,24 @@ int AvFormatDecoder::OpenFile(RingBuffer *rbuffer, bool novideo,
}

int err = 0;
bool found = false;
bool scanned = false;
bool scancomplete = false;
int remainingscans = 5;

if (m_livetv)
while (!scancomplete && remainingscans--)
{
// We try to open the file for up to 1.5 second using only buffer in memory
MythTimer timer; timer.start();
bool found = false;

m_avfRingBuffer->SetInInit(true);
// With live tv, the ringbufer may contain insufficient data for complete
// initialisation so we try a few times with a slight pause each time to
// allow extra data to become available. In the worst case scenarios, the
// stream may not have a keyframe for 4-5 seconds.
// As a last resort, we will try and fallback to the original FFmpeg MPEG-TS
// demuxer if it is not already used.
// For regular videos, this shouldn't be an issue as the complete file
// should be available - though we try a little harder for streamed formats
int retries = m_livetv || ringBuffer->IsStreamed() ? 50 : 10;

while (!found && timer.elapsed() < 1500)
while (!found && --retries)
{
m_ic = avformat_alloc_context();
if (!m_ic)
Expand All @@ -1020,106 +1032,50 @@ int AvFormatDecoder::OpenFile(RingBuffer *rbuffer, bool novideo,
return -1;
}

InitByteContext(true);
m_avfRingBuffer->SetInInit(m_livetv);
InitByteContext(m_livetv);

err = avformat_open_input(&m_ic, filename, fmt, nullptr);
if (err < 0)
{
LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
QString("avformat_open_input failed for in ram data after %1ms, retrying in 50ms")
.arg(timer.elapsed()));
usleep(50 * 1000); // wait 50ms
continue;
}
char error[AV_ERROR_MAX_STRING_SIZE];
av_make_error_string(error, sizeof(error), err);
LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Failed to open input ('%1')").arg(error));

// Test if we can find all streams details in what has been found so far
if (FindStreamInfo() < 0)
{
avformat_close_input(&m_ic);
LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
QString("FindStreamInfo failed for in ram data after %1ms, retrying in 50ms")
.arg(timer.elapsed()));
usleep(50 * 1000); // wait 50ms
continue;
}

found = true;
// note - m_ic (AVFormatContext) is freed on failure
if (retries > 1)
{
// wait a little to buffer more data
// 50*0.1 = 5 seconds max
QThread::usleep(100000);
// resets the read position
m_avfRingBuffer->SetInInit(false);
continue;
}

for (uint i = 0; i < m_ic->nb_streams; i++)
{
if (!has_codec_parameters(m_ic->streams[i]))
if (strcmp(fmt->name, "mpegts") == 0)
{
avformat_close_input(&m_ic);
found = false;
LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
QString("Invalid streams found in ram data after %1ms, retrying in 50ms")
.arg(timer.elapsed()));
usleep(50 * 1000); // wait 50ms
fmt = av_find_input_format("mpegts-ffmpeg");
if (fmt)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Attempting to use original FFmpeg MPEG-TS demuxer.");
// resets the read position
m_avfRingBuffer->SetInInit(false);
continue;
}
break;
}
}
found = true;
}

m_avfRingBuffer->SetInInit(false);

if (found)
{
LOG(VB_PLAYBACK, LOG_INFO, LOC +
QString("File successfully opened after %1ms")
.arg(timer.elapsed()));
}
else
{
LOG(VB_PLAYBACK, LOG_WARNING, LOC +
QString("No streams found in ram data after %1ms, defaulting to in-file")
.arg(timer.elapsed()));
}
scanned = found;
}

// If we haven't opened the file so far, revert to old method
while (!found)
{
m_ic = avformat_alloc_context();
if (!m_ic)
if (err < 0)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Could not allocate format context.");
LOG(VB_GENERAL, LOG_ERR, LOC + "Fatal error opening input. Aborting");
m_ic = nullptr;
return -1;
}

InitByteContext();

err = avformat_open_input(&m_ic, filename, fmt, nullptr);
if (err < 0)
{
if (strcmp(fmt->name, "mpegts") == 0)
{
fmt = av_find_input_format("mpegts-ffmpeg");
if (fmt)
{
LOG(VB_GENERAL, LOG_ERR, LOC +
QString("avformat_open_input failed with '%1' error.").arg(err));
LOG(VB_GENERAL, LOG_ERR, LOC +
QString("Attempting using original FFmpeg MPEG-TS demuxer."));
// ic would have been freed due to the earlier failure
continue;
}
break;
}
}
found = true;
}
if (err < 0)
{
LOG(VB_GENERAL, LOG_ERR, LOC +
QString("avformat err(%1) on avformat_open_input call.").arg(err));
m_ic = nullptr;
return -1;
}

if (!scanned)
{

// With certain streams, we don't get a complete stream analysis and the video
// codec/frame format is not fully detected. This can have various consequences - from
// failed playback to not enabling hardware decoding (as the frame formt is not valid).
Expand All @@ -1129,17 +1085,37 @@ int AvFormatDecoder::OpenFile(RingBuffer *rbuffer, bool novideo,
// it takes to complete the scan).
m_ic->max_analyze_duration = 60 * AV_TIME_BASE;

int ret = FindStreamInfo();
if (ret < 0)
m_avfRingBuffer->SetInInit(m_livetv);
err = FindStreamInfo();
if (err < 0)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Could not find codec parameters. " +
QString("file was \"%1\".").arg(filename));
avformat_close_input(&m_ic);
m_ic = nullptr;
LOG(VB_GENERAL, LOG_ERR, LOC + QString("Could not find codec parameters for '%1'").arg(filename));
CloseContext();
return -1;
}
m_avfRingBuffer->SetInInit(false);

// final sanity check that scanned streams are valid
scancomplete = true;
for (uint i = 0; i < m_ic->nb_streams; i++)
{
if (!StreamHasRequiredParameters(m_ic->streams[i]))
{
scancomplete = false;
if (remainingscans)
{
CloseContext();
LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stream scan incomplete - retrying");
QThread::usleep(250000); // wait 250ms
}
break;
}
}
}

if (!scancomplete)
LOG(VB_GENERAL, LOG_WARNING, LOC + "Scan incomplete - playback may not work");

m_ic->streams_changed = HandleStreamChange;
m_ic->stream_change_data = this;

Expand All @@ -1164,9 +1140,12 @@ int AvFormatDecoder::OpenFile(RingBuffer *rbuffer, bool novideo,
}

// Scan for the initial A/V streams
int ret = ScanStreams(novideo);
if (-1 == ret)
return ret;
err = ScanStreams(novideo);
if (-1 == err)
{
CloseContext();
return err;
}

AutoSelectTracks(); // This is needed for transcoder

Expand Down

0 comments on commit 76bca2a

Please sign in to comment.