Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using avconv to get a single frame from h264 video at set time

I want to use avconv to get a single image at a specified time out of a video file.

I've read just enough about libav to think I know what I'm doing, but not enough to actually know.

I've tried:

avconv -ss 00:00:01.786 -r 25 -i input_video.h264 -frames 1 output_image.jpg

as well as using t to avoid the 'frames' parameter:

avconv -ss 00:00:01.786 -r 25 -i input_video.h264 -t 0.01 output_image.jpg

and passing in seconds rather than using the hh:mm:ss.xxx format:

avconv -ss 1.786 -r 25 -i input_video.h264 -t 0.01 output_image.jpg

What I see is when ss is set to 0 (that's '0', '00:00:00.000', '0.0', etc.) the output_image is just the first frame of the video. As expected.

Any other value for ss - even 0.0001 - gives me the last frame of the video.

I'm using the latest avconv from the Raspbian wheezy repo. This behaviour feels like a bug to me, but I don't know the other intricacies of video streaming well enough to be sure.

Does anyone have any idea what I'm doing wrong?



Bonus question: I'd actually like to get a whole bunch of these images out of the same video. Stringing commands together seemed to work before, ie:

avconv -ss 1.786 -r 25 -i input_video.h264 -t 0.01 output_image1.jpg 
-ss 3.454 -r 25 -i input_video.h264 -t 0.01 output_image2.jpg
-ss 5.823 -r 25 -i input_video.h264 -t 0.01 output_image3.jpg
-ss etc,etc.

but I switched back to single images to debug this problem. Assuming the first issue gets solved, is that the best way to structure this command or is there a better one?

like image 329
Samson Mustang Avatar asked Nov 01 '22 18:11

Samson Mustang


1 Answers

Edit: As Mulvya noted, ffmpeg will always seek accurately with -ss. This issue is exclusive to avconv.

If you specify the -ss option before the -i option then avconv will inaccurately calculate the frame position.

You need to specify the -i option first so that avconv knows it needs to "seek" through the stream until it accurately finds the correct timestamp.

Also, your example time 1.786 is not aligned to the frames per second -r 25 that you specify in your example.

Since 1/25=0.04 any value for -ss should be divisible by 0.04 in order to specify an individual frame precisely.

The following should get the 46th frame of your video:

avconv -i input_video.h264 -r 25 -ss 1.8 -frames:v 1 output_image.jpg

If you want to get a specific frame by it's index then you will need to employ bc:

avconv -i input_video.h264 -r 25 -ss 0$(echo "scale=2;1000/25" | bc -l) -frames:v 1 output_image.jpg

Where 1000 is the 1001st frame of the video (since 0/25 is the 1st frame).

Note that unlike @hamboy75's example we pass -l (lower case L) to bc so that it performs a floating point calculation (and doesn't round to the nearest integer). scale=2 is used to produce a number accurate to 2dp.

Also note that bc has the "feature" of outputting numbers less than 1 without the leading zero (i.e. .04) which avconv does not understand. Therefore we also need to insert a leading zero prefixed to the calculation 0$()

When using this command you will get output that looks like the following

frame=    0 fps=  0 q=0.0 size=       0kB time=10000000000.00 bitrate=   0.0kbit
frame=    0 fps=  0 q=0.0 size=       0kB time=10000000000.00 bitrate=   0.0kbit
frame=    0 fps=  0 q=0.0 size=       0kB time=10000000000.00 bitrate=   0.0kbit
frame=    0 fps=  0 q=0.0 size=       0kB time=10000000000.00 bitrate=   0.0kbit
frame=    0 fps=  0 q=0.0 size=       0kB time=10000000000.00 bitrate=   0.0kbit

This is because avconv is seeking through the file to find the specific frame you requested accurately. So frames with a larger index will take longer to extract because avconv will always "seek" through the stream from the stream's beginning.

It may therefore be more desirable to extract a range of frames:

avconv -i input_video.h264 -r 25 -ss 0$(echo "scale=2;7500/25" | bc -l) -t 0$(echo "scale=2;250/25" | bc -l) output_image_%04d.jpg

This example extracts 10 seconds worth of frames from 5 minutes within the video. Of course you could also just use:

avconv -i input_video.h264 -r 25 -ss 300.0 -t 10.0 | bc -l) output_image_%04d.jpg

However, remember that for any duration less than a second the value should be divisible by the frame rate for the video (i.e. 0.04 for 25 fps).

The images for each frame will be named output_image_0001.jpg, output_image_0002.jpg, output_image_0003.jpg, etc. If you are looking to perform image comparison on the extracted frames you may want to consider using png over jpg for greater fidelity.

Note that if you specify a frame index larger than the number of frames present in the video, avconv will simply extract the last frame it finds. You may want to calculate the number of frames in a video by looking at the Duration: part of the output of avconv -i input_video.h264.

like image 67
richardjsimkins Avatar answered Dec 29 '22 06:12

richardjsimkins