I want to seek for an arbitrary frame in a video using libav
. More precisely, using the function avformat_seek_file
, which apparently uses av_seek_frame
internally.
I want to make a backward search (i.e. to get the closest possible frame before the one I seek), so that I can then go forward until I find precisely the one I want. For this, I use the function as follows:
avformat_seek_file(..., ...,
std::numeric_limits<boost::int64_t>::min(),
target_pts,
target_pts,
...);
Which means that I don't have any tolerance about finding a frame that comes after my target_pts, but I am happy with any frame coming before.
I am using the Big Buck Bunny videos for testing. Using the 480p H.264 video, I can seek any pts
without problems. But using the 480p OGG video, I can't. Actually, I can seek for any frame after pts = 73
, but not before. Seeking for pts = 0
sets the video to pts = 73
.
One might think that the stream actually begins at pts = 73
, but this is not what <stream>.start_time
returns. Moreover, if I only load the video and read the frames in order, I can get the first 73 frames without any problem. The issue is that I can never come back to one of those frames by using avformat_seek_file
.
Last point: if I use the flag AVSEEK_FLAG_ANY
, then it works. But that might result in me decoding only a part of the frame I want, which is not a solution for me.
Can anybody explain this weird behavior?
This may be an old question but this answer is for people who are having the same problem.
The old av_seek_frame
function uses the AVSEEK_FLAG_BACKWARD
flag to seek to frames with a time stamp smaller than or equal to the target time stamp. By default this function will seek to frames with a time stamp equal to or greater than the target time stamp. The documentation of the new avformat_seek_file
function indicates that this flag is ignored.
If flags contain AVSEEK_FLAG_ANY, then non-keyframes are treated as keyframes (this may not be supported by all demuxers). If flags contain AVSEEK_FLAG_BACKWARD, it is ignored.
As we can see in the source code below.
2156 if (s->seek2any>0)
2157 flags |= AVSEEK_FLAG_ANY;
2158 flags &= ~AVSEEK_FLAG_BACKWARD;
The problem occurs when the avformat_seek_file
function falls back on av_seek_frame
, as you mentioned, and needs to set the AVSEEK_FLAG_BACKWARD
flag again. This happens only if the difference between the target time stamp and the minimum time stamp is larger than the difference between the maximum time stamp and the target time stamp.
2187 // Fall back on old API if new is not implemented but old is.
2188 // Note the old API has somewhat different semantics.
2189 if (s->iformat->read_seek || 1) {
2190 int dir = (ts - (uint64_t)min_ts > (uint64_t)max_ts - ts ? AVSEEK_FLAG_BACKWARD : 0);
2191 int ret = av_seek_frame(s, stream_index, ts, flags | dir);
2192 if (ret<0 && ts != min_ts && max_ts != ts) {
2193 ret = av_seek_frame(s, stream_index, dir ? max_ts : min_ts, flags | dir);
2194 if (ret >= 0)
2195 ret = av_seek_frame(s, stream_index, ts, flags | (dir^AVSEEK_FLAG_BACKWARD));
2196 }
2197 return ret;
2198 }
In other words, ts - min_ts
overflows because you're using the smallest int64 value which ensures that the AVSEEK_FLAG_BACKWARD
flag will never be set. Why not use zero instead?