File Coverage

src/wavpack.c
Criterion Covered Total %
statement 189 226 83.6
branch 85 130 65.3
condition n/a
subroutine n/a
pod n/a
total 274 356 76.9


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 "wavpack.h"
18              
19             static int
20 10           get_wavpack_info(PerlIO *infile, char *file, HV *info)
21             {
22 10           wvpinfo *wvp = _wavpack_parse(infile, file, info, 0);
23              
24 10           Safefree(wvp);
25              
26 10           return 0;
27             }
28              
29             wvpinfo *
30 10           _wavpack_parse(PerlIO *infile, char *file, HV *info, uint8_t seeking)
31             {
32 10           int err = 0;
33 10           int done = 0;
34             u_char *bptr;
35              
36             wvpinfo *wvp;
37 10           Newz(0, wvp, sizeof(wvpinfo), wvpinfo);
38 10           Newz(0, wvp->buf, sizeof(Buffer), Buffer);
39 10           Newz(0, wvp->header, sizeof(WavpackHeader), WavpackHeader);
40              
41 10           wvp->infile = infile;
42 10           wvp->file = file;
43 10           wvp->info = info;
44 10           wvp->file_offset = 0;
45 10           wvp->audio_offset = 0;
46 10           wvp->seeking = seeking ? 1 : 0;
47              
48 10           buffer_init(wvp->buf, WAVPACK_BLOCK_SIZE);
49              
50 10           wvp->file_size = _file_size(infile);
51 10           my_hv_store( info, "file_size", newSVuv(wvp->file_size) );
52              
53             // Loop through each wvpk block until we find a good one
54 19 100         while (!done) {
55 11 50         if ( !_check_buf(infile, wvp->buf, 32, WAVPACK_BLOCK_SIZE) ) {
56 0           err = -1;
57 0           goto out;
58             }
59              
60 11           bptr = buffer_ptr(wvp->buf);
61              
62             // If first byte is 'R', assume old version
63 11 100         if ( bptr[0] == 'R' ) {
64 2 50         if ( !_wavpack_parse_old(wvp) ) {
65 0           err = -1;
66 0           goto out;
67             }
68              
69 2           break;
70             }
71              
72             // May need to read past some junk before wvpk header
73 30729 100         while ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) {
    100          
    100          
    50          
74 30720           buffer_consume(wvp->buf, 1);
75              
76 30720           wvp->audio_offset++;
77              
78 30720 100         if ( !buffer_len(wvp->buf) ) {
79 7 50         if ( !_check_buf(infile, wvp->buf, 32, WAVPACK_BLOCK_SIZE) ) {
80 0           PerlIO_printf(PerlIO_stderr(), "Unable to find a valid WavPack block in file: %s\n", file);
81 0           err = -1;
82 0           goto out;
83             }
84             }
85              
86 30720           bptr = buffer_ptr(wvp->buf);
87             }
88              
89 9 100         if ( _wavpack_parse_block(wvp) ) {
90 8           done = 1;
91             }
92             }
93              
94 10           my_hv_store( info, "audio_offset", newSVuv(wvp->audio_offset) );
95 10           my_hv_store( info, "audio_size", newSVuv(wvp->file_size - wvp->audio_offset) );
96              
97             out:
98 10           buffer_free(wvp->buf);
99 10           Safefree(wvp->buf);
100 10           Safefree(wvp->header);
101              
102 10           return wvp;
103             }
104              
105             int
106 9           _wavpack_parse_block(wvpinfo *wvp)
107             {
108             unsigned char *bptr;
109             uint16_t remaining;
110              
111 9           bptr = buffer_ptr(wvp->buf);
112              
113             // Verify wvpk signature
114 9 50         if ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) {
    50          
    50          
    50          
115             DEBUG_TRACE("Invalid wvpk header at %llu\n", wvp->file_offset);
116 0           return 1;
117             }
118              
119 9           buffer_consume(wvp->buf, 4);
120              
121 9           wvp->header->ckSize = buffer_get_int_le(wvp->buf);
122 9           wvp->header->version = buffer_get_short_le(wvp->buf);
123 9           wvp->header->track_no = buffer_get_char(wvp->buf);
124 9           wvp->header->index_no = buffer_get_char(wvp->buf);
125 9           wvp->header->total_samples = buffer_get_int_le(wvp->buf);
126 9           wvp->header->block_index = buffer_get_int_le(wvp->buf);
127 9           wvp->header->block_samples = buffer_get_int_le(wvp->buf);
128 9           wvp->header->flags = buffer_get_int_le(wvp->buf);
129 9           wvp->header->crc = buffer_get_int_le(wvp->buf);
130              
131             DEBUG_TRACE("wvpk header @ %llu:\n", wvp->file_offset);
132             DEBUG_TRACE(" size: %u\n", wvp->header->ckSize);
133             DEBUG_TRACE(" version: 0x%x\n", wvp->header->version);
134             DEBUG_TRACE(" track_no: 0x%x\n", wvp->header->track_no);
135             DEBUG_TRACE(" index_no: 0x%x\n", wvp->header->index_no);
136             DEBUG_TRACE(" total_samples: %u\n", wvp->header->total_samples);
137             DEBUG_TRACE(" block_index: %u\n", wvp->header->block_index);
138             DEBUG_TRACE(" block_samples: %u\n", wvp->header->block_samples);
139             DEBUG_TRACE(" flags: 0x%x\n", wvp->header->flags);
140             DEBUG_TRACE(" crc: 0x%x\n", wvp->header->crc);
141              
142 9           wvp->file_offset += 32;
143              
144 9           my_hv_store( wvp->info, "encoder_version", newSVuv(wvp->header->version) );
145              
146 9 50         if (wvp->header->version < 0x4) {
147             // XXX old version and not handled by 'R' check above for old version
148 0           PerlIO_printf(PerlIO_stderr(), "Unsupported old WavPack version: 0x%x\n", wvp->header->version);
149 0           return 1;
150             }
151              
152             // Read data from flags
153 9           my_hv_store( wvp->info, "bits_per_sample", newSVuv( 8 * ((wvp->header->flags & 0x3) + 1) ) );
154              
155             // Encoding mode
156 9 100         my_hv_store( wvp->info, (wvp->header->flags & 0x8) ? "hybrid" : "lossless", newSVuv(1) );
    100          
157              
158             {
159             // samplerate, may be overridden by a later ID_SAMPLE_RATE metadata block
160 9           uint32_t samplerate_index = (wvp->header->flags & 0x7800000) >> 23;
161 9 100         if ( samplerate_index < 0xF ) {
162 8           my_hv_store( wvp->info, "samplerate", newSVuv( wavpack_sample_rates[samplerate_index] ) );
163             }
164             else {
165             // Default to 44.1 just in case
166 1           my_hv_store( wvp->info, "samplerate", newSVuv(44100) );
167             }
168             }
169              
170             // Channels, may be overridden by a later ID_CHANNEL_INFO metadata block
171 9 50         my_hv_store( wvp->info, "channels", newSVuv( (wvp->header->flags & 0x4) ? 1 : 2 ) );
172              
173             // Parse metadata sub-blocks
174 9           remaining = wvp->header->ckSize - 24; // ckSize is 8 less than the block size
175              
176             // If block_samples is 0, we need to skip to the next block
177 9 100         if ( !wvp->header->block_samples ) {
178 1           wvp->file_offset += remaining;
179 1           _wavpack_skip(wvp, remaining);
180 1           return 0;
181             }
182              
183 54 100         while (remaining > 0) {
184             // Read sub-block header (2-4 bytes)
185             unsigned char id;
186             uint32_t size;
187              
188             DEBUG_TRACE("remaining: %d\n", remaining);
189              
190 53 50         if ( !_check_buf(wvp->infile, wvp->buf, 4, WAVPACK_BLOCK_SIZE) ) {
191 0           return 0;
192             }
193              
194 53           id = buffer_get_char(wvp->buf);
195 53           remaining--;
196              
197             // Size is in words
198 53 100         if (id & ID_LARGE) {
199             // 24-bit large size
200 7           id &= ~ID_LARGE;
201 7           size = buffer_get_int24_le(wvp->buf) << 1;
202 7           remaining -= 3;
203             DEBUG_TRACE(" ID_LARGE, changed to %x\n", id);
204             }
205             else {
206             // 8-bit size
207 46           size = buffer_get_char(wvp->buf) << 1;
208 46           remaining--;
209             }
210              
211 53 100         if (id & ID_ODD_SIZE) {
212 13           id &= ~ID_ODD_SIZE;
213 13           size--;
214             DEBUG_TRACE(" ID_ODD_SIZE, changed to %x\n", id);
215             }
216              
217 53 100         if ( id == ID_WV_BITSTREAM || !size ) {
    100          
218             // Found the bitstream, don't read any farther
219             DEBUG_TRACE(" Sub-Chunk: WV_BITSTREAM (size %u)\n", size);
220             break;
221             }
222              
223             // We only care about 0x27 (ID_SAMPLE_RATE) and 0xd (ID_CHANNEL_INFO)
224 46           switch (id) {
225             case ID_SAMPLE_RATE:
226             DEBUG_TRACE(" Sub-Chunk: ID_SAMPLE_RATE (size: %u)\n", size);
227 1           _wavpack_parse_sample_rate(wvp, size);
228 1           break;
229              
230             case ID_CHANNEL_INFO:
231             DEBUG_TRACE(" Sub-Chunk: ID_CHANNEL_INFO (size: %u)\n", size);
232 1           _wavpack_parse_channel_info(wvp, size);
233 1           break;
234              
235             case ID_DSD_BLOCK:
236             DEBUG_TRACE(" Sub-Chunk: ID_DSD_BLOCK (size: %u)\n", size);
237 1           _wavpack_parse_dsd_block(wvp, size);
238 1           break;
239              
240             default:
241             // Skip it
242             DEBUG_TRACE(" Sub-Chunk: %x (size: %u) (skipped)\n", id, size);
243 43           _wavpack_skip(wvp, size);
244             }
245              
246 46           remaining -= size;
247              
248             // If size was odd, skip a byte
249 46 100         if (size & 0x1) {
250 13 50         if ( buffer_len(wvp->buf) ) {
251 13           buffer_consume(wvp->buf, 1);
252             }
253             else {
254 0           _wavpack_skip(wvp, 1);
255             }
256              
257 13           remaining--;
258             }
259             }
260              
261             // Calculate bitrate
262 8 50         if ( wvp->header->total_samples && wvp->file_size > 0 ) {
    50          
263 8           SV **samplerate = my_hv_fetch( wvp->info, "samplerate" );
264 8 50         if (samplerate != NULL) {
265             uint32_t song_length_ms;
266              
267 8 100         if (wvp->header->flags & 0x80000000)
268 1           wvp->header->total_samples *= 8; // DSD
269              
270 8 50         song_length_ms = ((wvp->header->total_samples * 1.0) / SvIV(*samplerate)) * 1000;
271              
272 8           my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) );
273 8           my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) );
274 8           my_hv_store( wvp->info, "total_samples", newSVuv(wvp->header->total_samples) );
275             }
276             }
277              
278 8           return 1;
279             }
280              
281             int
282 1           _wavpack_parse_sample_rate(wvpinfo *wvp, uint32_t size)
283             {
284 1           uint32_t samplerate = buffer_get_int24_le(wvp->buf);
285              
286 1           my_hv_store( wvp->info, "samplerate", newSVuv(samplerate) );
287              
288 1           return 1;
289             }
290              
291             int
292 1           _wavpack_parse_channel_info(wvpinfo *wvp, uint32_t size)
293             {
294             uint32_t channels;
295 1           unsigned char *bptr = buffer_ptr(wvp->buf);
296              
297 1 50         if (size == 6) {
298 0           channels = (bptr[0] | ((bptr[2] & 0xf) << 8)) + 1;
299             }
300             else {
301 1           channels = bptr[0];
302             }
303              
304 1           my_hv_store( wvp->info, "channels", newSVuv(channels) );
305              
306 1           buffer_consume(wvp->buf, size);
307              
308 1           return 1;
309             }
310              
311             int
312 1           _wavpack_parse_dsd_block(wvpinfo *wvp, uint32_t size)
313             {
314 1 50         if (wvp->header->flags & 0x80000000) {
315 1           unsigned char *bptr = buffer_ptr(wvp->buf);
316 1           uint32_t samplerate_index = (wvp->header->flags & 0x7800000) >> 23;
317             uint32_t samplerate;
318 1 50         if (samplerate_index < 0xF)
319 1           samplerate = wavpack_sample_rates[samplerate_index] * (1 << bptr[0]) * 8;
320             else
321 0           samplerate = 64 * 44100; // Default to DSD64 just in case
322              
323 1           my_hv_store( wvp->info, "samplerate", newSVuv(samplerate) );
324 1           my_hv_store( wvp->info, "bits_per_sample", newSVuv(1) );
325             }
326              
327 1           _wavpack_skip(wvp, size);
328              
329 1           return 1;
330             }
331              
332             void
333 45           _wavpack_skip(wvpinfo *wvp, uint32_t size)
334             {
335 45 50         if ( buffer_len(wvp->buf) >= size ) {
336             //buffer_dump(mp4->buf, size);
337 45           buffer_consume(wvp->buf, size);
338              
339             DEBUG_TRACE(" skipped buffer data size %d\n", size);
340             }
341             else {
342 0           PerlIO_seek(wvp->infile, size - buffer_len(wvp->buf), SEEK_CUR);
343 0           buffer_clear(wvp->buf);
344              
345             DEBUG_TRACE(" seeked past %d bytes to %d\n", size, (int)PerlIO_tell(wvp->infile));
346             }
347 45           }
348              
349             int
350 2           _wavpack_parse_old(wvpinfo *wvp)
351             {
352 2           int ret = 1;
353             char chunk_id[5];
354             uint32_t chunk_size;
355             WavpackHeader3 wphdr;
356             WaveHeader3 wavhdr;
357             unsigned char *bptr;
358             uint32_t total_samples;
359             uint32_t song_length_ms;
360              
361 2           Zero(&wavhdr, sizeof(wavhdr), char);
362 2           Zero(&wphdr, sizeof(wphdr), char);
363              
364             DEBUG_TRACE("Parsing old WavPack version\n");
365              
366             // Verify RIFF header
367 2 50         if ( strncmp( (char *)buffer_ptr(wvp->buf), "RIFF", 4 ) ) {
368 0           PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing RIFF header: %s\n", wvp->file);
369 0           ret = 0;
370 0           goto out;
371             }
372              
373 2           buffer_consume(wvp->buf, 4);
374              
375 2           chunk_size = buffer_get_int_le(wvp->buf);
376              
377             // Check format
378 2 50         if ( strncmp( (char *)buffer_ptr(wvp->buf), "WAVE", 4 ) ) {
379 0           PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing WAVE header: %s\n", wvp->file);
380 0           ret = 0;
381 0           goto out;
382             }
383              
384 2           buffer_consume(wvp->buf, 4);
385              
386 2           wvp->file_offset += 12;
387              
388             // Verify we have at least 8 bytes
389 2 50         if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) {
390 0           ret = 0;
391 0           goto out;
392             }
393              
394             // loop through all chunks, read fmt, and break at data
395 4 50         while ( buffer_len(wvp->buf) >= 8 ) {
396 4           strncpy( chunk_id, (char *)buffer_ptr(wvp->buf), 4 );
397 4           chunk_id[4] = '\0';
398 4           buffer_consume(wvp->buf, 4);
399              
400 4           chunk_size = buffer_get_int_le(wvp->buf);
401              
402 4           wvp->file_offset += 8;
403              
404             // Adjust for padding
405 4 50         if ( chunk_size % 2 ) {
406 0           chunk_size++;
407             }
408              
409             DEBUG_TRACE(" %s size %d\n", chunk_id, chunk_size);
410              
411 4 100         if ( !strcmp( chunk_id, "data" ) ) {
412 2           break;
413             }
414              
415 2           wvp->file_offset += chunk_size;
416              
417 2 50         if ( !strcmp( chunk_id, "fmt " ) ) {
418 2 50         if ( !_check_buf(wvp->infile, wvp->buf, chunk_size, WAV_BLOCK_SIZE) ) {
419 0           ret = 0;
420 0           goto out;
421             }
422              
423 2 50         if (chunk_size < sizeof(wavhdr)) {
424 0           ret = 0;
425 0           goto out;
426             }
427              
428             // Read wav header
429 2           wavhdr.FormatTag = buffer_get_short_le(wvp->buf);
430 2           wavhdr.NumChannels = buffer_get_short_le(wvp->buf);
431 2           wavhdr.SampleRate = buffer_get_int_le(wvp->buf);
432 2           wavhdr.BytesPerSecond = buffer_get_int_le(wvp->buf);
433 2           wavhdr.BlockAlign = buffer_get_short_le(wvp->buf);
434 2           wavhdr.BitsPerSample = buffer_get_short_le(wvp->buf);
435              
436             // Skip rest of fmt chunk if necessary
437 2 50         if (chunk_size > 16) {
438 2           _wavpack_skip(wvp, chunk_size - 16);
439             }
440             }
441             else {
442             // Skip it
443 0           _wavpack_skip(wvp, chunk_size);
444             }
445              
446             // Verify we have at least 8 bytes
447 2 50         if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) {
448 0           ret = 0;
449 0           goto out;
450             }
451             }
452              
453             // Verify wav header, this code comes from unpack3.c
454 2 50         if (
455 2 50         wavhdr.FormatTag != 1 || !wavhdr.NumChannels || wavhdr.NumChannels > 2 ||
    50          
    50          
456 2 50         !wavhdr.SampleRate || wavhdr.BitsPerSample < 16 || wavhdr.BitsPerSample > 24 ||
    50          
    50          
457 2 50         wavhdr.BlockAlign / wavhdr.NumChannels > 3 || wavhdr.BlockAlign % wavhdr.NumChannels ||
    50          
458 2           wavhdr.BlockAlign / wavhdr.NumChannels < (wavhdr.BitsPerSample + 7) / 8
459             ) {
460 0           ret = 0;
461 0           goto out;
462             }
463              
464             // chunk_size here is the size of the data chunk
465 2 50         total_samples = chunk_size / wavhdr.NumChannels / ((wavhdr.BitsPerSample > 16) ? 3 : 2);
466              
467             // read WavpackHeader3 (differs for each version)
468 2           bptr = buffer_ptr(wvp->buf);
469 2 50         if ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) {
    50          
    50          
    50          
470 0           PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing wvpk header: %s\n", wvp->file);
471 0           ret = 0;
472 0           goto out;
473             }
474              
475 2           buffer_consume(wvp->buf, 4);
476              
477 2           wphdr.ckSize = buffer_get_int_le(wvp->buf);
478 2           wphdr.version = buffer_get_short_le(wvp->buf);
479              
480 2 50         if (wphdr.version >= 2) {
481 2           wphdr.bits = buffer_get_short_le(wvp->buf);
482             }
483              
484 2 100         if (wphdr.version == 3) {
485 1           wphdr.flags = buffer_get_short_le(wvp->buf);
486 1           wphdr.shift = buffer_get_short_le(wvp->buf);
487 1           wphdr.total_samples = buffer_get_int_le(wvp->buf);
488              
489 1           total_samples = wphdr.total_samples;
490             }
491              
492             DEBUG_TRACE("wvpk header @ %llu:\n", wvp->file_offset);
493             DEBUG_TRACE(" size: %u\n", wphdr.ckSize);
494             DEBUG_TRACE(" version: %d\n", wphdr.version);
495             DEBUG_TRACE(" bits: 0x%x\n", wphdr.bits);
496             DEBUG_TRACE(" flags: 0x%x\n", wphdr.flags);
497             DEBUG_TRACE(" shift: 0x%x\n", wphdr.shift);
498             DEBUG_TRACE(" total_samples: %d\n", wphdr.total_samples);
499              
500 2           my_hv_store( wvp->info, "encoder_version", newSVuv(wphdr.version) );
501 2           my_hv_store( wvp->info, "bits_per_sample", newSVuv(wavhdr.BitsPerSample) );
502 2           my_hv_store( wvp->info, "channels", newSVuv(wavhdr.NumChannels) );
503 2           my_hv_store( wvp->info, "samplerate", newSVuv(wavhdr.SampleRate) );
504 2           my_hv_store( wvp->info, "total_samples", newSVuv(total_samples) );
505              
506 2           song_length_ms = ((total_samples * 1.0) / wavhdr.SampleRate) * 1000;
507 2           my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) );
508 2           my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) );
509              
510             out:
511 2           return ret;
512             }
513