]> git.sesse.net Git - x264/blob - tools/regression-test.pl
Major API change: encapsulate NALs within libx264
[x264] / tools / regression-test.pl
1 #!/bin/env perl
2
3 # regression-test.pl: tests different versions of x264
4 # by Alex Izvorski & Loren Merrit 2007
5 # GPL
6
7 $^W=1;
8
9 use Getopt::Long;
10 use File::Path;
11 use File::Copy;
12 use File::Basename;
13
14 # prerequisites: 
15 # - perl > 5.8.0
16 # - svn
17 # - net access
18 # - linux/unix
19 # - gcc
20
21 # the following require a make testclean
22 # - changing x264 option sets and or adding/removing option sets
23 # - changing configure options
24 # - changing CFLAGS or other variables that affect compilation
25 # - a newer head revision
26
27 # the following do not require a make testclean, but may cause some tests to be rerun unnecessarily:
28 # - adding/removing input files 
29 # - adding/removing versions
30
31 @versions = ();
32 @input_files = ();
33 @option_sets = ();
34
35 GetOptions ("version=s" => \@versions,
36             "input=s" => \@input_files,
37             "options=s" => \@option_sets,
38             );
39
40 # TODO check inputs
41 # TODO some way to give make options
42 # TODO some way to give configure options
43
44 if (scalar(@versions) == 0)
45 {
46     @versions = ('rHEAD', 'current');
47 }
48
49 if (scalar(@option_sets) == 0 ||
50     scalar(@input_files) == 0)
51 {
52     print "Regression test for x264\n";
53     print "Usage:\n  perl tools/regression-test.pl --version=623 --version=624 --input=football_720x480p.yuv --input=foreman_352x288p.yuv --options='--me=dia --subme=2 --no-cabac'\n";
54     print "Any number of versions, option sets, and input files may be given.\n";
55     print "Versions may be any svn revision, a comma-separated list of revisions, or 'current' for the version in the current directory.\n";
56     exit;
57 }
58
59 mkpath("test");
60
61 @versions = map { split m![,\s]\s*! } @versions;
62
63 foreach my $version (@versions)
64 {
65     $version =~ s!^head$!rHEAD!i;
66     $version =~ s!^current$!current!i;
67     $version =~ s!^(\d+)$!r$1!;
68
69     if (-e "test/x264-$version/x264" && ($version ne "current"))
70     {
71         print("have version: $version\n");
72         next;
73     }
74     print("building version: $version\n");
75     if ($version eq "current")
76     {
77         system("(./configure && make) &> test/build.log");
78         mkpath("test/x264-$version");
79         if (! -e "x264") { print("build failed \n"); exit 1; }
80         copy("x264", "test/x264-$version/x264");
81         chmod(0755, "test/x264-$version/x264");
82         next;
83     }
84     system("svn checkout -$version svn://svn.videolan.org/x264/trunk/ test/x264-$version >/dev/null");
85     chdir("test/x264-$version");
86     system("(./configure && make) &> build.log");
87     chdir("../..");
88     if (! -e "test/x264-$version/x264") { print("build failed \n"); exit 1; }
89 }
90
91 $any_diff = 0;
92 foreach my $i (0 .. scalar(@option_sets)-1)
93 {
94     $opt = $option_sets[$i];
95     print("options: $opt \n");
96     foreach my $j (0 .. scalar(@input_files)-1)
97     {
98         my $file = $input_files[$j];
99         print("input file: $file \n");
100
101         my $outfile = basename($file);
102         $outfile =~ s!\.yuv$!!;
103         $outfile = "test/opt$i-$outfile$j";
104
105         foreach my $k (0 .. scalar(@versions)-1)
106         {
107             my $version = $versions[$k];
108
109             if (-e "$outfile-$version.log" && ($version ne "current"))
110             {
111                 print("have results for version: $version\n");
112             }
113             else
114             {
115                 print("running version: $version \n");
116                 # verbose option is required for frame-by-frame comparison
117                 system("test/x264-$version/x264 --verbose $opt $file -o $outfile-$version.264 > $outfile-$version.log 2>&1");
118             }
119
120             # TODO check for crashes
121             # TODO if (read_file("$outfile-$version.log") =~  m!could not open input file!) ...
122             # TODO check decompression with jm
123             # TODO dump (and check) frames
124
125             if ($k > 0)
126             {
127                 my $baseversion = $versions[$k - 1];
128                 print("comparing $version with $baseversion: ");
129                 $is_diff = 0;
130                 $is_diff ||= compare_logs("$outfile-$version.log", "$outfile-$baseversion.log");
131                 $is_diff ||= compare_raw264("$outfile-$version.264", "$outfile-$baseversion.264");
132                 if (! $is_diff) { print("identical \n"); }
133                 $any_diff ||= $is_diff;
134             }
135         }
136     }
137 }
138
139 print "\n";
140 if (! $any_diff) { print "no differences found\n"; }
141 else { print "some differences found\n"; exit 1; }
142
143
144 sub compare_logs
145 {
146     my ($log1, $log2) = @_;
147
148     my $logdata1 = read_file($log1);
149     my $logdata2 = read_file($log2);
150
151     # FIXME comparing versions with different log output format will fail
152     $logdata1 = join("\n", grep { m!frame=! } split(m!\n!, $logdata1));
153     $logdata2 = join("\n", grep { m!frame=! } split(m!\n!, $logdata2));
154
155     my $is_diff = 0;
156     if ($logdata1 ne $logdata2)
157     {
158         print("log files differ \n");
159         $is_diff = 1;
160     }
161
162     my $stats1 = parse_log_overall_stats($log1);
163     my $stats2 = parse_log_overall_stats($log2);
164
165     if ($stats1->{psnr_y} != $stats2->{psnr_y})
166     {
167         printf("psnr change: %+f dB \n", $stats1->{psnr_y} - $stats2->{psnr_y});
168         $is_diff = 1;
169     }
170     if ($stats1->{bitrate} != $stats2->{bitrate})
171     {
172         printf("bitrate change: %+f kb/s \n", $stats1->{bitrate} - $stats2->{bitrate});
173         $is_diff = 1;
174     }
175
176     #arbitrarily set cutoff to 3% change
177     #$speed_change_min = 0.03;
178     #if (abs($stats1->{fps} - $stats2->{fps}) > $speed_change_min * ($stats1->{fps} + $stats2->{fps})/2)
179     #{
180     #    printf("speed change: %+f fps \n", $stats1->{fps} - $stats2->{fps}); 
181     #    $is_diff = 1;
182     #}
183
184     return $is_diff;
185
186     # TODO compare frame by frame PSNR/SSIM, record improved or unimproved ranges
187     #parse_log_frame_stats($log1);
188     #parse_log_frame_stats($log2);
189
190     # TODO compare actual run times
191 }
192
193 sub compare_raw264
194 {
195     my ($raw1, $raw2) = @_;
196     
197     # FIXME this may use a lot of memory
198     my $rawdata1 = read_file($raw1);
199     my $rawdata2 = read_file($raw2);
200     
201     # remove first NAL, it is a version-specific SEI NAL
202     my $pat = chr(0).chr(0).chr(1);
203     $rawdata1 =~ s!^.*?$pat.*?$pat!$pat!;
204     $rawdata2 =~ s!^.*?$pat.*?$pat!$pat!;
205
206     if ($rawdata1 ne $rawdata2)
207     {
208         print("compressed files differ \n");
209         return 1;
210     }
211
212     return 0;
213 }
214
215 sub parse_log_frame_stats
216 {
217     my ($log) = @_;
218     my $logtext = read_file($log);
219
220     my @frames = ();
221     while ($logtext =~ m!x264 \[debug]: (frame=.*)!g)
222     {
223         my $line = $1;
224         if ($line !~ m!frame=\s*(\d+)\s* QP=\s*(\d+)\s* NAL=(\d+)\s* Slice:(\w)\s* Poc:(\d+)\s* I:(\d+)\s* P:(\d+)\s* SKIP:(\d+)\s* size=(\d+) bytes PSNR Y:(\d+\.\d+) U:(\d+\.\d+) V:(\d+\.\d+)!)
225         {
226             print "error: unparseable log line: $line \n"; next;
227         }
228         
229         my $frame = 
230             +{
231                 num=>$1,
232                 qp=>$2,
233                 nal=>$3,
234                 slice=>$4,
235                 poc=>$5,
236                 count_i=>$6,
237                 count_p=>$7,
238                 count_skip=>$8,
239                 size=>$9,
240                 psnr_y=>$10,
241                 psnr_u=>$11,
242                 psnr_v=>$12,
243             };
244
245         if ($line =~ m!SSIM Y:(\d+\.\d+)!)
246         {
247             $frame->{ssim} = $1;
248         }
249
250         push(@frames, $frame);
251     }
252
253     return @frames;
254 }
255
256 sub parse_log_overall_stats
257 {
258     my ($log) = @_;
259     my $logtext = read_file($log);
260
261     if ($logtext !~ m!x264 \[info\]: PSNR Mean Y:(\d+\.\d+) U:(\d+\.\d+) V:(\d+\.\d+) Avg:(\d+\.\d+) Global:(\d+\.\d+) kb/s:(\d+\.\d+)!)
262     {
263         print "error: unparseable log summary info \n"; return +{};
264     }
265     my $stats = 
266         +{ 
267             psnr_y=>$1,
268             psnr_u=>$2,
269             psnr_v=>$3,
270             psnr_avg=>$4,
271             psnr_global=>$5,
272             bitrate=>$6,
273         };
274     
275     if ($logtext !~ m!encoded (\d+) frames, (\d+\.\d+) fps!)
276     {
277         print "error: unparseable log summary info \n"; return +{};
278     }
279     $stats->{num_frames} = $1;
280     $stats->{fps} = $2;
281
282     return $stats;
283 }
284
285 sub read_file
286 {
287     my ($file) = @_;
288     open(F, $file) || die "could not open $file: $!";
289     undef $/;
290     my $data = <F>;
291     $/ = "\n";
292     close(F);
293     return $data;
294 }
295
296 sub write_file
297 {
298     my ($file, $data) = @_;
299     open(F, ">".$file) || die "could not open $file: $!";
300     print F $data;
301     close(F);
302 }