11# Source Configuration
2- SOURCE_URL = https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_720p_h264.mov
3- INPUT_VIDEO = BigBuckBunny_720p.mov
2+ SOURCE_URL = https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_720p_h264.mov
3+ INPUT_VIDEO = BigBuckBunny_720p.mov
44
5- INPUT_H264 = quarter_q_cbr .h264
6- OUT_DIR = .
7- FPS = 30
8- DURATION = 360
5+ INPUT_H264 = full_f_cbr .h264
6+ OUT_DIR = .
7+ FPS = 30
8+ DURATION = 360
99
1010# Bitrates in kbps (GStreamer x264enc uses kbit/sec)
1111BITRATE_F = 1250
1212BITRATE_H = 400
1313BITRATE_Q = 150
1414
1515# Core structural settings shared across both VBR and CBR profiles
16- # key-int-max=2147483647 == one IDR at start, pure P-frames after
1716BASE_COMMON = tune=zerolatency \
1817 bframes=0 \
1918 b-adapt=false \
2019 key-int-max=2147483647 \
2120 speed-preset=ultrafast \
2221 byte-stream=true \
2322 aud=false \
24- option-string="repeat-headers=1:open-gop=0"
23+ rc-lookahead=0 \
24+ sliced-threads=false \
25+ threads=1 \
26+ option-string="repeat-headers=1:open-gop=0:scenecut=0:intra-refresh=0"
2527
2628# Specific rate-control modifiers
27- CBR_FLAGS = pass=cbr nal-hrd=cbr
29+ CBR_FLAGS = pass=cbr nal-hrd=cbr vbv-buf-capacity=1000
2830VBR_FLAGS = # Intentionally blank. x264enc defaults to VBR/ABR when pass=cbr is omitted.
2931
30- .PHONY : all clean prepare estimate download
32+ .PHONY : all clean prepare estimate download distclean
3133
3234all : prepare $(INPUT_VIDEO ) \
3335 $(OUT_DIR)/full_f_vbr.h264 $(OUT_DIR)/full_f_cbr.h264 \
@@ -73,7 +75,7 @@ $(OUT_DIR)/full_f_cbr.h264: $(OUT_DIR)/trimmed.mp4
7375 filesrc location=$< ! \
7476 qtdemux ! h264parse ! avdec_h264 ! \
7577 videorate ! video/x-raw,framerate=$(FPS ) /1 ! \
76- x264enc $(BASE_COMMON ) $(CBR_FLAGS ) bitrate=$(BITRATE_F ) vbv-buf-capacity= $( BITRATE_F ) ! \
78+ x264enc $(BASE_COMMON ) $(CBR_FLAGS ) bitrate=$(BITRATE_F ) ! \
7779 video/x-h264,profile=baseline,stream-format=byte-stream ! \
7880 filesink location=$@
7981
@@ -96,7 +98,7 @@ $(OUT_DIR)/half_h_cbr.h264: $(OUT_DIR)/trimmed.mp4
9698 qtdemux ! h264parse ! avdec_h264 ! \
9799 videorate ! video/x-raw,framerate=$(FPS ) /1 ! \
98100 videoscale ! video/x-raw,height=360,pixel-aspect-ratio=1/1 ! \
99- x264enc $(BASE_COMMON ) $(CBR_FLAGS ) bitrate=$(BITRATE_H ) vbv-buf-capacity= $( BITRATE_H ) ! \
101+ x264enc $(BASE_COMMON ) $(CBR_FLAGS ) bitrate=$(BITRATE_H ) ! \
100102 video/x-h264,profile=baseline,stream-format=byte-stream ! \
101103 filesink location=$@
102104
@@ -119,33 +121,63 @@ $(OUT_DIR)/quarter_q_cbr.h264: $(OUT_DIR)/trimmed.mp4
119121 qtdemux ! h264parse ! avdec_h264 ! \
120122 videorate ! video/x-raw,framerate=$(FPS ) /1 ! \
121123 videoscale ! video/x-raw,height=180,pixel-aspect-ratio=1/1 ! \
122- x264enc $(BASE_COMMON ) $(CBR_FLAGS ) bitrate=$(BITRATE_Q ) vbv-buf-capacity= $( BITRATE_Q ) ! \
124+ x264enc $(BASE_COMMON ) $(CBR_FLAGS ) bitrate=$(BITRATE_Q ) ! \
123125 video/x-h264,profile=baseline,stream-format=byte-stream ! \
124126 filesink location=$@
125127
126128# ==============================================================================
127129# ANALYSIS & UTILITIES
128130# ==============================================================================
131+
132+ # Prints one line per access unit (frame) with its size in bytes.
133+ # NOTE: fakesink's per-buffer "last-message" is only printed via gst-launch's
134+ # deep-notify hook, which requires -v (verbose). -q suppresses it entirely,
135+ # so grep matches nothing and `make` reports Error 1 with no visible cause.
129136estimate :
130137 @if [ ! -f " $( INPUT_H264) " ]; then \
131138 echo " Error: Input file '$( INPUT_H264) ' not found." >&2 ; \
132139 exit 1; \
133140 fi
134- @echo " timestamp_seconds,frame_count,bitrate_kbps"
135- @ffprobe -v error -framerate $(FPS ) -i $(INPUT_H264 ) -show_entries packet=size -of compact=p=0:nk=1 | \
136- awk \
137- -v window=$(FPS ) \
138- -v fps=$(FPS ) \
139- ' BEGIN { sum=0 } \
140- { \
141- idx = NR % window; \
142- sum -= history[idx]; \
143- history[idx] = $$ 1; \
144- sum += $$ 1; \
145- if (NR > = window) { \
146- timestamp = NR / fps; \
147- printf " %.3f,%d,%.2f\n" , timestamp, NR, (sum * 8) / 1000; \
148- } \
141+ @gst-launch-1.0 -v \
142+ filesrc location=$(INPUT_H264 ) ! \
143+ h264parse ! \
144+ fakesink silent=false 2>&1 | \
145+ grep -oP ' \(\K[0-9]+(?=\s*bytes)' || \
146+ { echo " Error: no frame sizes captured — check gst-launch output format." >&2 ; exit 1; }
147+
148+ # Same as estimate, but also prints stdev/CV, and separates the largest frame
149+ # (likely a keyframe) from the steady-state P-frames for a cleaner
150+ # apples-to-apples "tightness" comparison against Chrome's trace.
151+ estimate-summary :
152+ @if [ ! -f " $( INPUT_H264) " ]; then \
153+ echo " Error: Input file '$( INPUT_H264) ' not found." >&2 ; \
154+ exit 1; \
155+ fi
156+ @gst-launch-1.0 -v \
157+ filesrc location=$(INPUT_H264 ) ! \
158+ h264parse ! \
159+ fakesink silent=false 2>&1 | \
160+ grep -oP ' \(\K[0-9]+(?=\s*bytes)' | \
161+ awk ' { \
162+ print; \
163+ sizes[n]=$$ 1; sum+=$$ 1; n++; \
164+ if ($$ 1 > max) { max=$$ 1; maxidx=n-1 } \
165+ } \
166+ END { \
167+ if (! n) { print " no frames captured" > " /dev/stderr" ; exit 1 } \
168+ mean = sum/n; \
169+ for (i=0; i< n; i++) { d=sizes[i]-mean; ss+=d* d } \
170+ stdev = sqrt(ss/n); \
171+ sum2=0; n2=0; \
172+ for (i=0; i< n; i++) { if (i! =maxidx) { sum2+=sizes[i]; n2++ } } \
173+ mean2 = n2 ? sum2/n2 : 0; \
174+ ss2=0; \
175+ for (i=0; i< n; i++) { if (i! =maxidx) { d=sizes[i]-mean2; ss2+=d* d } } \
176+ stdev2 = n2 ? sqrt(ss2/n2) : 0; \
177+ printf " ---\n" ; \
178+ printf " frames=%d mean=%.1f stdev=%.1f cv=%.1f%%\n" , n, mean, stdev, (stdev/mean)* 100; \
179+ printf " largest frame: idx=%d size=%d (likely keyframe)\n" , maxidx, max; \
180+ printf " excl. largest: frames=%d mean=%.1f stdev=%.1f cv=%.1f%%\n" , n2, mean2, stdev2, n2? (stdev2/mean2)* 100:0; \
149181 }'
150182
151183clean :
0 commit comments