File Coverage

blib/lib/Audio/Extract/PCM/Backend/SndFile.pm
Criterion Covered Total %
statement 17 17 100.0
branch 1 2 50.0
condition n/a
subroutine 6 6 100.0
pod n/a
total 24 25 96.0


line stmt bran cond sub pod time code
1             package Audio::Extract::PCM::Backend::SndFile;
2 2     2   13 use strict;
  2         5  
  2         106  
3 2     2   13 use warnings;
  2         4  
  2         85  
4 2     2   12 use Audio::Extract::PCM::Format;
  2         3  
  2         29  
5 2     2   66 use base qw(Audio::Extract::PCM::Backend);
  2         4  
  2         963  
6              
7              
8             # If required stuff cannot be found, we must fail with a special error message,
9             # so that AEPCM knows that this is not a real error (otherwise it would show
10             # the error message to the user).
11             BEGIN {
12 2     2   15 use Class::Inspector;
  2         3  
  2         116  
13              
14 2 50   2   11 unless (Class::Inspector->installed('Audio::SndFile')) {
15 2         823 die __PACKAGE__ . " - trynext\n"; # try next backend
16             }
17             }
18             use Audio::SndFile;
19              
20             __PACKAGE__->mk_accessors(qw(_sndfile _sampletype));
21              
22              
23             use constant SHORT_SIZE => length pack('s', 0);
24             use constant INT_SIZE => length pack('i', 0);
25              
26              
27             =head1 NAME
28              
29             Audio::Extract::PCM::Backend::SndFile - sndfile backend for audio extraction
30              
31             =head1 SYNOPSIS
32              
33             This module makes L capable to use the sndfile library
34             (specifically L) for audio extraction.
35              
36             =cut
37              
38              
39             my @sample_format_vals = (
40             {
41             value => 'int',
42             samplesize => INT_SIZE,
43             signed => 1,
44             },
45             {
46             value => 'short',
47             samplesize => SHORT_SIZE,
48             signed => 1,
49             },
50             );
51              
52              
53             =head2 open_back
54              
55             See L.
56              
57             =cut
58              
59             sub open_back {
60             my $this = shift;
61             my ($format) = @_;
62              
63             my ($sampletype, $sample_format) = $format->findvalue(\@sample_format_vals);
64             return 'trynext' unless defined $sample_format;
65              
66             my $sndfile;
67             {
68             local $@;
69             local $SIG{__DIE__};
70              
71             $sndfile = eval { Audio::SndFile->open('<', $this->filename, endianness => 'cpu') };
72              
73             if ($@) {
74             $@ =~ s#^(.*) at .*?\z#$1#s;
75             $this->error("$@");
76             return ();
77             }
78             }
79             $this->_sndfile($sndfile);
80              
81             if (! defined $format->samplesize) {
82             # User has no specific samplesize requests; choose a wise default
83              
84             my ($unsigned, $orig_ssize) = $sndfile->subtype() =~ /_(u?)(\d+)\z/;
85              
86             # We choose the sample size according to the file's sample size. For
87             # libsndfile's more obscure sample formats (like "ulaw" and float
88             # formats, and what the heck "dwvw_16" is I don't actually care), this
89             # is just heuristic. But for usual integer pcm formats, it should be
90             # fine.
91              
92             $orig_ssize++ if $unsigned;
93              
94             if (defined $orig_ssize && $orig_ssize <= 8 * SHORT_SIZE) {
95              
96             ($sampletype, $sample_format) = ('short', AEPF->new(samplesize => SHORT_SIZE));
97             } else {
98             ($sampletype, $sample_format) = ('int', AEPF->new(samplesize => INT_SIZE));
99             }
100             }
101              
102             my $duration = $sndfile->frames() / $sndfile->samplerate();
103              
104             my $endformat = Audio::Extract::PCM::Format->new(
105             channels => $sndfile->channels,
106             freq => $sndfile->samplerate,
107             signed => 1,
108             duration => $duration,
109              
110             # I *believe* that read always returns native endian data, but I gotta
111             # check this. Anyway, $sndfile->endianness always returns "file", so
112             # we cannot use it here.
113             endian => 'native',
114             # endian => $sndfile->endianness,
115             );
116              
117             $endformat->combine($sample_format);
118              
119             $this->_sampletype($sampletype);
120              
121             return $endformat;
122             }
123              
124              
125             =head2 read_back
126              
127             See L.
128              
129             =cut
130              
131             sub read_back {
132             my $this = shift;
133             my $buf = \shift;
134             my (%args) = @_;
135              
136             my $bytes = $args{bytes};
137             my $format = $this->format;
138              
139             my $items = $bytes / $format->samplesize;
140              
141             $items++ until 0 == $items % $format->channels;
142              
143             my $workbuf = $args{append} ? do{\my($x)} : $buf;
144             $$workbuf = '';
145              
146             my $readfunc = 'read_' . $this->_sampletype;
147              
148             my $l = $this->_sndfile->$readfunc($$workbuf, $items);
149             $$buf .= $$workbuf if $args{append} && $l > 0;
150             return $l * $format->samplesize;
151             }
152              
153              
154             =head2 used_versions
155              
156             Returns versions of L and libsndfile in a hash reference.
157              
158             =cut
159              
160             sub used_versions {
161             return {
162             'Audio::SndFile' => Audio::SndFile->VERSION,
163             'libsndfile' => Audio::SndFile::lib_version(),
164             };
165             }
166              
167              
168              
169             =head1 TODO
170              
171             Thoroughly test this for float pcm files, especially normalization issues.
172              
173             =cut
174              
175             our $AVAILABLE = 1;