diff mbox

[FFmpeg-devel,2/3] ffmpeg: use new decode API

Message ID 20160929182519.3648-2-nfxjfg@googlemail.com
State Accepted
Headers show

Commit Message

wm4 Sept. 29, 2016, 6:25 p.m. UTC
This is a bit messy, mainly due to timestamp handling.

decode_video() relied on the fact that it could set dts on a flush/drain
packet. This is not possible with the old API, and won't be. (I think
doing this was very questionable with the old API. Flush packets should
not contain any information; they just cause a FIFO to be emptied.) This
is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
and using the suggested DTS in the drain case.

The fate-cavs test still fails due to dropping the last frame. This
happens because the timestamp of the last frame goes backwards
(ffprobe -show_frames shows the same thing). I suspect that this
"worked" due to the best effort timestamp logic picking the DTS
over the decreasing PTS. Since this logic is in libavcodec (where
it probably shouldn't be), this can't be easily fixed. The timestamps
of the cavs samples are weird anyway, so I chose not to fix it.

Another strange thing is the timestamp handling in the video path of
process_input_packet (after the decode_video() call). It looks like
the code to increase next_dts and next_pts should be run every time
a frame is decoded - but it's needed even if output is skipped.
---
 ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
 ffmpeg.h            |   3 +
 tests/ref/fate/cavs |   1 -
 3 files changed, 129 insertions(+), 53 deletions(-)

Comments

Michael Niedermayer Sept. 29, 2016, 9:35 p.m. UTC | #1
On Thu, Sep 29, 2016 at 08:25:18PM +0200, wm4 wrote:
> This is a bit messy, mainly due to timestamp handling.
> 
> decode_video() relied on the fact that it could set dts on a flush/drain
> packet. This is not possible with the old API, and won't be. (I think
> doing this was very questionable with the old API. Flush packets should
> not contain any information; they just cause a FIFO to be emptied.) This
> is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
> and using the suggested DTS in the drain case.
> 
> The fate-cavs test still fails due to dropping the last frame. This
> happens because the timestamp of the last frame goes backwards
> (ffprobe -show_frames shows the same thing). I suspect that this
> "worked" due to the best effort timestamp logic picking the DTS
> over the decreasing PTS. Since this logic is in libavcodec (where
> it probably shouldn't be), this can't be easily fixed. The timestamps
> of the cavs samples are weird anyway, so I chose not to fix it.
> 
> Another strange thing is the timestamp handling in the video path of
> process_input_packet (after the decode_video() call). It looks like
> the code to increase next_dts and next_pts should be run every time
> a frame is decoded - but it's needed even if output is skipped.
> ---
>  ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
>  ffmpeg.h            |   3 +
>  tests/ref/fate/cavs |   1 -
>  3 files changed, 129 insertions(+), 53 deletions(-)

with this and patch 1/3
the following infinite loops
./ffmpeg  -f openal -i ''  -t 0.1 file.wav

[...]
wm4 Sept. 29, 2016, 10:30 p.m. UTC | #2
On Thu, 29 Sep 2016 23:35:49 +0200
Michael Niedermayer <michael@niedermayer.cc> wrote:

> On Thu, Sep 29, 2016 at 08:25:18PM +0200, wm4 wrote:
> > This is a bit messy, mainly due to timestamp handling.
> > 
> > decode_video() relied on the fact that it could set dts on a flush/drain
> > packet. This is not possible with the old API, and won't be. (I think
> > doing this was very questionable with the old API. Flush packets should
> > not contain any information; they just cause a FIFO to be emptied.) This
> > is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
> > and using the suggested DTS in the drain case.
> > 
> > The fate-cavs test still fails due to dropping the last frame. This
> > happens because the timestamp of the last frame goes backwards
> > (ffprobe -show_frames shows the same thing). I suspect that this
> > "worked" due to the best effort timestamp logic picking the DTS
> > over the decreasing PTS. Since this logic is in libavcodec (where
> > it probably shouldn't be), this can't be easily fixed. The timestamps
> > of the cavs samples are weird anyway, so I chose not to fix it.
> > 
> > Another strange thing is the timestamp handling in the video path of
> > process_input_packet (after the decode_video() call). It looks like
> > the code to increase next_dts and next_pts should be run every time
> > a frame is decoded - but it's needed even if output is skipped.
> > ---
> >  ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
> >  ffmpeg.h            |   3 +
> >  tests/ref/fate/cavs |   1 -
> >  3 files changed, 129 insertions(+), 53 deletions(-)  
> 
> with this and patch 1/3
> the following infinite loops
> ./ffmpeg  -f openal -i ''  -t 0.1 file.wav
> 

Give me a way to reproduce without OpenAL or anything libavdevice. I
feel like I suffered enough already for this shit.
wm4 Sept. 30, 2016, 8:15 a.m. UTC | #3
On Thu, 29 Sep 2016 23:35:49 +0200
Michael Niedermayer <michael@niedermayer.cc> wrote:

> On Thu, Sep 29, 2016 at 08:25:18PM +0200, wm4 wrote:
> > This is a bit messy, mainly due to timestamp handling.
> > 
> > decode_video() relied on the fact that it could set dts on a flush/drain
> > packet. This is not possible with the old API, and won't be. (I think
> > doing this was very questionable with the old API. Flush packets should
> > not contain any information; they just cause a FIFO to be emptied.) This
> > is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
> > and using the suggested DTS in the drain case.
> > 
> > The fate-cavs test still fails due to dropping the last frame. This
> > happens because the timestamp of the last frame goes backwards
> > (ffprobe -show_frames shows the same thing). I suspect that this
> > "worked" due to the best effort timestamp logic picking the DTS
> > over the decreasing PTS. Since this logic is in libavcodec (where
> > it probably shouldn't be), this can't be easily fixed. The timestamps
> > of the cavs samples are weird anyway, so I chose not to fix it.
> > 
> > Another strange thing is the timestamp handling in the video path of
> > process_input_packet (after the decode_video() call). It looks like
> > the code to increase next_dts and next_pts should be run every time
> > a frame is decoded - but it's needed even if output is skipped.
> > ---
> >  ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
> >  ffmpeg.h            |   3 +
> >  tests/ref/fate/cavs |   1 -
> >  3 files changed, 129 insertions(+), 53 deletions(-)  
> 
> with this and patch 1/3
> the following infinite loops
> ./ffmpeg  -f openal -i ''  -t 0.1 file.wav

Not a reasonably to reproduce test case as it requires installing
OpenAL and possibly has specific hardware requirements.

Will push this patch in 24 hours unless other problems are pointed out.
Timo Rothenpieler Sept. 30, 2016, 8:38 a.m. UTC | #4
> Will push this patch in 24 hours unless other problems are pointed out.

+1 for pushing the entire series.
Changes like this are almost impossible to do without breaking some
corner cases.
Delaying it until all of those are fixed won't get the project anywhere.

So far I haven't found anything that breaks for me.
Using it with cuvid, which is so far the only decoder that natively
implements the new decode API, also works fine, as long as no
deinterlacing/multi-frame-output is involved.

Fixing the corner cases can be done as they are discovered and brought
up as bugs.
wm4 Sept. 30, 2016, 8:43 a.m. UTC | #5
On Fri, 30 Sep 2016 10:38:41 +0200
Timo Rothenpieler <timo@rothenpieler.org> wrote:

> > Will push this patch in 24 hours unless other problems are pointed out.  
> 
> +1 for pushing the entire series.
> Changes like this are almost impossible to do without breaking some
> corner cases.
> Delaying it until all of those are fixed won't get the project anywhere.

Keep in mind that I'll fix anything that is pointed out and
reproducible.

> So far I haven't found anything that breaks for me.
> Using it with cuvid, which is so far the only decoder that natively
> implements the new decode API, also works fine, as long as no
> deinterlacing/multi-frame-output is involved.
> 
> Fixing the corner cases can be done as they are discovered and brought
> up as bugs.

Why exactly does multi-frame output not work? Though that would of
course be new functionality, rather than making sure existing code
works.
Timo Rothenpieler Sept. 30, 2016, 8:56 a.m. UTC | #6
Am 30.09.2016 um 10:43 schrieb wm4:
> On Fri, 30 Sep 2016 10:38:41 +0200
> Timo Rothenpieler <timo@rothenpieler.org> wrote:
> 
>>> Will push this patch in 24 hours unless other problems are pointed out.  
>>
>> +1 for pushing the entire series.
>> Changes like this are almost impossible to do without breaking some
>> corner cases.
>> Delaying it until all of those are fixed won't get the project anywhere.
> 
> Keep in mind that I'll fix anything that is pointed out and
> reproducible.
> 
>> So far I haven't found anything that breaks for me.
>> Using it with cuvid, which is so far the only decoder that natively
>> implements the new decode API, also works fine, as long as no
>> deinterlacing/multi-frame-output is involved.
>>
>> Fixing the corner cases can be done as they are discovered and brought
>> up as bugs.
> 
> Why exactly does multi-frame output not work? Though that would of
> course be new functionality, rather than making sure existing code
> works.

Because the decoder changes the frame rate in case of deinterlacing, as
it outputs two frames for every input frame.
FFmpeg is not aware of the possibility of that happening at all, and
does not check/re-evaluate the frame rate.

The primary issue causes by that is that it uses the original framerate
as timebase for the encoder, and with a 1/25 timebase it's impossible to
represent 50 frames per second.

On top of that, cuvid does not set the framerate on open, but when the
first frame is decoded.
Also I just realized I never tried just doubling the framerate on open
if deinterlacing is enabled, which might just be enough to make it work.
Carl Eugen Hoyos Sept. 30, 2016, 9:07 a.m. UTC | #7
2016-09-30 10:43 GMT+02:00 wm4 <nfxjfg@googlemail.com>:

> Keep in mind that I'll fix anything that is pointed out and
> reproducible.

This seems to contradict your last two emails...

Carl Eugen
Hendrik Leppkes Sept. 30, 2016, 9:11 a.m. UTC | #8
On Fri, Sep 30, 2016 at 11:07 AM, Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote:
> 2016-09-30 10:43 GMT+02:00 wm4 <nfxjfg@googlemail.com>:
>
>> Keep in mind that I'll fix anything that is pointed out and
>> reproducible.
>
> This seems to contradict your last two emails...

Please keep the discussion technical.

If you can point out a reproducible failure that does not require
specific hardware (say to run OpenAL), wm4 has already said he'll be
working on those.

- Hendrik
Marton Balint Sept. 30, 2016, 9:12 a.m. UTC | #9
On Fri, 30 Sep 2016, wm4 wrote:

> On Thu, 29 Sep 2016 23:35:49 +0200
> Michael Niedermayer <michael@niedermayer.cc> wrote:
>
>> On Thu, Sep 29, 2016 at 08:25:18PM +0200, wm4 wrote:
>> > This is a bit messy, mainly due to timestamp handling.
>> > 
>> > decode_video() relied on the fact that it could set dts on a flush/drain
>> > packet. This is not possible with the old API, and won't be. (I think
>> > doing this was very questionable with the old API. Flush packets should
>> > not contain any information; they just cause a FIFO to be emptied.) This
>> > is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
>> > and using the suggested DTS in the drain case.
>> > 
>> > The fate-cavs test still fails due to dropping the last frame. This
>> > happens because the timestamp of the last frame goes backwards
>> > (ffprobe -show_frames shows the same thing). I suspect that this
>> > "worked" due to the best effort timestamp logic picking the DTS
>> > over the decreasing PTS. Since this logic is in libavcodec (where
>> > it probably shouldn't be), this can't be easily fixed. The timestamps
>> > of the cavs samples are weird anyway, so I chose not to fix it.
>> > 
>> > Another strange thing is the timestamp handling in the video path of
>> > process_input_packet (after the decode_video() call). It looks like
>> > the code to increase next_dts and next_pts should be run every time
>> > a frame is decoded - but it's needed even if output is skipped.
>> > ---
>> >  ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
>> >  ffmpeg.h            |   3 +
>> >  tests/ref/fate/cavs |   1 -
>> >  3 files changed, 129 insertions(+), 53 deletions(-) 
>> 
>> with this and patch 1/3
>> the following infinite loops
>> ./ffmpeg  -f openal -i ''  -t 0.1 file.wav
>
> Not a reasonably to reproduce test case as it requires installing
> OpenAL and possibly has specific hardware requirements.
>
> Will push this patch in 24 hours unless other problems are pointed out.

This seems like a very simple to fix bug in openal.

I will send a patch series soon.

Regards,
Marton
wm4 Sept. 30, 2016, 9:20 a.m. UTC | #10
On Fri, 30 Sep 2016 11:12:31 +0200 (CEST)
Marton Balint <cus@passwd.hu> wrote:

> On Fri, 30 Sep 2016, wm4 wrote:
> 
> > On Thu, 29 Sep 2016 23:35:49 +0200
> > Michael Niedermayer <michael@niedermayer.cc> wrote:
> >  
> >> On Thu, Sep 29, 2016 at 08:25:18PM +0200, wm4 wrote:  
> >> > This is a bit messy, mainly due to timestamp handling.
> >> > 
> >> > decode_video() relied on the fact that it could set dts on a flush/drain
> >> > packet. This is not possible with the old API, and won't be. (I think
> >> > doing this was very questionable with the old API. Flush packets should
> >> > not contain any information; they just cause a FIFO to be emptied.) This
> >> > is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
> >> > and using the suggested DTS in the drain case.
> >> > 
> >> > The fate-cavs test still fails due to dropping the last frame. This
> >> > happens because the timestamp of the last frame goes backwards
> >> > (ffprobe -show_frames shows the same thing). I suspect that this
> >> > "worked" due to the best effort timestamp logic picking the DTS
> >> > over the decreasing PTS. Since this logic is in libavcodec (where
> >> > it probably shouldn't be), this can't be easily fixed. The timestamps
> >> > of the cavs samples are weird anyway, so I chose not to fix it.
> >> > 
> >> > Another strange thing is the timestamp handling in the video path of
> >> > process_input_packet (after the decode_video() call). It looks like
> >> > the code to increase next_dts and next_pts should be run every time
> >> > a frame is decoded - but it's needed even if output is skipped.
> >> > ---
> >> >  ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
> >> >  ffmpeg.h            |   3 +
> >> >  tests/ref/fate/cavs |   1 -
> >> >  3 files changed, 129 insertions(+), 53 deletions(-)   
> >> 
> >> with this and patch 1/3
> >> the following infinite loops
> >> ./ffmpeg  -f openal -i ''  -t 0.1 file.wav  
> >
> > Not a reasonably to reproduce test case as it requires installing
> > OpenAL and possibly has specific hardware requirements.
> >
> > Will push this patch in 24 hours unless other problems are pointed out.  
> 
> This seems like a very simple to fix bug in openal.
> 
> I will send a patch series soon.

Thanks, it's appreciated + I will review it.
Michael Niedermayer Sept. 30, 2016, 7:48 p.m. UTC | #11
On Thu, Sep 29, 2016 at 08:25:18PM +0200, wm4 wrote:
> This is a bit messy, mainly due to timestamp handling.
> 
> decode_video() relied on the fact that it could set dts on a flush/drain
> packet. This is not possible with the old API, and won't be. (I think
> doing this was very questionable with the old API. Flush packets should
> not contain any information; they just cause a FIFO to be emptied.) This
> is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
> and using the suggested DTS in the drain case.
> 
> The fate-cavs test still fails due to dropping the last frame. This
> happens because the timestamp of the last frame goes backwards
> (ffprobe -show_frames shows the same thing). I suspect that this
> "worked" due to the best effort timestamp logic picking the DTS
> over the decreasing PTS. Since this logic is in libavcodec (where
> it probably shouldn't be), this can't be easily fixed. The timestamps
> of the cavs samples are weird anyway, so I chose not to fix it.
> 
> Another strange thing is the timestamp handling in the video path of
> process_input_packet (after the decode_video() call). It looks like
> the code to increase next_dts and next_pts should be run every time
> a frame is decoded - but it's needed even if output is skipped.
> ---
>  ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
>  ffmpeg.h            |   3 +
>  tests/ref/fate/cavs |   1 -
>  3 files changed, 129 insertions(+), 53 deletions(-)
[...]

> @@ -2101,23 +2129,45 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
>      return err < 0 ? err : ret;
>  }
>  
> -static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
> +static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int eof)
>  {
>      AVFrame *decoded_frame, *f;
>      int i, ret = 0, err = 0, resample_changed;
>      int64_t best_effort_timestamp;

> +    int64_t dts = AV_NOPTS_VALUE;
[...]
> -    pkt->dts  = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);
> +    if (ist->dts != AV_NOPTS_VALUE)
> +        dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);

can be simplified with AV_ROUND_PASS_MINMAX if you like

[...]
> diff --git a/ffmpeg.h b/ffmpeg.h
> index 3ba62a1..1195055 100644
> --- a/ffmpeg.h
> +++ b/ffmpeg.h
> @@ -348,6 +348,9 @@ typedef struct InputStream {
>      // number of frames/samples retrieved from the decoder
>      uint64_t frames_decoded;
>      uint64_t samples_decoded;
> +
> +    uint64_t *dts_buffer;

it makes no difference with the current code but timestamps are signed

[...]
wm4 Oct. 1, 2016, 3:25 p.m. UTC | #12
On Fri, 30 Sep 2016 21:48:03 +0200
Michael Niedermayer <michael@niedermayer.cc> wrote:

> On Thu, Sep 29, 2016 at 08:25:18PM +0200, wm4 wrote:
> > This is a bit messy, mainly due to timestamp handling.
> > 
> > decode_video() relied on the fact that it could set dts on a flush/drain
> > packet. This is not possible with the old API, and won't be. (I think
> > doing this was very questionable with the old API. Flush packets should
> > not contain any information; they just cause a FIFO to be emptied.) This
> > is replaced with checking the best_effort_timestamp for AV_NOPTS_VALUE,
> > and using the suggested DTS in the drain case.
> > 
> > The fate-cavs test still fails due to dropping the last frame. This
> > happens because the timestamp of the last frame goes backwards
> > (ffprobe -show_frames shows the same thing). I suspect that this
> > "worked" due to the best effort timestamp logic picking the DTS
> > over the decreasing PTS. Since this logic is in libavcodec (where
> > it probably shouldn't be), this can't be easily fixed. The timestamps
> > of the cavs samples are weird anyway, so I chose not to fix it.
> > 
> > Another strange thing is the timestamp handling in the video path of
> > process_input_packet (after the decode_video() call). It looks like
> > the code to increase next_dts and next_pts should be run every time
> > a frame is decoded - but it's needed even if output is skipped.
> > ---
> >  ffmpeg.c            | 178 +++++++++++++++++++++++++++++++++++++---------------
> >  ffmpeg.h            |   3 +
> >  tests/ref/fate/cavs |   1 -
> >  3 files changed, 129 insertions(+), 53 deletions(-)  
> [...]
> 
> > @@ -2101,23 +2129,45 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
> >      return err < 0 ? err : ret;
> >  }
> >  
> > -static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
> > +static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int eof)
> >  {
> >      AVFrame *decoded_frame, *f;
> >      int i, ret = 0, err = 0, resample_changed;
> >      int64_t best_effort_timestamp;  
> 
> > +    int64_t dts = AV_NOPTS_VALUE;  
> [...]
> > -    pkt->dts  = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);
> > +    if (ist->dts != AV_NOPTS_VALUE)
> > +        dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);  
> 
> can be simplified with AV_ROUND_PASS_MINMAX if you like
> 
> [...]
> > diff --git a/ffmpeg.h b/ffmpeg.h
> > index 3ba62a1..1195055 100644
> > --- a/ffmpeg.h
> > +++ b/ffmpeg.h
> > @@ -348,6 +348,9 @@ typedef struct InputStream {
> >      // number of frames/samples retrieved from the decoder
> >      uint64_t frames_decoded;
> >      uint64_t samples_decoded;
> > +
> > +    uint64_t *dts_buffer;  
> 
> it makes no difference with the current code but timestamps are signed

Pushed with the type for timestamps adjusted, and the new failing tests
modified as discussed on IRC. Didn't change the NOPTS check above, but
I'm not opposed to it, feel free to push a commit doing that if you
think it's much of an improvement.
diff mbox

Patch

diff --git a/ffmpeg.c b/ffmpeg.c
index f03a594..e7ac5c9 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -550,6 +550,7 @@  static void ffmpeg_cleanup(int ret)
         av_frame_free(&ist->sub2video.frame);
         av_freep(&ist->filters);
         av_freep(&ist->hwaccel_device);
+        av_freep(&ist->dts_buffer);
 
         avcodec_free_context(&ist->dec_ctx);
 
@@ -1976,6 +1977,33 @@  static void check_decode_result(InputStream *ist, int *got_output, int ret)
     }
 }
 
+// This does not quite work like avcodec_decode_audio4/avcodec_decode_video2.
+// There is the following difference: if you got a frame, you must call
+// it again with pkt=NULL. pkt==NULL is treated differently from pkt.size==0
+// (pkt==NULL means get more output, pkt.size==0 is a flush/drain packet)
+static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
+{
+    int ret;
+
+    *got_frame = 0;
+
+    if (pkt) {
+        ret = avcodec_send_packet(avctx, pkt);
+        // In particular, we don't expect AVERROR(EAGAIN), because we read all
+        // decoded frames with avcodec_receive_frame() until done.
+        if (ret < 0 && ret != AVERROR_EOF)
+            return ret;
+    }
+
+    ret = avcodec_receive_frame(avctx, frame);
+    if (ret < 0 && ret != AVERROR(EAGAIN))
+        return ret;
+    if (ret >= 0)
+        *got_frame = 1;
+
+    return 0;
+}
+
 static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
 {
     AVFrame *decoded_frame, *f;
@@ -1990,7 +2018,7 @@  static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
     decoded_frame = ist->decoded_frame;
 
     update_benchmark(NULL);
-    ret = avcodec_decode_audio4(avctx, decoded_frame, got_output, pkt);
+    ret = decode(avctx, decoded_frame, got_output, pkt);
     update_benchmark("decode_audio %d.%d", ist->file_index, ist->st->index);
 
     if (ret >= 0 && avctx->sample_rate <= 0) {
@@ -1998,7 +2026,8 @@  static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
         ret = AVERROR_INVALIDDATA;
     }
 
-    check_decode_result(ist, got_output, ret);
+    if (ret != AVERROR_EOF)
+        check_decode_result(ist, got_output, ret);
 
     if (!*got_output || ret < 0)
         return ret;
@@ -2066,14 +2095,13 @@  static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
     } else if (decoded_frame->pkt_pts != AV_NOPTS_VALUE) {
         decoded_frame->pts = decoded_frame->pkt_pts;
         decoded_frame_tb   = ist->st->time_base;
-    } else if (pkt->pts != AV_NOPTS_VALUE) {
+    } else if (pkt && pkt->pts != AV_NOPTS_VALUE) {
         decoded_frame->pts = pkt->pts;
         decoded_frame_tb   = ist->st->time_base;
     }else {
         decoded_frame->pts = ist->dts;
         decoded_frame_tb   = AV_TIME_BASE_Q;
     }
-    pkt->pts           = AV_NOPTS_VALUE;
     if (decoded_frame->pts != AV_NOPTS_VALUE)
         decoded_frame->pts = av_rescale_delta(decoded_frame_tb, decoded_frame->pts,
                                               (AVRational){1, avctx->sample_rate}, decoded_frame->nb_samples, &ist->filter_in_rescale_delta_last,
@@ -2101,23 +2129,45 @@  static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output)
     return err < 0 ? err : ret;
 }
 
-static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
+static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int eof)
 {
     AVFrame *decoded_frame, *f;
     int i, ret = 0, err = 0, resample_changed;
     int64_t best_effort_timestamp;
+    int64_t dts = AV_NOPTS_VALUE;
     AVRational *frame_sample_aspect;
+    AVPacket avpkt;
+
+    // With fate-indeo3-2, we're getting 0-sized packets before EOF for some
+    // reason. This seems like a semi-critical bug. Don't trigger EOF, and
+    // skip the packet.
+    if (!eof && pkt && pkt->size == 0)
+        return 0;
 
     if (!ist->decoded_frame && !(ist->decoded_frame = av_frame_alloc()))
         return AVERROR(ENOMEM);
     if (!ist->filter_frame && !(ist->filter_frame = av_frame_alloc()))
         return AVERROR(ENOMEM);
     decoded_frame = ist->decoded_frame;
-    pkt->dts  = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);
+    if (ist->dts != AV_NOPTS_VALUE)
+        dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);
+    if (pkt) {
+        avpkt = *pkt;
+        avpkt.dts = dts; // ffmpeg.c probably shouldn't do this
+    }
+
+    // The old code used to set dts on the drain packet, which does not work
+    // with the new API anymore.
+    if (eof) {
+        void *new = av_realloc_array(ist->dts_buffer, ist->nb_dts_buffer + 1, sizeof(ist->dts_buffer[0]));
+        if (!new)
+            return AVERROR(ENOMEM);
+        ist->dts_buffer = new;
+        ist->dts_buffer[ist->nb_dts_buffer++] = dts;
+    }
 
     update_benchmark(NULL);
-    ret = avcodec_decode_video2(ist->dec_ctx,
-                                decoded_frame, got_output, pkt);
+    ret = decode(ist->dec_ctx, decoded_frame, got_output, pkt ? &avpkt : NULL);
     update_benchmark("decode_video %d.%d", ist->file_index, ist->st->index);
 
     // The following line may be required in some cases where there is no parser
@@ -2135,7 +2185,8 @@  static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
                    ist->st->codecpar->video_delay);
     }
 
-    check_decode_result(ist, got_output, ret);
+    if (ret != AVERROR_EOF)
+        check_decode_result(ist, got_output, ret);
 
     if (*got_output && ret >= 0) {
         if (ist->dec_ctx->width  != decoded_frame->width ||
@@ -2167,6 +2218,15 @@  static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
     ist->hwaccel_retrieved_pix_fmt = decoded_frame->format;
 
     best_effort_timestamp= av_frame_get_best_effort_timestamp(decoded_frame);
+
+    if (eof && best_effort_timestamp == AV_NOPTS_VALUE && ist->nb_dts_buffer > 0) {
+        best_effort_timestamp = ist->dts_buffer[0];
+
+        for (i = 0; i < ist->nb_dts_buffer - 1; i++)
+            ist->dts_buffer[i] = ist->dts_buffer[i + 1];
+        ist->nb_dts_buffer--;
+    }
+
     if(best_effort_timestamp != AV_NOPTS_VALUE) {
         int64_t ts = av_rescale_q(decoded_frame->pts = best_effort_timestamp, ist->st->time_base, AV_TIME_BASE_Q);
 
@@ -2185,8 +2245,6 @@  static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
                ist->st->time_base.num, ist->st->time_base.den);
     }
 
-    pkt->size = 0;
-
     if (ist->st->sample_aspect_ratio.num)
         decoded_frame->sample_aspect_ratio = ist->st->sample_aspect_ratio;
 
@@ -2225,12 +2283,12 @@  static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output)
                 break;
         } else
             f = decoded_frame;
-        ret = av_buffersrc_add_frame_flags(ist->filters[i]->filter, f, AV_BUFFERSRC_FLAG_PUSH);
-        if (ret == AVERROR_EOF) {
-            ret = 0; /* ignore */
-        } else if (ret < 0) {
+        err = av_buffersrc_add_frame_flags(ist->filters[i]->filter, f, AV_BUFFERSRC_FLAG_PUSH);
+        if (err == AVERROR_EOF) {
+            err = 0; /* ignore */
+        } else if (err < 0) {
             av_log(NULL, AV_LOG_FATAL,
-                   "Failed to inject frame into filter network: %s\n", av_err2str(ret));
+                   "Failed to inject frame into filter network: %s\n", av_err2str(err));
             exit_program(1);
         }
     }
@@ -2315,7 +2373,8 @@  static int send_filter_eof(InputStream *ist)
 static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof)
 {
     int ret = 0, i;
-    int got_output = 0;
+    int repeating = 0;
+    int eof_reached = 0;
 
     AVPacket avpkt;
     if (!ist->saw_first_ts) {
@@ -2338,84 +2397,99 @@  static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo
         av_init_packet(&avpkt);
         avpkt.data = NULL;
         avpkt.size = 0;
-        goto handle_eof;
     } else {
         avpkt = *pkt;
     }
 
-    if (pkt->dts != AV_NOPTS_VALUE) {
+    if (pkt && pkt->dts != AV_NOPTS_VALUE) {
         ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q);
         if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed)
             ist->next_pts = ist->pts = ist->dts;
     }
 
     // while we have more to decode or while the decoder did output something on EOF
-    while (ist->decoding_needed && (avpkt.size > 0 || (!pkt && got_output))) {
-        int duration;
-    handle_eof:
+    while (ist->decoding_needed) {
+        int duration = 0;
+        int got_output = 0;
 
         ist->pts = ist->next_pts;
         ist->dts = ist->next_dts;
 
         switch (ist->dec_ctx->codec_type) {
         case AVMEDIA_TYPE_AUDIO:
-            ret = decode_audio    (ist, &avpkt, &got_output);
+            ret = decode_audio    (ist, repeating ? NULL : &avpkt, &got_output);
             break;
         case AVMEDIA_TYPE_VIDEO:
-            ret = decode_video    (ist, &avpkt, &got_output);
-            if (avpkt.duration) {
-                duration = av_rescale_q(avpkt.duration, ist->st->time_base, AV_TIME_BASE_Q);
-            } else if(ist->dec_ctx->framerate.num != 0 && ist->dec_ctx->framerate.den != 0) {
-                int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict+1 : ist->dec_ctx->ticks_per_frame;
-                duration = ((int64_t)AV_TIME_BASE *
-                                ist->dec_ctx->framerate.den * ticks) /
-                                ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame;
-            } else
-                duration = 0;
+            ret = decode_video    (ist, repeating ? NULL : &avpkt, &got_output, !pkt);
+            if (!repeating || !pkt || got_output) {
+                if (pkt && pkt->duration) {
+                    duration = av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q);
+                } else if(ist->dec_ctx->framerate.num != 0 && ist->dec_ctx->framerate.den != 0) {
+                    int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict+1 : ist->dec_ctx->ticks_per_frame;
+                    duration = ((int64_t)AV_TIME_BASE *
+                                    ist->dec_ctx->framerate.den * ticks) /
+                                    ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame;
+                }
 
-            if(ist->dts != AV_NOPTS_VALUE && duration) {
-                ist->next_dts += duration;
-            }else
-                ist->next_dts = AV_NOPTS_VALUE;
+                if(ist->dts != AV_NOPTS_VALUE && duration) {
+                    ist->next_dts += duration;
+                }else
+                    ist->next_dts = AV_NOPTS_VALUE;
+            }
 
             if (got_output)
                 ist->next_pts += duration; //FIXME the duration is not correct in some cases
             break;
         case AVMEDIA_TYPE_SUBTITLE:
+            if (repeating)
+                break;
             ret = transcode_subtitles(ist, &avpkt, &got_output);
+            if (!pkt && ret >= 0)
+                ret = AVERROR_EOF;
             break;
         default:
             return -1;
         }
 
+        if (ret == AVERROR_EOF) {
+            eof_reached = 1;
+            break;
+        }
+
         if (ret < 0) {
             av_log(NULL, AV_LOG_ERROR, "Error while decoding stream #%d:%d: %s\n",
                    ist->file_index, ist->st->index, av_err2str(ret));
             if (exit_on_error)
                 exit_program(1);
+            // Decoding might not terminate if we're draining the decoder, and
+            // the decoder keeps returning an error.
+            // This should probably be considered a libavcodec issue.
+            // Sample: fate-vsynth1-dnxhd-720p-hr-lb
+            if (!pkt)
+                eof_reached = 1;
             break;
         }
 
-        avpkt.dts=
-        avpkt.pts= AV_NOPTS_VALUE;
+        if (!got_output)
+            break;
 
-        // touch data and size only if not EOF
-        if (pkt) {
-            if(ist->dec_ctx->codec_type != AVMEDIA_TYPE_AUDIO)
-                ret = avpkt.size;
-            avpkt.data += ret;
-            avpkt.size -= ret;
-        }
-        if (!got_output) {
-            continue;
-        }
-        if (got_output && !pkt)
+        // During draining, we might get multiple output frames in this loop.
+        // ffmpeg.c does not drain the filter chain on configuration changes,
+        // which means if we send multiple frames at once to the filters, and
+        // one of those frames changes configuration, the buffered frames will
+        // be lost. This can upset certain FATE tests.
+        // Decode only 1 frame per call on EOF to appease these FATE tests.
+        // The ideal solution would be to rewrite decoding to use the new
+        // decoding API in a better way.
+        if (!pkt)
             break;
+
+        repeating = 1;
     }
 
     /* after flushing, send an EOF on all the filter inputs attached to the stream */
     /* except when looping we need to flush but not to send an EOF */
-    if (!pkt && ist->decoding_needed && !got_output && !no_eof) {
+    if (!pkt && ist->decoding_needed && eof_reached && !no_eof) {
         int ret = send_filter_eof(ist);
         if (ret < 0) {
             av_log(NULL, AV_LOG_FATAL, "Error marking filters as finished\n");
@@ -2459,7 +2533,7 @@  static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo
         do_streamcopy(ist, ost, pkt);
     }
 
-    return got_output;
+    return !eof_reached;
 }
 
 static void print_sdp(void)
diff --git a/ffmpeg.h b/ffmpeg.h
index 3ba62a1..1195055 100644
--- a/ffmpeg.h
+++ b/ffmpeg.h
@@ -348,6 +348,9 @@  typedef struct InputStream {
     // number of frames/samples retrieved from the decoder
     uint64_t frames_decoded;
     uint64_t samples_decoded;
+
+    uint64_t *dts_buffer;
+    int nb_dts_buffer;
 } InputStream;
 
 typedef struct InputFile {
diff --git a/tests/ref/fate/cavs b/tests/ref/fate/cavs
index 31b9d29..ddcbe04 100644
--- a/tests/ref/fate/cavs
+++ b/tests/ref/fate/cavs
@@ -173,4 +173,3 @@ 
 0,        167,        167,        1,   622080, 0xdcb4cee8
 0,        168,        168,        1,   622080, 0xb41172e5
 0,        169,        169,        1,   622080, 0x56c72478
-0,        170,        170,        1,   622080, 0x84ff3af9