The documentation of av_seek_frame() makes a contract that it seeks to a keyframe if AVSEEK_FLAG_ANY is not set. mpg files break this contract by not seeking to a keyframe and still returning success.

Updated Edit:
It seems that any AVInputFormat that doesn't have it's own read_seek() or read_seek2() functions defined will default to either ff_seek_frame_binary() or ff_gen_search() (with priority given to ff_seek_frame_binary()). ff_seek_frame_binary() doesn't respect seeking by keyframe, and will break the keyframe contract made by av_seek_frame() when AVSEEK_FLAG_ANY is not set.

Proposed solutions:1)
Seeking in mpg should require AVSEEK_FLAG_ANY to be set in order for success to be returned (return failure if it's not specified). This can be done by requiring AVSEEK_FLAG_ANY to be set before calling ff_seek_frame_binary().

2) (in response to Don's suggestions)
Change ff_seek_frame_binary() so that it respects seeking by keyframe if AVSEEK_FLAG_ANY is not set. This can be done by sequentially reading (either forwards or backwards (I would prefer backwards)) up to a keyframe after the binary search is complete.

Change History (15)

I don't think the above is the solution and in fact it may break a lot of applications.

I think the real solution is to make seeking to a timestamp work as it is supposed to, which is seeking to a keyframe. This would involve fixing/adding seek code for mpeg related code. I think now it just uses the generic seek code.

Work arounds for this are clumsy where it probably is fairly easy to fix for someone knowledgeable in this area. Also, fixing it once in the place it should be fixed means many others don't have to spend time on it.

I think that's a good point. One option is to add custom seek code to mpg. Another option is to add some new code to ff_seek_frame_binary() (which is what I believe mpg defaults to using, judging by the code I'm reading). ff_seek_frame_binary() could be changed so that if AVSEEK_FLAG_ANY is not set, after it finds its frame, and if that frame is not a keyframe, it reads backwards until it finds a keyframe. Otherwise, if AVSEEK_FLAG_ANY is set it just returns the frame it finds.

I failed to extract a non-keyframe out of this sample with ffmpeg.
If it is possible, please provide the command together with complete, uncut console output.

Can you describe how you were able to extract only keyframes out of this sample? Because I'm afraid we may not be on the same page (again). If av_seek_frame() seeks to a keyframe, then the first packet out of av_read_frame() should be a keyframe packet and the first call to avcodec_decode_video2() with this packet should return a full decoded frame. This is not the case. av_seek_frame() on this file (and other mpg files) seeks to a non-keyframe, and the first several packets out of av_read_frame() are not keyframe packets, and several calls must be made to avcodec_decode_video2() before a full decoded frame is returned.

The problem isn't that you get a junk frame out (the decoder makes sure you don't). The problem is that av_seek_frame() doesn't seek to a keyframe packet (despite the fact that that's what it claims it does), and several calls must be made to av_read_frame() before finally finding a keyframe packet.

This is best looked at by a developer. Trying to produce something using ffmpeg or ffplay may be difficult to visualize for you. Sometimes recovery is quick and is not as noticeable. If a developer will try to seek on the above file and look at the first packet read after the seek, most likely it will not be a key packet. Sometimes you get lucky and you will land on a keyframe but mostly not. Since in general you do not land on keyframe after a seek for mpeg2video files, there will be a delay before animation begins. This can be a 2 second delay and just depends on the file and the time you attempted to seek to.

The seek for mpeg2video files uses ff_seek_frame_binary/ff_gen_search and it's just not smart enough to to do keyframe seeking because there are no index entries. Any format that is using the generic seek code may also have the same problem. Best thing is to add specialized seeking for mpeg2video like most of the other formats that work have.

If you are just looking visually at the above file you may not notice too much of a problem. Some files are much worse. The above file is just the smallest I have that will reproduce the problem but a developer needs to look at it. The other thing is that since ff_seek_frame_binary is being used without any index entries, it should be obvious to any developer why this is a problem and is most likely already known.

There are no index entries for these files and the seek just falls into ff_gen_search with the times that are originally passed in. Now maybe if index entries were added, this could be made to work without adding special seek code.

The problem isn't that you get a junk frame out (the decoder makes sure you don't).

I wonder what this sentence means: There are many samples that return gray frames for above command line (including some sample that you pointed to).

Really? I don't get any gray frames with those commands. I get good looking frames out, but that's because the decoder swallows the non-keyframes after the seek and ffmpeg keeps reading until it gets a keyframe packet and the decoder finally returns a full frame. I get:

The output image looks okay though. The key here is the same thing Don points out (the [mpeg2video @ 0x10180f200] warning: first frame is no keyframe part). If you're able to get gray frames out with that command then something's different between our ffmpegs...

@Don: I don't think (someone please correct me if I'm wrong) that specialized code can really be written for the mpeg's AVInputFormat seek function because of the structure of mpeg's. I think it would just be better to fix ff_seek_frame_binary(), because if custom code was written for mpeg's it would end up being so similar to ff_seek_frame_binary(). IIUC, mpeg's are impossible to properly seek by keyframes reliably, and the best solution would be to do a binary search for a frame, and then after finding it, sequentially reading (either forwards or backwards) to the next keyframe.

The problem isn't that you get a junk frame out (the decoder makes sure you don't).

I wonder what this sentence means: There are many samples that return gray frames for above command line (including some sample that you pointed to).

Really? I don't get any gray frames with those commands.

Assuming you mean with the sample from this ticket: That is what I wrote.

I tried to explain that Don opened many tickets, some of them contain samples that produced gray frames with above command line(s) or very similar ones (most of them should be fixed in current git head).

The reason ff_seek_frame_binary is not effective in this case is because there is no index table that marks keyframes. So ff_seek_frame_binary basically does nothing and falls into ff_gen_search with the original requested timestamp. If an index table could be built then ff_seek_frame_binary would probably be fine as it is. Some other formats have a simple read_seek that builds the index table and then calls ff_seek_frame_binary. So Michael Bradshaw says it is not possible to build an index table in this case.

Special seek code can always be available for any format but in this case it just defaults to the generic seek code and ends up in ff_gen_search. ff_gen_search is currently blind about any keyframes as far as I can tell in this case. Your requested timestamp is unlikely to fall on a keyframe and since there were no index entries. ff_gen_search does work if you have seeked to a known keyframe timestamp.

ff_gen_search is the code that actually closes in on the timestamp and it does appear to work for timestamps that represent actual keyframes. So you probably don't want to modify ff_gen_search to do extra work in the case when a timestamp is passed in that is known to represent a keyframe. These known timestamps are typically retrieved from an index table.

ff_gen_search seems to be more of a subroutine that works off whatever you pass it and used for reading the timestamp position.

So now we are back to needing a specialized read_seek function in this case. This is because you probably don't want to modify the existing generic seek functions. It is completely normal for various formats to have a specialized read_seek function. The read_seek could be a modified version of ff_gen_search or whatever that does the right thing.

Would be good if someone that knew for sure would comment on all this.