1#!/usr/bin/perl 2 3use warnings; 4use strict; 5 6use Digest::MD5 qw(md5_hex); 7use Test::More tests => 118; 8 9my $TWOLAME_CMD = $ENV{TWOLAME_CMD} || "../frontend/twolame"; 10my $STWOLAME_CMD = $ENV{STWOLAME_CMD} || "../simplefrontend/stwolame"; 11die "Error: twolame command not found: $TWOLAME_CMD" unless (-e $TWOLAME_CMD); 12die "Error: stwolame command not found: $STWOLAME_CMD" unless (-e $STWOLAME_CMD); 13 14 15my $encoding_parameters = [ 16 { 17 # Test Case 1 (default settings) 18 'input_filename' => 'testcase-44100.wav', 19 'input_md5sum' => 'f50499fded70a74c810dbcadb3f28062', 20 'bitrate' => 192, 21 'samplerate' => 44100, 22 'version' => '1', 23 'mode' => 'stereo', 24 'psycmode' => 3, 25 'original' => 1, 26 'extension' => 0, 27 'copyright' => 0, 28 'padding' => 0, 29 'protect' => 0, 30 'deemphasis' => 'n', 31 'total_frames' => 22, 32 'total_bytes' => 13772, 33 'total_samples' => 25344, 34 'output_md5sum' => '956f85e3647314750a1d3ed3fbf81ae3' 35 }, 36 { 37 # Test Case 2 (toolame 0.2l default settings) 38 'input_filename' => 'testcase-44100.wav', 39 'input_md5sum' => 'f50499fded70a74c810dbcadb3f28062', 40 'bitrate' => 192, 41 'samplerate' => 44100, 42 'version' => '1', 43 'mode' => 'joint', 44 'psycmode' => 1, 45 'original' => 0, 46 'extension' => 0, 47 'copyright' => 0, 48 'padding' => 1, 49 'protect' => 0, 50 'deemphasis' => 'n', 51 'total_frames' => 22, 52 'total_bytes' => 13792, 53 'total_samples' => 25344, 54 'output_md5sum' => 'b7937b5f2ea56460afaeee6f1a5dc77f' 55 }, 56 { 57 # Test Case 3 (MPEG-2 test) 58 'input_filename' => 'testcase-22050.wav', 59 'input_md5sum' => 'a5ec3077c2138a1023bcd980aec8e4b4', 60 'bitrate' => 32, 61 'samplerate' => 22050, 62 'version' => '2', 63 'mode' => 'mono', 64 'psycmode' => 4, 65 'original' => 0, 66 'extension' => 0, 67 'copyright' => 1, 68 'padding' => 1, 69 'protect' => 0, 70 'deemphasis' => '5', 71 'total_frames' => 11, 72 'total_bytes' => 2298, 73 'total_samples' => 12672, 74 'output_md5sum' => '336027628adcfd5f0bb02863920fd6f1' 75 }, 76 { 77 # Test Case 4 (error protection test) 78 'input_filename' => 'testcase-44100.wav', 79 'input_md5sum' => 'f50499fded70a74c810dbcadb3f28062', 80 'bitrate' => 192, 81 'samplerate' => 44100, 82 'version' => '1', 83 'mode' => 'stereo', 84 'psycmode' => 3, 85 'original' => 1, 86 'extension' => 0, 87 'copyright' => 0, 88 'padding' => 0, 89 'protect' => 1, 90 'deemphasis' => 'n', 91 'total_frames' => 22, 92 'total_bytes' => 13772, 93 'total_samples' => 25344, 94 'output_md5sum' => '7e7aa8e3cfafdd1cd2eda53a9ab8bef3' 95 }, 96 { 97 # Test Case 5 (private bit set) 98 'input_filename' => 'testcase-44100.wav', 99 'input_md5sum' => 'f50499fded70a74c810dbcadb3f28062', 100 'bitrate' => 192, 101 'samplerate' => 44100, 102 'version' => '1', 103 'mode' => 'stereo', 104 'psycmode' => 3, 105 'original' => 1, 106 'extension' => 1, 107 'copyright' => 0, 108 'padding' => 0, 109 'protect' => 0, 110 'deemphasis' => 'n', 111 'total_frames' => 22, 112 'total_bytes' => 13772, 113 'total_samples' => 25344, 114 'output_md5sum' => '695bb8ebb85e79442ff309c733e7551a' 115 }, 116 { 117 # Test Case 6 (32-bit floating point input) 118 'input_filename' => 'testcase-float32.wav', 119 'input_md5sum' => '7c4f7598df7d31223463b3b1bbc03d35', 120 'bitrate' => 192, 121 'samplerate' => 44100, 122 'version' => '1', 123 'mode' => 'stereo', 124 'psycmode' => 3, 125 'original' => 1, 126 'extension' => 0, 127 'copyright' => 0, 128 'padding' => 0, 129 'protect' => 0, 130 'deemphasis' => 'n', 131 'total_frames' => 22, 132 'total_bytes' => 13772, 133 'total_samples' => 25344, 134 # Disabled because different architectures seem to give different floating point results 135 #'output_md5sum' => 'b9e7341a171c619006fa44a075d3ced5' 136 }, 137]; 138 139 140my $count = 1; 141foreach my $params (@$encoding_parameters) { 142 my $INPUT_FILENAME = input_filepath($params->{input_filename}); 143 my $OUTPUT_FILENAME = "testcase-$count.mp2"; 144 145 die "Input file does not exist: $INPUT_FILENAME" unless (-e $INPUT_FILENAME); 146 is(md5_file($INPUT_FILENAME), $params->{input_md5sum}, "[$count] MD5sum of $INPUT_FILENAME"); 147 148 my $result = system($TWOLAME_CMD, 149 '--quiet', 150 '--bitrate', $params->{bitrate}, 151 '--mode', $params->{mode}, 152 '--psyc-mode', $params->{psycmode}, 153 $params->{copyright} ? '--copyright' : '--non-copyright', 154 $params->{original} ? '--original' : '--non-original', 155 $params->{extension} ? '--private-ext' : '', 156 $params->{protect} ? '--protect' : '', 157 $params->{padding} ? '--padding' : '', 158 '--deemphasis', $params->{deemphasis}, 159 $INPUT_FILENAME, $OUTPUT_FILENAME 160 ); 161 162 is($result, 0, "[$count] twolame response code"); 163 164 my $info = mpeg_audio_info($OUTPUT_FILENAME); 165 is($info->{syncword}, 0xff, "[$count] MPEG Audio Header - Sync Word"); 166 is($info->{version}, $params->{version}, "[$count] MPEG Audio Header - Version"); 167 is($info->{layer}, 2, "[$count] MPEG Audio Header - Layer"); 168 is($info->{mode}, $params->{mode}, "[$count] MPEG Audio Header - Mode"); 169 is($info->{samplerate}, $params->{samplerate}, "[$count] MPEG Audio Header - Sample Rate"); 170 is($info->{bitrate}, $params->{bitrate}, "[$count] MPEG Audio Header - Bitrate"); 171 is($info->{copyright}, $params->{copyright}, "[$count] MPEG Audio Header - Copyright Flag"); 172 is($info->{original}, $params->{original}, "[$count] MPEG Audio Header - Original Flag"); 173 is($info->{extension}, $params->{extension}, "[$count] MPEG Audio Header - Private Extension Bit"); 174 is($info->{protect}, $params->{protect}, "[$count] MPEG Audio Header - Error Protection Flag"); 175 is($info->{deemphasis}, $params->{deemphasis}, "[$count] MPEG Audio Header - De-emphasis"); 176 177 # FIXME: test that CRC is correct 178 179 is($info->{total_frames}, $params->{total_frames}, "[$count] total number of frames"); 180 is($info->{total_bytes}, $params->{total_bytes}, "[$count] total number of bytes"); 181 is($info->{total_samples}, $params->{total_samples}, "[$count] total number of samples"); 182 183 is(filesize($OUTPUT_FILENAME), $params->{total_bytes}, , "[$count] file size of output file"); 184 185 if ($params->{output_md5sum}) { 186 is(md5_file($OUTPUT_FILENAME), $params->{output_md5sum}, "[$count] md5sum of output file"); 187 } 188 189 $count++; 190} 191 192# Ensure that encoding 44khz with bitrate of '0' results in error 193{ 194 my $INPUT_FILENAME = input_filepath('testcase-44100.wav'); 195 my $OUTPUT_FILENAME = 'testcase-44100.mp2'; 196 my $result = system("$TWOLAME_CMD --quiet -b 0 $INPUT_FILENAME $OUTPUT_FILENAME"); 197 ok($result != 0, "44100 samplerate with bitrate of '0' should result in an error"); 198} 199 200# Ensure that encoding 22khz with bitrate of '0' results in error 201{ 202 my $INPUT_FILENAME = input_filepath('testcase-22050.wav'); 203 my $OUTPUT_FILENAME = 'testcase-22050.mp2'; 204 my $result = system("$TWOLAME_CMD --quiet -b 0 $INPUT_FILENAME $OUTPUT_FILENAME"); 205 ok($result != 0, "22050 samplerate with bitrate of '0' should result in an error"); 206} 207 208# Test encoding from STDIN 209SKIP: { 210 my $result = system("which sndfile-convert > /dev/null"); 211 skip("sndfile-convert is not available", 5) unless ($result == 0); 212 213 my $INPUT_FILENAME = input_filepath('testcase-44100.wav'); 214 $result = system("sndfile-convert -pcm16 $INPUT_FILENAME testcase.raw"); 215 is($result, 0, "sndfile-convert to raw response code"); 216 217 my $OUTPUT_FILENAME = 'testcase-stdin.mp2'; 218 $result = system("$TWOLAME_CMD --quiet --raw-input - $OUTPUT_FILENAME < testcase.raw"); 219 is($result, 0, "converting from STDIN - response code"); 220 221 my $info = mpeg_audio_info($OUTPUT_FILENAME); 222 is($info->{total_frames}, 22, "converting from STDIN - total number of frames"); 223 is($info->{total_bytes}, 13772, "converting from STDIN - total number of bytes"); 224 is(md5_file($OUTPUT_FILENAME), '956f85e3647314750a1d3ed3fbf81ae3', "converting from STDIN - md5sum of output file"); 225} 226 227 228# Test encoding using the simplefrontend 229{ 230 my $INPUT_FILENAME = input_filepath('testcase-44100.wav'); 231 my $OUTPUT_FILENAME = 'testcase-simple.mp2'; 232 my $result = system("$STWOLAME_CMD $INPUT_FILENAME $OUTPUT_FILENAME"); 233 is($result, 0, "converting using simplefrontend - response code"); 234 235 my $info = mpeg_audio_info($OUTPUT_FILENAME); 236 is($info->{total_frames}, 22, "converting using simplefrontend - total number of frames"); 237 is($info->{total_bytes}, 13772, "converting using simplefrontend - total number of bytes"); 238 is(md5_file($OUTPUT_FILENAME), '956f85e3647314750a1d3ed3fbf81ae3', "converting using simplefrontend - md5sum of output file"); 239} 240 241 242## END OF TESTS ## 243 244sub input_filepath { 245 # Input test data files are in the same directory as the test script 246 my ($filename) = @_; 247 my $filepath = __FILE__; 248 $filepath =~ s/test.pl/$filename/; 249 return $filepath; 250} 251 252sub filesize { 253 return (stat($_[0]))[7]; 254} 255 256sub md5_file { 257 my ($filename) = @_; 258 259 my $ctx = Digest::MD5->new; 260 261 open(FILE, $filename) or die "Failed to open file: $filename ($!)"; 262 $ctx->addfile(*FILE); 263 close(FILE); 264 265 return $ctx->hexdigest; 266} 267 268sub mpeg_audio_info { 269 my ($filename) = @_; 270 my $info = undef; 271 272 open(MPAFILE, $filename) or die "Failed to open file: $filename ($!)"; 273 274 until (eof(MPAFILE)) { 275 my $header = ''; 276 my $bytes = read(MPAFILE, $header, 4); 277 if ($bytes != 4) { 278 warn "Failed to read MPEG Audio header"; 279 last; 280 } 281 282 my $frame_info = parse_mpeg_header($header); 283 if ($frame_info->{syncword} != 0xff) { 284 warn "Lost MPEG Audio header sync"; 285 last; 286 } 287 288 # Now read in the rest of the frame 289 my $buffer = ''; 290 my $remaining = ($frame_info->{framesize}-4); 291 $bytes = read(MPAFILE, $buffer, $remaining); 292 if ($bytes != $remaining) { 293 warn "Failed to read remaining buts of MPEG Audio frame"; 294 last; 295 } 296 297 if ($frame_info->{protect}) { 298 $frame_info->{crc} = unpack('n', $buffer); 299 } 300 301 $info = $frame_info unless ($info); 302 $info->{total_frames} += 1; 303 $info->{total_samples} += $frame_info->{samples}; 304 $info->{total_bytes} += $frame_info->{framesize}; 305 }; 306 307 close(MPAFILE); 308 309 return $info; 310} 311 312sub parse_mpeg_header { 313 my ($buffer) = @_; 314 my $header = unpack('N', $buffer); 315 my $info = {}; 316 317 my $bitrate_table = [ 318 [ # MPEG 1 319 [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448], # Layer 1 320 [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384], # Layer 2 321 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320] # Layer 3 322 ], 323 [ # MPEG 2 324 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256], # Layer 1 325 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160], # Layer 2 326 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160] # Layer 3 327 ], 328 [ # MPEG 2.5 329 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256], # Layer 1 330 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160], # Layer 2 331 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160] # Layer 3 332 ] 333 ]; 334 335 my $samplerate_table = [ 336 [ 44100, 48000, 32000 ], # MPEG 1 337 [ 22050, 24000, 16000 ], # MPEG 2 338 [ 11025, 12000, 8000 ] # MPEG 2.5 339 ]; 340 341 $info->{syncword} = ($header >> 23) & 0xff; 342 343 my $version = (($header >> 19) & 0x03); 344 if ($version == 0x00) { 345 $info->{version} = '2.5'; # MPEG 2.5 346 } elsif ($version == 0x02) { 347 $info->{version} = '2'; # MPEG 2 348 } elsif ($version == 0x03) { 349 $info->{version} = '1'; # MPEG 1 350 } else { 351 $info->{version} = undef; 352 } 353 354 $info->{layer} = 4-(($header >> 17) & 0x03); 355 if ($info->{layer}==4) { 356 $info->{layer} = 0; 357 } 358 359 $info->{protect} = (($header >> 16) & 0x01) ? 0 : 1; 360 $info->{padding} = ($header >> 9) & 0x01; 361 $info->{extension} = ($header >> 8) & 0x01; 362 $info->{mode_ext} = ($header >> 4) & 0x03; 363 $info->{copyright} = ($header >> 3) & 0x01; 364 $info->{original} = ($header >> 2) & 0x01; 365 366 my $bitrate_index = ($header >> 12) & 0x0F; 367 my $samplerate_index = ($header >> 10) & 0x03; 368 if ($info->{layer} && $info->{version}) { 369 $info->{bitrate} = $bitrate_table->[$info->{version}-1]->[$info->{layer}-1][$bitrate_index]; 370 $info->{samplerate} = $samplerate_table->[$info->{version}-1]->[$samplerate_index]; 371 } else { 372 $info->{bitrate} = undef; 373 $info->{samplerate} = undef; 374 } 375 376 my $deemphasis = $header & 0x03; 377 if ($deemphasis == 0) { 378 $info->{deemphasis} = 'n'; # None 379 } elsif ($deemphasis == 1) { 380 $info->{deemphasis} = '5'; # 50/15 ms 381 } elsif ($deemphasis == 3) { 382 $info->{deemphasis} = 'c'; # CCITT J.17 383 } else { 384 $info->{deemphasis} = undef; 385 } 386 387 my $mode = ($header >> 6) & 0x03; 388 if ($mode == 0) { 389 $info->{mode} = 'stereo'; 390 } elsif ($mode == 1) { 391 $info->{mode} = 'joint'; 392 } elsif ($mode == 2) { 393 $info->{mode} = 'dual'; 394 } elsif ($mode == 3) { 395 $info->{mode} = 'mono'; 396 } else { 397 $info->{mode} = undef; 398 } 399 400 if ($info->{layer} == '1') { 401 $info->{samples} = 384; 402 } elsif ($info->{layer} == '2') { 403 $info->{samples} = 1152; 404 } elsif ($info->{layer} == '3') { 405 $info->{samples} = ($info->{version} eq '1') ? 1152 : 576; 406 } 407 408 if ($info->{samplerate}) { 409 $info->{framesize} = int(($info->{samples} * $info->{bitrate} * 1000 / $info->{samplerate}) / 8 + $info->{padding}); 410 } else { 411 $info->{framesize} = undef; 412 } 413 414 return $info; 415} 416 417