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