Problem:
omxplayer's source code calls the ffmpeg av_seek_frame()
method using the AVSEEK_FLAG_BACKWARD
flag. Although not 100% sure, I believe this seeks to the closest i-frame. Instead, I want to seek to exact locations, so I modified the source code such that the av_seek_frame()
method now uses the AVSEEK_FLAG_ANY
flag. Now, when the movie loads, I get a grey screen, generally for 1 second, during which I can hear the audio. I have tried this on multiple computers (I am actually synchronizing them, therefore, at the same time too) so it is not a n isolated incident. My guess is that seeking to non i-frames is computationally more expensive, resulting in the initial grey screen.
Question: How, using ffmpeg, can I instruct the audio to wait until the video is ready before proceeding.
Actually, AVSEEK_FLAG_BACKWARD
indicates that you want to find closest keyframe having a smaller timestamp than the one you are seeking.
By using AVSEEK_FLAG_ANY
, you get the frame that corresponds exactly to the timestamp you asked for. But this frame might not be a keyframe, which means that it cannot be fully decoded. That explains your "grey screen", that appears until the next keyframe is reached.
The solution would therefore be to seek backward using AVSEEK_FLAG_BACKWARD
and, from this keyframe, read the next frames (e.g. using av_read_frame()
) until you get to the one corresponding to your timestamp. At this point, your frame would be fully decoded, and would not appear as a "grey screen" anymore.
NOTE: It appears that, for some reason, av_seek_frame()
using AVSEEK_FLAG_BACKWARD
returns the next keyframe when the frame that I am seeking is the one directly before this keyframe. Otherwise it returns the previous keyframe (which is what I want). My solution is to change the timestamp I give to av_seek_frame()
to ensure that it will return the keyframe before the frame I am seeking.
Completing JonesV answer with some code:
void seekFrame(int FrameIndex)
{
// Seek is done on packet dts
int64_t target_dts_usecs = (int64_t)round(frameIndex * (double)_video_stream->r_frame_rate.den / _video_stream->r_frame_rate.num * AV_TIME_BASE);
// Remove first dts: when non zero seek should be more accurate
auto first_dts_usecs = (int64_t)round(_video_stream->first_dts * (double)_video_stream->time_base.num / _video_stream->time_base.den * AV_TIME_BASE);
target_dts_usecs += first_dts_usecs;
int rv = av_seek_frame(_format_ctx, -1, target_dts_usecs, AVSEEK_FLAG_BACKWARD);
if (rv < 0)
throw exception();
avcodec_flush_buffers(_codec_ctx);
}
Then you can begin decoding checking AVPacket.dts
against original target dts, computed on AVStream.time_base
. As soon as you reached the target dts, the next decoded frame should be the desired frame.