File Coverage

src/wav.c
Criterion Covered Total %
statement 188 224 83.9
branch 115 190 60.5
condition n/a
subroutine n/a
pod n/a
total 303 414 73.1


line stmt bran cond sub pod time code
1             /*
2             * This program is free software; you can redistribute it and/or modify
3             * it under the terms of the GNU General Public License as published by
4             * the Free Software Foundation; either version 2 of the License, or
5             * (at your option) any later version.
6             *
7             * This program is distributed in the hope that it will be useful,
8             * but WITHOUT ANY WARRANTY; without even the implied warranty of
9             * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10             * GNU General Public License for more details.
11             *
12             * You should have received a copy of the GNU General Public License
13             * along with this program; if not, write to the Free Software
14             * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15             */
16              
17             #include "wav.h"
18              
19             static int
20 11           get_wav_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
21             {
22             Buffer buf;
23             off_t file_size;
24 11           int err = 0;
25             uint32_t chunk_size;
26              
27 11           file_size = _file_size(infile);
28              
29 11           buffer_init(&buf, WAV_BLOCK_SIZE);
30              
31 11 50         if ( !_check_buf(infile, &buf, 12, WAV_BLOCK_SIZE) ) {
32 0           err = -1;
33 0           goto out;
34             }
35              
36 11 100         if ( !strncmp( (char *)buffer_ptr(&buf), "RIFF", 4 ) ) {
37             // We've got a RIFF file
38 8           buffer_consume(&buf, 4);
39              
40 8           chunk_size = buffer_get_int_le(&buf);
41              
42             // Check format
43 8 50         if ( strncmp( (char *)buffer_ptr(&buf), "WAVE", 4 ) ) {
44 0           PerlIO_printf(PerlIO_stderr(), "Invalid WAV file: missing WAVE header: %s\n", file);
45 0           err = -1;
46 0           goto out;
47             }
48              
49 8           buffer_consume(&buf, 4);
50              
51 8           my_hv_store( info, "file_size", newSVuv(file_size) );
52              
53 8           _parse_wav(infile, &buf, file, file_size, info, tags);
54             }
55 3 50         else if ( !strncmp( (char *)buffer_ptr(&buf), "FORM", 4 ) ) {
56             // We've got an AIFF file
57             char *bptr;
58              
59 3           buffer_consume(&buf, 4);
60              
61 3           chunk_size = buffer_get_int(&buf);
62              
63             // Check format
64 3           bptr = buffer_ptr(&buf);
65 3 50         if ( bptr[0] == 'A' && bptr[1] == 'I' && bptr[2] == 'F' && (bptr[3] == 'F' || bptr[3] == 'C') ) {
    50          
    50          
    100          
    50          
66 3           buffer_consume(&buf, 4);
67              
68 3           my_hv_store( info, "file_size", newSVuv(file_size) );
69              
70 3           _parse_aiff(infile, &buf, file, file_size, info, tags);
71             }
72             else {
73 0           PerlIO_printf(PerlIO_stderr(), "Invalid AIFF file: missing AIFF header: %s\n", file);
74 0           err = -1;
75 0           goto out;
76             }
77             }
78             else {
79 0           PerlIO_printf(PerlIO_stderr(), "Invalid WAV file: missing RIFF header: %s\n", file);
80 0           err = -1;
81 0           goto out;
82             }
83              
84             out:
85 11           buffer_free(&buf);
86              
87 11 50         if (err) return err;
88              
89 11           return 0;
90             }
91              
92             void
93 8           _parse_wav(PerlIO *infile, Buffer *buf, char *file, uint32_t file_size, HV *info, HV *tags)
94             {
95 8           uint32_t offset = 12;
96              
97 34 100         while ( offset < file_size - 8 ) {
98             char chunk_id[5];
99             uint32_t chunk_size;
100              
101             // Verify we have at least 8 bytes
102 29 50         if ( !_check_buf(infile, buf, 8, WAV_BLOCK_SIZE) ) {
103 3           return;
104             }
105              
106 29           strncpy( chunk_id, (char *)buffer_ptr(buf), 4 );
107 29           chunk_id[4] = '\0';
108 29           buffer_consume(buf, 4);
109              
110 29           chunk_size = buffer_get_int_le(buf);
111              
112             // Adjust for padding
113 29 100         if ( chunk_size % 2 ) {
114 3           chunk_size++;
115             }
116              
117 29           offset += 8;
118              
119             DEBUG_TRACE("%s size %d\n", chunk_id, chunk_size);
120              
121             // Seek past data, everything else we parse
122             // XXX: Are there other large chunks we should ignore?
123 29 100         if ( !strcmp( chunk_id, "data" ) ) {
124             SV **bitrate;
125              
126 8           my_hv_store( info, "audio_offset", newSVuv(offset) );
127 8           my_hv_store( info, "audio_size", newSVuv(chunk_size) );
128              
129             // Calculate duration, unless we already know it (i.e. from 'fact')
130 8 100         if ( !my_hv_fetch( info, "song_length_ms" ) ) {
131 2           bitrate = my_hv_fetch( info, "bitrate" );
132 2 50         if (bitrate != NULL) {
133 2 50         my_hv_store( info, "song_length_ms", newSVuv( (chunk_size / (SvIV(*bitrate) / 8.)) * 1000 ) );
134             }
135             }
136              
137             // sanity check size, this is inside the data chunk code
138             // to support setting audio_offset even when the data size is wrong
139 8 100         if (chunk_size > file_size - offset) {
140             DEBUG_TRACE("data size > file_size, skipping\n");
141 2           return;
142             }
143              
144             // Seek past data if there are more chunks after it
145 6 100         if ( file_size > offset + chunk_size ) {
146 4           PerlIO_seek(infile, offset + chunk_size, SEEK_SET);
147             }
148              
149 6           buffer_clear(buf);
150             }
151 22 100         else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) {
    50          
    50          
152             // Read header to verify version
153 1           unsigned char *bptr = buffer_ptr(buf);
154              
155 1 50         if (
156 1 50         (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') &&
    50          
    50          
157 1 50         bptr[3] < 0xff && bptr[4] < 0xff &&
    50          
158 1 50         bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
    50          
    50          
159             ) {
160             // Start parsing ID3 from offset
161 1           parse_id3(infile, file, info, tags, offset, file_size);
162             }
163              
164             // Seek past ID3 and clear buffer
165 1           PerlIO_seek(infile, offset + chunk_size, SEEK_SET);
166 1           buffer_clear(buf);
167             }
168             else {
169             // sanity check size
170 20 100         if (chunk_size > file_size - offset) {
171             DEBUG_TRACE("chunk_size > file_size, skipping\n");
172 1           return;
173             }
174              
175             // Make sure we have enough data
176 19 50         if ( !_check_buf(infile, buf, chunk_size, WAV_BLOCK_SIZE) ) {
177 0           return;
178             }
179              
180 19 100         if ( !strcmp( chunk_id, "fmt " ) ) {
181 8           _parse_wav_fmt(buf, chunk_size, info);
182             }
183 11 100         else if ( !strcmp( chunk_id, "LIST" ) ) {
184 2           _parse_wav_list(buf, chunk_size, tags);
185             }
186 9 100         else if ( !strcmp( chunk_id, "PEAK" ) ) {
187 3           _parse_wav_peak(buf, chunk_size, info, 0);
188             }
189 6 50         else if ( !strcmp( chunk_id, "fact" ) ) {
190             // A 4-byte fact chunk in a non-PCM wav is the number of samples
191             // Use it to calculate duration
192 6 50         if ( chunk_size == 4 ) {
193 6           uint32_t num_samples = buffer_get_int_le(buf);
194 6           SV **samplerate = my_hv_fetch( info, "samplerate" );
195 6 50         if (samplerate != NULL) {
196             DEBUG_TRACE("[wav] Setting song_length_ms from fact chunk: ( num_samples(%d) * 1000 / samplerate(%ld) )\n", num_samples, SvIV(*samplerate));
197             // GH#2, cast num_samples to 64-bit to avoid 32-bit overflow
198 6 50         my_hv_store( info, "song_length_ms", newSVuv( ((uint64_t)num_samples * 1000) / SvIV(*samplerate) ) );
199             }
200             }
201             else {
202             // Unknown, skip it
203 6           buffer_consume(buf, chunk_size);
204             }
205             }
206             else {
207 0 0         if (
208 0           !strcmp(chunk_id, "SAUR") // Wavosour data chunk
209 0 0         || !strcmp(chunk_id, "otom") // Wavosaur?
210 0 0         || !strcmp(chunk_id, "PAD ") // Padding
211             ) {
212             // Known chunks to skip
213             }
214             else {
215             // Warn about unknown chunks so we can investigate them
216 0           PerlIO_printf(PerlIO_stderr(), "Unhandled WAV chunk %s size %d (skipped)\n", chunk_id, chunk_size);
217             }
218              
219 0           buffer_consume(buf, chunk_size);
220             }
221             }
222              
223 26           offset += chunk_size;
224             }
225             }
226              
227             void
228 8           _parse_wav_fmt(Buffer *buf, uint32_t chunk_size, HV *info)
229             {
230             uint32_t samplerate;
231             uint16_t channels, bps;
232 8           uint16_t format = buffer_get_short_le(buf);
233              
234 8           my_hv_store( info, "format", newSVuv(format) );
235              
236 8           channels = buffer_get_short_le(buf);
237 8           my_hv_store( info, "channels", newSVuv(channels) );
238              
239 8           samplerate = buffer_get_int_le(buf);
240 8           my_hv_store( info, "samplerate", newSVuv(samplerate) );
241 8           my_hv_store( info, "bitrate", newSVuv( buffer_get_int_le(buf) * 8 ) );
242 8           my_hv_store( info, "block_align", newSVuv( buffer_get_short_le(buf) ) );
243              
244 8           bps = buffer_get_short_le(buf);
245 8           my_hv_store( info, "bits_per_sample", newSVuv(bps) );
246              
247 8 100         if ( chunk_size > 16 ) {
248 3           uint16_t extra_len = buffer_get_short_le(buf);
249              
250             // Bug 14462, a WAV file with only an 18-byte fmt chunk should ignore extra_len bytes
251 3 50         if (extra_len && chunk_size > 18) {
    100          
252             DEBUG_TRACE(" skipping extra_len bytes in fmt: %d\n", extra_len);
253 2           buffer_consume(buf, extra_len);
254             }
255             }
256              
257             // DLNA
258 8 50         if (channels <= 2 && bps == 16) {
    100          
259 1 50         if (samplerate == 44100 || samplerate == 48000)
    0          
260 1           my_hv_store( info, "dlna_profile", newSVpv("LPCM", 0) );
261 0 0         else if (samplerate >= 8000 && samplerate <= 32000)
    0          
262 0           my_hv_store( info, "dlna_profile", newSVpv("LPCM_low", 0) );
263             }
264 8           }
265              
266             void
267 2           _parse_wav_list(Buffer *buf, uint32_t chunk_size, HV *tags)
268             {
269             char type_id[5];
270 2           uint32_t pos = 4;
271              
272 2           strncpy( type_id, (char *)buffer_ptr(buf), 4 );
273 2           type_id[4] = '\0';
274 2           buffer_consume(buf, 4);
275              
276             DEBUG_TRACE(" LIST type %s\n", type_id);
277              
278 2 50         if ( !strcmp( type_id, "adtl" ) ) {
279             // XXX need test file
280 0           PerlIO_printf(PerlIO_stderr(), "Unhandled LIST type adtl\n");
281 0           buffer_consume(buf, chunk_size - 4);
282             }
283 2 50         else if ( !strcmp( type_id, "INFO" ) ) {
284 13 100         while ( pos < chunk_size ) {
285             uint32_t len;
286 11           uint32_t nulls = 0;
287             SV *key;
288             SV *value;
289             unsigned char *bptr;
290              
291 11           key = newSVpvn( buffer_ptr(buf), 4 );
292 11           buffer_consume(buf, 4);
293 11           pos += 4;
294              
295 11           len = buffer_get_int_le(buf);
296              
297             // Bug 12250, apparently some WAV files don't use the padding byte
298             // so we can't read them.
299 11 50         if ( len > chunk_size - pos ) {
300 0           PerlIO_printf(PerlIO_stderr(), "Invalid data in WAV LIST INFO chunk (len %d > chunk_size - pos %d)\n", len, chunk_size - pos);
301 0           break;
302             }
303              
304 11           pos += 4 + len;
305              
306             // Bug 14946, Strip any nulls from the end of the value
307 11           bptr = buffer_ptr(buf);
308 21 50         while ( len && bptr[len - 1] == '\0' ) {
    100          
309 10           len--;
310 10           nulls++;
311             }
312              
313 11           value = newSVpvn( buffer_ptr(buf), len );
314 11           buffer_consume(buf, len + nulls);
315              
316             DEBUG_TRACE(" %s / %s (%d + %d nulls)\n", SvPVX(key), SvPVX(value), len, nulls);
317              
318 11           my_hv_store_ent( tags, key, value );
319 11           SvREFCNT_dec(key);
320              
321             // Handle padding
322 11 50         if ( (len + nulls) % 2 ) {
323 0           buffer_consume(buf, 1);
324 0           pos++;
325             }
326             }
327             }
328             else {
329 0           PerlIO_printf(PerlIO_stderr(), "Unhandled LIST type %s\n", type_id);
330 0           buffer_consume(buf, chunk_size - 4);
331             }
332 2           }
333              
334             void
335 4           _parse_wav_peak(Buffer *buf, uint32_t chunk_size, HV *info, uint8_t big_endian)
336             {
337 4           uint16_t channels = 0;
338 4           AV *peaklist = newAV();
339              
340 4           SV **entry = my_hv_fetch( info, "channels" );
341 4 50         if ( entry != NULL ) {
342 4 50         channels = SvIV(*entry);
343             }
344              
345             // Skip version/timestamp
346 4           buffer_consume(buf, 8);
347              
348 12 100         while ( channels-- ) {
349 8           HV *peak = newHV();
350              
351 8 100         my_hv_store( peak, "value", newSVnv( big_endian ? buffer_get_float32(buf) : buffer_get_float32_le(buf) ) );
352 8 100         my_hv_store( peak, "position", newSVuv( big_endian ? buffer_get_int(buf) : buffer_get_int_le(buf) ) );
353              
354 8           av_push( peaklist, newRV_noinc( (SV *)peak) );
355             }
356              
357 4           my_hv_store( info, "peak", newRV_noinc( (SV *)peaklist ) );
358 4           }
359              
360             void
361 3           _parse_aiff(PerlIO *infile, Buffer *buf, char *file, uint32_t file_size, HV *info, HV *tags)
362             {
363 3           uint32_t offset = 12;
364              
365 11 100         while ( offset < file_size - 8 ) {
366             char chunk_id[5];
367             int chunk_size;
368              
369             // Verify we have at least 8 bytes
370 9 50         if ( !_check_buf(infile, buf, 8, WAV_BLOCK_SIZE) ) {
371 1           return;
372             }
373              
374 9           strncpy( chunk_id, (char *)buffer_ptr(buf), 4 );
375 9           chunk_id[4] = '\0';
376 9           buffer_consume(buf, 4);
377              
378 9           chunk_size = buffer_get_int(buf);
379              
380             // Adjust for padding
381 9 50         if ( chunk_size % 2 ) {
382 0           chunk_size++;
383             }
384              
385 9           offset += 8;
386              
387             DEBUG_TRACE("%s size %d\n", chunk_id, chunk_size);
388              
389             // Seek past SSND, everything else we parse
390             // XXX: Are there other large chunks we should ignore?
391 9 100         if ( !strcmp( chunk_id, "SSND" ) ) {
392             int ssnd_offset, ssnd_blocksize;
393              
394 3 50         if ( !_check_buf(infile, buf, 8, WAV_BLOCK_SIZE) ) {
395 0           return;
396             }
397              
398 3           ssnd_offset = buffer_get_int(buf);
399 3           ssnd_blocksize = buffer_get_int(buf);
400              
401             DEBUG_TRACE("SSND offset: %u block size: %u\n", ssnd_offset, ssnd_blocksize);
402              
403 3           my_hv_store( info, "audio_offset", newSVuv(offset + 8 + ssnd_offset) );
404 3           my_hv_store( info, "audio_size", newSVuv(chunk_size - 8 - ssnd_offset) );
405              
406             // Seek past data if there are more chunks after it
407 3 100         if ( file_size > offset + chunk_size ) {
408 2           PerlIO_seek(infile, offset + chunk_size, SEEK_SET);
409             }
410              
411 3           buffer_clear(buf);
412             }
413 7 50         else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) {
    100          
    50          
414             // Read header to verify version
415 2           unsigned char *bptr = buffer_ptr(buf);
416              
417 2 50         if (
418 2 50         (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') &&
    50          
    50          
419 2 50         bptr[3] < 0xff && bptr[4] < 0xff &&
    50          
420 2 50         bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
    50          
    50          
421             ) {
422             // Start parsing ID3 from offset
423 2           parse_id3(infile, file, info, tags, offset, file_size);
424             }
425              
426             // Seen ID3 chunks with the chunk size in little-endian instead of big-endian
427 2 100         if (chunk_size < 0 || offset + chunk_size > file_size) {
    50          
428             break;
429             }
430              
431             // Seek past ID3 and clear buffer
432             DEBUG_TRACE("Seeking past ID3 to %d\n", offset + chunk_size);
433 1           PerlIO_seek(infile, offset + chunk_size, SEEK_SET);
434 1           buffer_clear(buf);
435             }
436             else {
437             // sanity check size
438 4 50         if (chunk_size > file_size - offset) {
439             DEBUG_TRACE("chunk_size > file_size, skipping\n");
440 0           return;
441             }
442              
443             // Make sure we have enough data
444 4 50         if ( !_check_buf(infile, buf, chunk_size, WAV_BLOCK_SIZE) ) {
445 0           return;
446             }
447              
448 4 100         if ( !strcmp( chunk_id, "COMM" ) ) {
449 3           _parse_aiff_comm(buf, chunk_size, info);
450             }
451 1 50         else if ( !strcmp( chunk_id, "PEAK" ) ) {
452 1           _parse_wav_peak(buf, chunk_size, info, 1);
453             }
454             else {
455 0           PerlIO_printf(PerlIO_stderr(), "Unhandled AIFF chunk %s size %d (skipped)\n", chunk_id, chunk_size);
456 0           buffer_consume(buf, chunk_size);
457             }
458             }
459              
460 8           offset += chunk_size;
461             }
462             }
463              
464             void
465 3           _parse_aiff_comm(Buffer *buf, uint32_t chunk_size, HV *info)
466             {
467 3           uint16_t channels = buffer_get_short(buf);
468 3           uint32_t frames = buffer_get_int(buf);
469 3           uint16_t bits_per_sample = buffer_get_short(buf);
470 3           double samplerate = buffer_get_ieee_float(buf);
471              
472 3           my_hv_store( info, "channels", newSVuv(channels) );
473 3           my_hv_store( info, "bits_per_sample", newSVuv(bits_per_sample) );
474 3           my_hv_store( info, "samplerate", newSVuv(samplerate) );
475              
476 3           my_hv_store( info, "bitrate", newSVuv( samplerate * channels * bits_per_sample ) );
477 3           my_hv_store( info, "song_length_ms", newSVuv( ((frames * 1.0) / samplerate) * 1000 ) );
478 3           my_hv_store( info, "block_align", newSVuv( channels * bits_per_sample / 8 ) );
479              
480 3 100         if (chunk_size > 18) {
481             // AIFC extra data
482 1           my_hv_store( info, "compression_type", newSVpvn( buffer_ptr(buf), 4 ) );
483 1           buffer_consume(buf, 4);
484              
485 1           my_hv_store( info, "compression_name", newSVpvn( buffer_ptr(buf), chunk_size - 22 ) );
486 1           buffer_consume(buf, chunk_size - 22);
487             }
488              
489             // DLNA
490 3 50         if (channels <= 2 && bits_per_sample == 16) {
    100          
491 2 50         if (samplerate == 44100 || samplerate == 48000)
    0          
492 2           my_hv_store( info, "dlna_profile", newSVpv("LPCM", 0) );
493 0 0         else if (samplerate >= 8000 && samplerate <= 32000)
    0          
494 0           my_hv_store( info, "dlna_profile", newSVpv("LPCM_low", 0) );
495             }
496 3           }