Impact of adding additional keyframes on video size

The following is a quick test on looking at the impact on filesize of adding additional keyframes to an encode for the purpose of making it more suitable for segmentation in adaptive bitrate delivery.

The following shows that on a very hard to encode source file (reflections on water) that adding a keyframe every 50 frames (2 seconds on 25 fps source) that the overall increase in size was only 96k (approx 1%).

Note that the video is 1153 frames long so we would expect to have at least 23 key frames given a requested keyframe interval of 50. The source also had very few traditional scene changes which is reflected by the x264 generated number of keyframes of only 6 when left to only adding keyframes on scenecut.

Sample command to show GOP / I frame structure using ffprobe:

ffprobe -select_streams v:0 -show_frames goptest.mov |grep key_frame|less

File with x264 selected key frames

Encode settings

/usr/local/bin/ffmpeg -y -i goptest.mov -codec:v libx264 -b:v 6000K -s 1920x1080 -preset slower -tune film -me_range 24 -bufsize 50000K -maxrate 50000K -refs 4 -profile:v high -level 4.1 -threads 0 -sn 'goptest_1080p.mp4'

ffmpeg encode results

video:34160kB audio:538kB subtitle:0 global headers:0kB muxing overhead 0.077031%
[libx264 @ 0x7fc873846800] frame I:6     Avg QP:16.50  size: 25800
[libx264 @ 0x7fc873846800] frame P:1131  Avg QP:23.22  size: 30785
[libx264 @ 0x7fc873846800] frame B:16    Avg QP: 6.54  size:   354
[libx264 @ 0x7fc873846800] consecutive B-frames: 98.0%  0.3%  0.3%  1.4%
[libx264 @ 0x7fc873846800] mb I  I16..4: 66.5% 32.0%  1.5%
[libx264 @ 0x7fc873846800] mb P  I16..4: 49.4% 40.7%  1.3%  P16..4:  6.4%  0.4%  0.1%  0.0%  0.0%    skip: 1.6%
[libx264 @ 0x7fc873846800] mb B  I16..4:  0.2%  0.1%  0.0%  B16..8:  9.5%  0.0%  0.0%  direct: 0.1%  skip:90.2%  L0:68.0% L1:31.9% BI: 0.1%
[libx264 @ 0x7fc873846800] final ratefactor: 22.37
[libx264 @ 0x7fc873846800] 8x8 transform intra:44.5% inter:93.6%
[libx264 @ 0x7fc873846800] direct mvs  spatial:62.5% temporal:37.5%
[libx264 @ 0x7fc873846800] coded y,uvDC,uvAC intra: 21.3% 67.9% 13.0% inter: 17.0% 39.8% 0.6%
[libx264 @ 0x7fc873846800] i16 v,h,dc,p: 35% 25% 18% 22%
[libx264 @ 0x7fc873846800] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 14% 19% 36%  3%  5%  5% 10%  3%  5%
[libx264 @ 0x7fc873846800] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 18% 19% 44%  1%  6%  4%  6%  1%  2%
[libx264 @ 0x7fc873846800] i8c dc,h,v,p: 43% 33% 19%  5%
[libx264 @ 0x7fc873846800] Weighted P-Frames: Y:0.3% UV:0.2%
[libx264 @ 0x7fc873846800] ref P L0: 62.8%  3.6% 17.0%  9.3%  7.3%
[libx264 @ 0x7fc873846800] ref B L0: 99.4%  0.6%
[libx264 @ 0x7fc873846800] ref B L1: 99.7%  0.3%
[libx264 @ 0x7fc873846800] kb/s:6067.44

File with keyint=50

Encode settings

Note that just using keyint on it’s own doesn’t necessarily mean that keyframes will be forced every 50 frames, see below for alternative option by switching x264 scenecut off

/usr/local/bin/ffmpeg -y -i goptest.mov -codec:v libx264 -b:v 6000K -s 1920x1080 -preset slower -tune film -me_range 24 -bufsize 50000K -maxrate 50000K -refs 4 -profile:v high -level 4.1 -x264opts keyint=50 -threads 0 -sn 'goptest_1080p_keyint50.mp4'

ffmpeg encode results

video:34256kB audio:538kB subtitle:0 global headers:0kB muxing overhead 0.077043%
[libx264 @ 0x7fe631846800] frame I:26    Avg QP:19.07  size: 41326
[libx264 @ 0x7fe631846800] frame P:1112  Avg QP:23.33  size: 30574
[libx264 @ 0x7fe631846800] frame B:15    Avg QP: 6.27  size:   300
[libx264 @ 0x7fe631846800] consecutive B-frames: 98.1%  0.3%  0.5%  1.0%
[libx264 @ 0x7fe631846800] mb I  I16..4: 52.5% 43.3%  4.2%
[libx264 @ 0x7fe631846800] mb P  I16..4: 49.8% 40.4%  1.3%  P16..4:  6.3%  0.4%  0.1%  0.0%  0.0%    skip: 1.8%
[libx264 @ 0x7fe631846800] mb B  I16..4:  0.2%  0.1%  0.0%  B16..8:  2.8%  0.0%  0.0%  direct: 0.0%  skip:96.8%  L0:12.8% L1:87.0% BI: 0.2%
[libx264 @ 0x7fe631846800] final ratefactor: 22.35
[libx264 @ 0x7fe631846800] 8x8 transform intra:44.1% inter:94.0%
[libx264 @ 0x7fe631846800] direct mvs  spatial:60.0% temporal:40.0%
[libx264 @ 0x7fe631846800] coded y,uvDC,uvAC intra: 21.3% 67.2% 13.0% inter: 16.4% 38.5% 0.6%
[libx264 @ 0x7fe631846800] i16 v,h,dc,p: 35% 25% 18% 22%
[libx264 @ 0x7fe631846800] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 14% 19% 37%  3%  5%  5% 10%  3%  5%
[libx264 @ 0x7fe631846800] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 19% 20% 43%  1%  5%  4%  6%  1%  2%
[libx264 @ 0x7fe631846800] i8c dc,h,v,p: 43% 33% 19%  5%
[libx264 @ 0x7fe631846800] Weighted P-Frames: Y:0.3% UV:0.2%
[libx264 @ 0x7fe631846800] ref P L0: 63.5%  3.6% 16.9%  9.1%  7.0%
[libx264 @ 0x7fe631846800] ref B L0: 89.3% 10.7%
[libx264 @ 0x7fe631846800] kb/s:6084.54

Whith scene cut disabled

Note that with scenecut disable there are actually less key frames than with scenecut enabled, which makes sense as opposed to just dropping a key frame in every 50 frames it would also add them when a scene change is detetecd

/usr/local/bin/ffmpeg -y -i goptest.mov -codec:v libx264 -b:v 6000K -s 1920x1080 -preset slower -tune film -me_range 24 -bufsize 50000K -maxrate 50000K -refs 4 -profile:v high -level 4.1 -x264opts keyint=50:no-scenecut -threads 0 -sn 'goptest_1080p_keyint50_noscenecut.mp4'
frame= 1153 fps=1.1 q=-1.0 Lsize= 34821kB time=00:00:46.04 bitrate=6195.7kbits/s dup=15 drop=0 
video:34255kB audio:538kB subtitle:0 global headers:0kB muxing overhead 0.077056%
[libx264 @ 0x7f9b9b846800] frame I:24 Avg QP:19.70 size: 45538
[libx264 @ 0x7f9b9b846800] frame P:1112 Avg QP:23.33 size: 30551
[libx264 @ 0x7f9b9b846800] frame B:17 Avg QP: 7.33 size: 643
[libx264 @ 0x7f9b9b846800] consecutive B-frames: 97.9% 0.2% 0.5% 1.4%
[libx264 @ 0x7f9b9b846800] mb I I16..4: 51.0% 45.6% 3.4%
[libx264 @ 0x7f9b9b846800] mb P I16..4: 49.8% 40.4% 1.3% P16..4: 6.3% 0.4% 0.1% 0.0% 0.0% skip: 1.7%
[libx264 @ 0x7f9b9b846800] mb B I16..4: 0.2% 0.1% 0.0% B16..8: 8.3% 0.0% 0.0% direct: 0.5% skip:90.8% L0:73.7% L1:26.2% BI: 0.0%
[libx264 @ 0x7f9b9b846800] final ratefactor: 22.35
[libx264 @ 0x7f9b9b846800] 8x8 transform intra:44.2% inter:94.3%
[libx264 @ 0x7f9b9b846800] direct mvs spatial:64.7% temporal:35.3%
[libx264 @ 0x7f9b9b846800] coded y,uvDC,uvAC intra: 21.2% 67.2% 12.9% inter: 16.5% 38.6% 0.5%
[libx264 @ 0x7f9b9b846800] i16 v,h,dc,p: 35% 25% 18% 22%
[libx264 @ 0x7f9b9b846800] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 14% 19% 37% 3% 5% 5% 10% 3% 5%
[libx264 @ 0x7f9b9b846800] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 18% 20% 43% 1% 5% 4% 6% 1% 2%
[libx264 @ 0x7f9b9b846800] i8c dc,h,v,p: 43% 33% 19% 5%
[libx264 @ 0x7f9b9b846800] Weighted P-Frames: Y:0.3% UV:0.2%
[libx264 @ 0x7f9b9b846800] ref P L0: 63.3% 3.6% 16.9% 9.2% 7.0%
[libx264 @ 0x7f9b9b846800] ref B L0: 98.5% 1.5% 0.0%
[libx264 @ 0x7f9b9b846800] ref B L1: 99.6% 0.4%
[libx264 @ 0x7f9b9b846800] kb/s:6084.43

 

 

Creating a timelapse from a sequence of images on GOPRO with ffmpeg

If you have set your Go Pro to capture a sequence of images then here are some easy steps to convert them into a movie with ffmpeg.

My GoPro Her0 3 Black numbers the videos in the following format:

G0010001.JPG

Where the first 1 represents the batch of photos, e.g. if I took 3 timelapses on the same SD card before formatting the sequences would look like:

G0010001.JPG

G0020001.JPG

G0020001.JPG

To use with ffmpeg you need to use the image input filter and unless your image happens to start at 1 (almost never happens to me) you also need to specify the -start_number, especially if you decide to manually delete a few images at the start as I sometimes do.

If the first image was G0010582.JPG you would use the following command:

ffmpeg -f image2 -start_number 002 -i G0010%03d.JPG -vcodec libx264 -b:v 5000k -s 1920×1080 timelapse_1080P.mp4

Note that with this setting I am outputting a 5Mbit video, you can drop this all together if you want to get a lot higher quality or manually up it to 5-8Mbit for decent quality HD video while maintaining a reasonable size.

I also find ffplay is quite good for a quick view on how it looks:

>ffplay timelapse_1080P.mp4

 

Useful script for multi-rate HLS output from ffmpeg

#!/bin/bash
VIDSOURCE=”$1RESOLUTION=”854x480”
BITRATE1=”800000BITRATE2=”600000BITRATE3=”400000”
 
AUDIO_OPTS=”-c:a libfaac -b:a 160000 -ac 2VIDEO_OPTS1=”-s $RESOLUTION -c:v libx264 -b:v $BITRATE1 -vprofilebaseline -preset medium -x264opts level=41VIDEO_OPTS2=”-s $RESOLUTION -c:v libx264 -b:v $BITRATE2 -vprofile
baseline -preset medium -x264opts level=41VIDEO_OPTS3=”-s $RESOLUTION -c:v libx264 -b:v $BITRATE3 -vprofile
baseline -preset medium -x264opts level=41OUTPUT_HLS=”-hls_time 3 -hls_list_size 10 -hls_wrap 30 -start_number 1ffmpeg -i$VIDSOURCE-y -threads 4
              $AUDIO_OPTS $VIDEO_OPTS1 $OUTPUT_HLS stream_hi.m3u8
              $AUDIO_OPTS $VIDEO_OPTS2 $OUTPUT_HLS stream_med.m3u8
              $AUDIO_OPTS $VIDEO_OPTS3 $OUTPUT_HLS stream_low.m3u8

Credit to: jeisom@gmail.com

Using ffprobe to evaluate keyframes

Note: It appears this needs to be updated as ffprobe no longer outputs pict_type

ffprobe is quite a good little tool for evaluating keyframes in a video and here is a simple statement that can show you an easy way to view the GOP structure of a video:

ffprobe -show_frames out_gop12.ts |grep ‘media_type=video|pict_type|coded_picture_number’|less

Now on doing this I am seeing that ffmpeg and x264 aren’t necessarily following the GOP structure that I have set which has a max keyframe distance of 12.

Sample output:

media_type=video

pict_type=I

coded_picture_number=0

media_type=video

pict_type=P

coded_picture_number=1

media_type=video

pict_type=I

coded_picture_number=2

media_type=video

pict_type=P

coded_picture_number=3

media_type=video

pict_type=P

coded_picture_number=4

media_type=video

pict_type=P

coded_picture_number=5

media_type=video

pict_type=B

coded_picture_number=7

media_type=video

pict_type=P

coded_picture_number=6

media_type=video

pict_type=B

coded_picture_number=10

media_type=video

pict_type=B

coded_picture_number=9

media_type=video

pict_type=B

coded_picture_number=11

media_type=video

pict_type=P

coded_picture_number=8

media_type=video

pict_type=B

coded_picture_number=14

Here is a good post with some more info on the topic
I have also created a little Python script that will process the output to show you when keyframes occur:

#!/usr/bin/python

importos

import re

importsys

import subprocess

#infile = sys.argv[1]

infile= “/tmp/ffmpeg_main_profile/out_gop12.ts

lines_past_type = 20

line_count = 0

print_line = False

gop = 12

frame = 0

found_iframe = False

cmd = “ffprobe -show_frames %s” % infile

# -print_format json

process = subprocess.Popen(cmd,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE

,cwd=target_dir)

whileTrue:

line = process.stdout.readline()

if line != ”:

#the real code does filtering here

#print line.rstrip()

if (line.rstrip()==“media_type=video”):

print_line = True

if (print_line and (line_count < lines_past_type)):

#print line.rstrip()

if re.match(r‘pict_type=I.*’,line):

found_iframe = True

#print line.rstrip()

match = re.match(r‘coded_picture_number=(.*)’,line)

if (match):

frame =  match.group(1)

if (found_iframe):

print “I Frame: %s” % frame

div12 = float(frame) / 12

found_iframe = False

line_count+=1

if (line_count >= lines_past_type):

print_line = False

line_count = 0

else:

break