#!/usr/bin/perl use strict; use Getopt::Long; use Pod::Usage; use Audio::Wav; use Data::Dumper; # Parse options my $wav_file = ''; my $ref_file = ''; GetOptions('wavfile=s' => \$wav_file, 'reffile=s' => \$ref_file) or pod2usage(); print("Comparing $wav_file to $ref_file\n"); # Load wav and ref files my $wav = new Audio::Wav; my $wav_read = $wav->read($wav_file) or die "FAIL: Can't read $wav_file: $!\n"; my $ref_read = $wav->read($ref_file) or die "FAIL: Can't read $wav_file: $!\n"; my $wav_details = $wav_read->details(); my $ref_details = $ref_read->details(); print_wav($wav_read); print_wav($ref_read); # Sanity checks if ($wav_read->length_samples() < $ref_read->length_samples()) { die("$wav_file is too short"); } if ($wav_details->{channels} != 2) { die("FAIL: $wav_file must have two channels"); } if ($wav_details->{sample_rate} != $ref_details->{sample_rate}) { die("FAIL: $wav_file and $ref_file have different sample rates"); } # fixme allow different bits/sample to be comparec if ($wav_details->{bits_sample} != $ref_details->{bits_sample}) { die("FAIL: $wav_file and $ref_file have different bits/sample"); } # fixme allow mono ref files if ($ref_details->{channels} != 2) { die("FAIL: $ref_file must have two channels"); } # discard any initial silence in the two files my $wav_silence = eat_silence($wav_read); my $ref_silence = eat_silence($ref_read); print $wav_file, ": $wav_silence frames of leading silence\n"; print $ref_file, ": $ref_silence frames of leading silence\n"; if ($wav_silence < $ref_silence) { die("FAIL: not enough leading silence in $wav_file"); } # number of frames the match / fail my $frame_ok = 0; my $frame_diff = 0; my $channels = $wav_details->{channels}; my $length = $ref_read->length_samples(); my $frames = $ref_silence; while ($frames < $length) { # read samples my @wav_samples = $wav_read->read(); my @ref_samples = $ref_read->read(); if ($frames++ % 100000 == 0) { printf("%0.2f%% %d/%d\n", (($frames / $length) * 100), $frame_ok, ($frame_ok+$frame_diff), ); } # compare samples my $samples_ok = 0; for (my $i=0; $i<$channels; $i++) { if ($wav_samples[$i] == $ref_samples[$i]) { $samples_ok++; } } # record if the frame is ok if ($samples_ok == $channels) { $frame_ok++; } else { $frame_diff++; } } # the rest of the wav file must be silence my $remaining = $wav_read->length_samples() - $wav_read->position_samples(); my $end_silence = eat_silence($wav_read); print $wav_file, ": $end_silence frames of trailing silence\n"; if ($remaining != $end_silence) { die "FAIL: Extra samples at the end of the track\n"; } print "PASS: it is bit perfect!\n"; exit 0; sub eat_silence { my $sample; my $silence = 0; my $read = shift; my $details = $read->details(); my $frames = $read->position_samples(); my $length = $read->length_samples(); while ($frames < $length) { my @samples = $read->read(); foreach $sample (@samples) { if ($sample != 0) { # seek back one sample $read->move_to_sample($read->position_samples() - 1); return $silence; } } $frames++; $silence++; } return $silence; } sub print_wav { my $read = shift; my $details = $read->details(); print $read->file_name(), ":\n"; print "\t", "sample_rate: ", $details->{sample_rate}, "\n"; print "\t", "channels: ", $details->{channels}, "\n"; print "\t", "bits_sample: ", $details->{bits_sample}, "\n"; print "\t", "length_samples: ", $read->length_samples(), "\n"; #print Data::Dumper->Dump([ $details ]); } __END__ =head1 NAME lossless_conformance_test =head1 SYNOPSIS lossless_conformance_test [options] Options: --wavfile wav file under test --reffile reference file =cut