File Coverage

blib/lib/Audio/Extract/PCM/Backend/Mad.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::Mad;
2 1     1   7 use strict;
  1         5  
  1         64  
3 1     1   6 use warnings;
  1         6  
  1         49  
4 1     1   5 use Carp;
  1         1  
  1         139  
5 1     1   5 use Audio::Extract::PCM::Format;
  1         3  
  1         32  
6              
7             # If required stuff cannot be found, we must fail with a special error message,
8             # so that AEPCM knows that this is not a real error (otherwise it would show
9             # the error message to the user).
10             BEGIN {
11 1     1   40 use Class::Inspector;
  1         2  
  1         127  
12              
13 1 50   1   6 unless (Class::Inspector->installed('Audio::Mad')) {
14 1         423 die __PACKAGE__ . " - trynext\n"; # try next backend
15             }
16             }
17              
18             use Audio::Mad qw(:all);
19             use List::Util qw(sum);
20             use base qw(Audio::Extract::PCM::Backend);
21              
22             my $use_mmap;
23             BEGIN {
24             local $@;
25             local $SIG{__DIE__};
26             $use_mmap = eval 'use Sys::Mmap::Simple qw(map_handle); 1';
27             }
28              
29             __PACKAGE__->mk_accessors(qw(stream frame synth timer resample dither samples_pending));
30              
31              
32             =head1 NAME
33              
34             Audio::Extract::PCM::Backend::Mad - mad backend for audio extraction
35              
36             =head1 SYNOPSIS
37              
38             This module makes L capable to use the libmad library
39             (specifically L) for audio extraction.
40              
41             =head2 Memory usage
42              
43             Unless L is available, the MP3 encoded data will be read
44             into memory completely. This is a few megabytes for typical music files, but
45             may be some hundred MB or more for radio broadcasts, music albums or whatever
46             strange applications you find for this module.
47              
48             If L is installed, it will be used automatically.
49              
50             =head1 WARNING
51              
52             L version 0.6 from 2003 has problems. Consider applying the patch
53             from L until L
54             releases a fixed version.
55              
56             =head1 METHODS
57              
58             =head2 new
59              
60             See L.
61              
62             =cut
63              
64              
65             sub new {
66             my $class = shift;
67             my $this = $class->SUPER::new(@_);
68              
69             $this->stream(Audio::Mad::Stream->new());
70             $this->frame(Audio::Mad::Frame->new());
71             $this->synth(Audio::Mad::Synth->new());
72             $this->timer(Audio::Mad::Timer->new());
73             $this->samples_pending([]);
74              
75             return $this;
76             }
77              
78              
79             my @dithervalues = (
80             {
81             value => MAD_DITHER_S8,
82             samplesize => 1,
83             signed => 1,
84             },
85             {
86             value => MAD_DITHER_U8,
87             samplesize => 1,
88             signed => 0,
89             },
90             {
91             value => MAD_DITHER_S24_LE,
92             samplesize => 3,
93             endian => 'little',
94             signed => 1,
95             },
96             {
97             value => MAD_DITHER_S24_BE,
98             samplesize => 3,
99             endian => 'big',
100             signed => 1,
101             },
102             {
103             value => MAD_DITHER_S32_LE,
104             samplesize => 4,
105             endian => 'little',
106             signed => 1,
107             },
108             {
109             value => MAD_DITHER_S32_BE,
110             samplesize => 4,
111             endian => 'big',
112             signed => 1,
113             },
114             {
115             value => MAD_DITHER_S16_LE,
116             samplesize => 2,
117             endian => 'little',
118             signed => 1,
119             },
120             {
121             value => MAD_DITHER_S16_BE,
122             samplesize => 2,
123             endian => 'big',
124             signed => 1,
125             },
126             );
127              
128              
129             =head2 open_back
130              
131             See L.
132              
133             =cut
134              
135             sub open_back {
136             my $this = shift;
137              
138             my ($format) = @_;
139              
140             my ($dithervalue, $endformat) = $format->findvalue(\@dithervalues);
141             if ($dithervalue) {
142             $this->dither(Audio::Mad::Dither->new($dithervalue));
143             } else {
144             return 'trynext'; # try next backend
145             }
146              
147             # We need to find out the sample rate of the input stream. To do that, we
148             # must decode the first frame.
149              
150             my $fn = $this->filename;
151             croak 'no filename given' unless defined $fn;
152              
153             open my $fh, '<:raw', $fn or do {
154             $this->error("open: $fn: $!");
155             return ();
156             };
157              
158             # The buffer should stay in memory while $this exists: Audio::Mad does not
159             # care about garbage collection.
160             # Maybe this shouldn't be done in the backend: We don't want the next
161             # backend to slurp the file again if we have to return 'trynext'.
162              
163             if ($use_mmap) {
164              
165             $this->{buffer} = do {
166             #mmap(my $buf, 0, PROT_READ, MAP_SHARED, $fh) or die "mmap: $fn: $!";
167              
168             my $buf;
169              
170             local $@;
171             local $SIG{__DIE__};
172             eval {
173             map_handle($buf, $fh);
174             };
175             if ($@) {
176             # This happens e.g. for pipes
177             $this->error("Could not map file ($fn): $@");
178             return ();
179              
180             # Should instead slurp the file?
181             # Or should we return 'trynext'?
182             }
183              
184             \ $buf;
185             };
186              
187             # This is only for the reference counter :)
188             $this->{__fh} = $fh;
189              
190             } else {
191              
192             warn 'Install Sys::Mmap::Simple for more efficiency' unless our($have_warned_mmap)++;
193              
194             $this->{buffer} = do {
195              
196             # I try not to use a my-scalar because perl would't free the memory
197             # when it goes out of scope.
198              
199             # Actually I'm not sure if this is better, but it looks complicated
200             # enough.
201              
202             my %foohash = (buf => '');
203              
204             for (;;) {
205             my $l = sysread($fh, $foohash{buf}, 4096, length($foohash{buf}));
206             unless (defined $l) {
207             $this->error("read: $!");
208             return ();
209             }
210             last unless $l;
211             }
212              
213             \$foohash{buf};
214             };
215             close $fh or die "$fh: $!";
216              
217             }
218              
219             $this->stream->buffer(${$this->{buffer}});
220              
221             # Now everything is set up for the first call to _crunch_frame, which will
222             # finally provide us with the sought-after sample rate.
223              
224             $this->_crunch_frame or do {
225             $this->error("could not decode file"); # XXX what about empty files?
226             return ();
227             };
228              
229             $endformat->combine(channels => $this->frame->NCHANNELS);
230             return 'trynext' unless $format->satisfied($endformat);
231              
232             my $srcfreq = $this->frame->samplerate;
233             my $freq = $format->freq || $srcfreq;
234              
235             $this->resample(Audio::Mad::Resample->new($srcfreq, $freq));
236              
237             if (2 == $this->resample->mode && $freq != $srcfreq) {
238             # resampling of these values not supported
239              
240             # XXX try other accepted sampling frequencies, if there are any
241              
242             return 'trynext' if ! $format->satisfied(freq => $srcfreq);
243              
244             $freq = $srcfreq;
245             }
246              
247             $endformat->combine(freq => $freq);
248              
249              
250             return $endformat;
251             }
252              
253              
254             sub _resample {
255             my $this = shift;
256             if (2 == $this->resample->mode) {
257             return @_;
258             }
259             return $this->resample->resample(@_);
260             }
261              
262              
263             # Reads a frame (or tries until one frame has been read successfully)
264             sub _crunch_frame {
265             my $this = shift;
266              
267             while (-1 == $this->frame->decode($this->stream)) {
268             if (MAD_ERROR_BUFLEN == $this->stream->error) {
269             return 0;
270             }
271             unless ($this->stream->err_ok()) {
272             $this->error("Fatal decoding error: " . $this->stream->error);
273             return ();
274             }
275              
276             # Don't warn for recoverable errors. Too much noise.
277             # warn "Decoding error: " . $this->stream->error;
278             }
279              
280             $this->synth->synth($this->frame);
281             push @{$this->samples_pending}, [$this->frame->duration, $this->synth->samples];
282             return 1;
283             }
284              
285              
286             =head2 read
287              
288             See L.
289              
290             =cut
291              
292             sub read {
293             my $this = shift;
294             my $buf = \shift;
295             my %args = @_;
296              
297             $$buf = '' unless $args{append};
298              
299             my $bytes_read = 0;
300              
301             for (;;) {
302             while (@{$this->samples_pending}) {
303             my $s = shift @{$this->samples_pending};
304              
305             my $pcm = $this->dither->dither($this->_resample(@{$s}[1..$#$s]));
306             $$buf .= $pcm;
307             $bytes_read += length $pcm;
308             }
309              
310             if (defined $args{bytes}) {
311             last if $args{bytes} <= $bytes_read;
312             }
313              
314             my $crunch = $this->_crunch_frame;
315             return () unless defined $crunch;
316             last unless $crunch;
317             }
318              
319             return $bytes_read;
320             }
321              
322              
323             =head2 used_versions
324              
325             Returns a hashref with Audio::Mad's version as a value.
326              
327             =cut
328              
329             sub used_versions {
330             return {
331             'Audio::Mad' => Audio::Mad->VERSION,
332             };
333             }
334              
335              
336             our $AVAILABLE = 1;