Jack Edmonds
Jack Edmonds

Reputation: 33161

ffmpeg av_seek_frame

I am attempting to seek in a movie using ffmpeg's av_seek_frame method however I'm having the most trouble determining how to generate a time-stamp to seek to. Assuming I want to seek x amount of frames either forward or backward and I know what frame the movie is currently on, how would I go about doing this?

Upvotes: 18

Views: 24577

Answers (3)

Asad-ullah Khan
Asad-ullah Khan

Reputation: 1832

Not sure if this is super accurate, but the following is very simple and seems to work:

int n_seconds = 10; // seek forward 10 seconds
// time_base is in seconds, eg. the time base may be 1/1000th of a second,
// so just multiply by the reciprocal (den = denominator, num = numerator)
int64_t ts = av_rescale(
    n_seconds,
    format_ctx->streams[video_stream_index]->time_base.den,
    format_ctx->streams[video_stream_index]->time_base.num
);
// even though it mentions in docs that you shouldn't use this because it is a
// work in progress, it's been around for more than a decade now, ffplay/ffmpeg/ffprobe
// all use it...it is the most consistent and easiest to use. the way I am using
// it here is to seek to the nearest keyframe (not frame!). I would not recommend
// using it in any other way:
// eg. AVSEEK_FLAG_ANY/FRAME/BACKWARD (BACKWARD is ignored anyways)
// 0 as flag seeks to keyframes only. I have set the max timestamp to the same value so
// that we only look for nearest keyframes behind us
int err = avformat_seek_file(pFormatContext, video_stream_index, 0, ts, ts, 0);

This seeks to the nearest keyframe! Which could be pretty far from you want. However, it will only ever be behind the target timestamp, so you can just av_read_frame until you get to where you want, using AVframe->pts * AVStream->timebase to calculate what time the frame is (use av_rescale to do this).

Also note that if you need to seek backward (that is a frame behind one you already read with av_read_frame) or you are going to be calling av_read_frame more than once on a frame in general, you have to send/receive the packet/frame with avcodec_send_packet and avcodec_receive_frame respectively, otherwise the codec context will be out of sync (I think that's the issue?). You can't just read packets blankly. You also should avcodec_flush_buffers after you seek to a new location that's behind where you were reading from (you should probably just call it every time you seek, but I am not sure about performance).

Doc reference:

int avformat_seek_file (..., int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags)

Upvotes: 2

Hugo Maxwell
Hugo Maxwell

Reputation: 763

Here is how i did it:

// Duration of one frame in AV_TIME_BASE units
int64_t timeBase;

void open(const char* fpath){
    ...
    timeBase = (int64_t(pCodecCtx->time_base.num) * AV_TIME_BASE) / int64_t(pCodecCtx->time_base.den);
    ...
}

bool seek(int frameIndex){

    if(!pFormatCtx)
        return false;

    int64_t seekTarget = int64_t(frameIndex) * timeBase;

    if(av_seek_frame(pFormatCtx, -1, seekTarget, AVSEEK_FLAG_ANY) < 0)
        mexErrMsgTxt("av_seek_frame failed.");

}

The AVSEEK_FLAG_ANY enables seeking to every frame and not just keyframes.

Upvotes: 19

David Siaw
David Siaw

Reputation:

Simple answer: You should have an AVFormatContext object lying around. Its duration property tells you how long your file is in terms of the time-stamp multiplied by 1000 that can be used in av_seek_frame, so treat it as 100%. Then you can calculate how far into the video you want to seek to.

if you want to go forward one frame, simply call av_read_frame and avcodec_decode_video until it fills the got_picture_ptr with a non-zero value. Before calling avcodec_decode_video make sure the packet from av_read_frame is from the video stream. avcodec_decode_video will then fill in the AVFrame structure which you can use to do anything with.

Upvotes: 10

Related Questions