mpv-player / mpv

🎥 Command line video player
https://mpv.io
Other
26.65k stars 2.83k forks source link

ao=null never receives EOF with ao-null-untimed #14427

Open kasper93 opened 5 days ago

kasper93 commented 5 days ago

mpv Information

mpv v0.38.0-590-gd36f72eb63-dirty Copyright © 2000-2024 mpv/MPlayer/mplayer2 projects
 built on Jun 23 2024 20:55:13
libplacebo version: v7.349.0 (v7.349.0-rc1-20-gea3d0673-dirty)
FFmpeg version: N-115634-g6a05128a13
FFmpeg library versions:
   libavcodec      61.7.100
   libavdevice     61.2.100
   libavfilter     10.2.102
   libavformat     61.3.104
   libavutil       59.21.100
   libswresample   5.2.100
   libswscale      8.2.100

Other Information

Reproduction Steps

  1. Extract provided sample file
  2. mpv --no-config --ao=null --untimed --ao-null-untimed ./clusterfuzz-testcase-minimized-fuzzer_loadfile-4665657266864128

Expected Behavior

mpv exits after playing the file

Actual Behavior

mpv hangs and never reports EOF

Log File

We get one frame of data, eat it, and than loop without any data.

[ao/null] starting AO
[ao/null] in=7168 space=8704(8704) pl=1, eof=0
[ao/null] in=0 space=8704(8704) pl=1, eof=0
[ao/null] in=0 space=8704(8704) pl=1, eof=0
[ao/null] in=0 space=8704(8704) pl=1, eof=0
[ao/null] in=0 space=8704(8704) pl=1, eof=0
<repeated inf times>

Sample Files

clusterfuzz-testcase-minimized-fuzzer_loadfile-4665657266864128.zip

I carefully read all instruction and confirm that I did the following:

kasper93 commented 5 days ago

@na-na-hi @ruihe774 Do you have ideas how we can fix untimed ao null? Last time @na-na-hi blamed our mkv demuxer, but it is not the case in this case.

Proposed change by @ruihe774 works, https://github.com/mpv-player/mpv/pull/14079/files#r1593338003 question is this is the best way to handle this.

Basically in non-untimed mode, when we happen to have 0 samples, we trigger audio underrun and this reinits things and trigger EOF. In untimed mode we skip reporting underruns, but then there is no code path to actually trigger EOF.

Help appreciated.

na-na-hi commented 5 days ago

So what's happening here is that the demux layer is reporting EOF, but it doesn't reach to the end of audio filter chain for the first audio frame read (might be decoder's fault but not sure right now). Only after the ao is restarted (because of underrun which happens without --ao-null-untimed), the next audio frame filtered reports EOF.

For well-formed files and not invalid ones generated by fuzzers, this does not happen: even for a very short audio, the first audio frame already reports EOF.

I think the solution here is that for these malformed files, we have to make sure somehow that the first audio frame reports EOF in this case. If it's not possible, then we have to make ao read a few more frames to determine the EOF status.

kasper93 commented 5 days ago

For well-formed files and not invalid ones generated by fuzzers, this does not happen: even for a very short audio, the first audio frame already reports EOF.

I think it can still happens, if the last frame fails to decode. Generally, we can have a valid file, corrupted at the and and similar can happen.

If we look at this https://github.com/mpv-player/mpv/blob/cd1b63f628a99d16183961056bb8177511a85888/audio/decode/ad_lavc.c#L194-L201

we can see that we expect AVERROR_EOF from the decoder, but we don't get it. If I call avcodec_receive_frame once more we get EOF. So this is what happens after ao reset. I'm not sure yet, why we don't try to read more data. We don't have EOF, but we stop reading, why? ao=null should eat every data and request more.

EDIT:

We get EOF from demuxer, so I think we should already report EOF, if we have no more data to decode. The stream is truncated, decoder doesn't detect EOF, but we never feed more data from demuxer, because there is none.

na-na-hi commented 5 days ago

ffplay has the same issue. It doesn't exit when playing this file. Maybe this needs to be fixed in ffmpeg?

avcodec_receive_frame here also doesn't signal decoding error in this case.

If I call avcodec_receive_frame once more we get EOF.

Because we send the decoder a NULL packet, which is EOF propagated all the way from the stream layer. It seems that this EOF isn't propagated until we request the frame one more time, which isn't done because we're stuck on the last incomplete frame.