File Coverage

src/wavpack.c
Criterion Covered Total %
statement 194 232 83.6
branch 84 128 65.6
condition n/a
subroutine n/a
pod n/a
total 278 360 77.2


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 10           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 62 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 1           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 1           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 1           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 43           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           song_length_ms = ((wvp->header->total_samples * 1.0) / SvIV(*samplerate)) * 1000;
268              
269             // flags bit 31: 0 = PCM audio; 1 = DSD audio (5.0+)
270 8 100         if (wvp->header->flags & 0x80000000)
271 1           song_length_ms *= 8;
272              
273 8           my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) );
274 8           my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) );
275 8           my_hv_store( wvp->info, "total_samples", newSVuv(wvp->header->total_samples) );
276             }
277             }
278              
279 8           return 1;
280             }
281              
282             int
283 1           _wavpack_parse_sample_rate(wvpinfo *wvp, uint32_t size)
284             {
285 1           uint32_t samplerate = buffer_get_int24_le(wvp->buf);
286              
287 1           my_hv_store( wvp->info, "samplerate", newSVuv(samplerate) );
288              
289 1           return 1;
290             }
291              
292             int
293 1           _wavpack_parse_channel_info(wvpinfo *wvp, uint32_t size)
294             {
295             uint32_t channels;
296 1           unsigned char *bptr = buffer_ptr(wvp->buf);
297              
298 1 50         if (size == 6) {
299 0           channels = (bptr[0] | ((bptr[2] & 0xf) << 8)) + 1;
300             }
301             else {
302 1           channels = bptr[0];
303             }
304              
305 1           my_hv_store( wvp->info, "channels", newSVuv(channels) );
306              
307 1           buffer_consume(wvp->buf, size);
308              
309 1           return 1;
310             }
311              
312             int
313 1           _wavpack_parse_dsd_block(wvpinfo *wvp, uint32_t size)
314             {
315 1 50         if (wvp->header->flags & 0x80000000) {
316 1           unsigned char *bptr = buffer_ptr(wvp->buf);
317 1           uint32_t samplerate_index = (wvp->header->flags & 0x7800000) >> 23;
318             uint32_t samplerate;
319 1 50         if (samplerate_index < 0xF)
320 1           samplerate = wavpack_sample_rates[samplerate_index] * (1 << bptr[0]) * 8;
321             else
322 0           samplerate = 64 * 44100; // Default to DSD64 just in case
323              
324 1           my_hv_store( wvp->info, "samplerate", newSVuv(samplerate) );
325 1           my_hv_store( wvp->info, "bits_per_sample", newSVuv(1) );
326             }
327              
328 1           _wavpack_skip(wvp, size);
329              
330 1           return 1;
331             }
332              
333             void
334 45           _wavpack_skip(wvpinfo *wvp, uint32_t size)
335             {
336 45 50         if ( buffer_len(wvp->buf) >= size ) {
337             //buffer_dump(mp4->buf, size);
338 45           buffer_consume(wvp->buf, size);
339              
340             DEBUG_TRACE(" skipped buffer data size %d\n", size);
341             }
342             else {
343 0           PerlIO_seek(wvp->infile, size - buffer_len(wvp->buf), SEEK_CUR);
344 0           buffer_clear(wvp->buf);
345              
346             DEBUG_TRACE(" seeked past %d bytes to %d\n", size, (int)PerlIO_tell(wvp->infile));
347             }
348 45           }
349              
350             int
351 2           _wavpack_parse_old(wvpinfo *wvp)
352             {
353 2           int ret = 1;
354             char chunk_id[5];
355             uint32_t chunk_size;
356             WavpackHeader3 wphdr;
357             WaveHeader3 wavhdr;
358             unsigned char *bptr;
359             uint32_t total_samples;
360             uint32_t song_length_ms;
361              
362 2           Zero(&wavhdr, sizeof(wavhdr), char);
363 2           Zero(&wphdr, sizeof(wphdr), char);
364              
365             DEBUG_TRACE("Parsing old WavPack version\n");
366              
367             // Verify RIFF header
368 2 50         if ( strncmp( (char *)buffer_ptr(wvp->buf), "RIFF", 4 ) ) {
369 0           PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing RIFF header: %s\n", wvp->file);
370 0           ret = 0;
371 0           goto out;
372             }
373              
374 2           buffer_consume(wvp->buf, 4);
375              
376 2           chunk_size = buffer_get_int_le(wvp->buf);
377              
378             // Check format
379 2 50         if ( strncmp( (char *)buffer_ptr(wvp->buf), "WAVE", 4 ) ) {
380 0           PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing WAVE header: %s\n", wvp->file);
381 0           ret = 0;
382 0           goto out;
383             }
384              
385 2           buffer_consume(wvp->buf, 4);
386              
387 2           wvp->file_offset += 12;
388              
389             // Verify we have at least 8 bytes
390 2 50         if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) {
391 0           ret = 0;
392 0           goto out;
393             }
394              
395             // loop through all chunks, read fmt, and break at data
396 4 50         while ( buffer_len(wvp->buf) >= 8 ) {
397 4           strncpy( chunk_id, (char *)buffer_ptr(wvp->buf), 4 );
398 4           chunk_id[4] = '\0';
399 4           buffer_consume(wvp->buf, 4);
400              
401 4           chunk_size = buffer_get_int_le(wvp->buf);
402              
403 4           wvp->file_offset += 8;
404              
405             // Adjust for padding
406 4 50         if ( chunk_size % 2 ) {
407 0           chunk_size++;
408             }
409              
410             DEBUG_TRACE(" %s size %d\n", chunk_id, chunk_size);
411              
412 4 100         if ( !strcmp( chunk_id, "data" ) ) {
413 2           break;
414             }
415              
416 2           wvp->file_offset += chunk_size;
417              
418 2 50         if ( !strcmp( chunk_id, "fmt " ) ) {
419 2 50         if ( !_check_buf(wvp->infile, wvp->buf, chunk_size, WAV_BLOCK_SIZE) ) {
420 0           ret = 0;
421 0           goto out;
422             }
423              
424 2 50         if (chunk_size < sizeof(wavhdr)) {
425 0           ret = 0;
426 0           goto out;
427             }
428              
429             // Read wav header
430 2           wavhdr.FormatTag = buffer_get_short_le(wvp->buf);
431 2           wavhdr.NumChannels = buffer_get_short_le(wvp->buf);
432 2           wavhdr.SampleRate = buffer_get_int_le(wvp->buf);
433 2           wavhdr.BytesPerSecond = buffer_get_int_le(wvp->buf);
434 2           wavhdr.BlockAlign = buffer_get_short_le(wvp->buf);
435 2           wavhdr.BitsPerSample = buffer_get_short_le(wvp->buf);
436              
437             // Skip rest of fmt chunk if necessary
438 2 50         if (chunk_size > 16) {
439 0           _wavpack_skip(wvp, chunk_size - 16);
440             }
441             }
442             else {
443             // Skip it
444 0           _wavpack_skip(wvp, chunk_size);
445             }
446              
447             // Verify we have at least 8 bytes
448 2 50         if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) {
449 0           ret = 0;
450 0           goto out;
451             }
452             }
453              
454             // Verify wav header, this code comes from unpack3.c
455 2           if (
456 2 50         wavhdr.FormatTag != 1 || !wavhdr.NumChannels || wavhdr.NumChannels > 2 ||
    50          
    50          
457 2 50         !wavhdr.SampleRate || wavhdr.BitsPerSample < 16 || wavhdr.BitsPerSample > 24 ||
    50          
    50          
458 2 50         wavhdr.BlockAlign / wavhdr.NumChannels > 3 || wavhdr.BlockAlign % wavhdr.NumChannels ||
    50          
459 2 50         wavhdr.BlockAlign / wavhdr.NumChannels < (wavhdr.BitsPerSample + 7) / 8
460             ) {
461 0           ret = 0;
462 0           goto out;
463             }
464              
465             // chunk_size here is the size of the data chunk
466 2 50         total_samples = chunk_size / wavhdr.NumChannels / ((wavhdr.BitsPerSample > 16) ? 3 : 2);
467              
468             // read WavpackHeader3 (differs for each version)
469 2           bptr = buffer_ptr(wvp->buf);
470 2 50         if ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) {
    50          
    50          
    50          
471 0           PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing wvpk header: %s\n", wvp->file);
472 0           ret = 0;
473 0           goto out;
474             }
475              
476 2           buffer_consume(wvp->buf, 4);
477              
478 2           wphdr.ckSize = buffer_get_int_le(wvp->buf);
479 2           wphdr.version = buffer_get_short_le(wvp->buf);
480              
481 2 50         if (wphdr.version >= 2) {
482 2           wphdr.bits = buffer_get_short_le(wvp->buf);
483             }
484              
485 2 100         if (wphdr.version == 3) {
486 1           wphdr.flags = buffer_get_short_le(wvp->buf);
487 1           wphdr.shift = buffer_get_short_le(wvp->buf);
488 1           wphdr.total_samples = buffer_get_int_le(wvp->buf);
489              
490 1           total_samples = wphdr.total_samples;
491             }
492              
493             DEBUG_TRACE("wvpk header @ %llu:\n", wvp->file_offset);
494             DEBUG_TRACE(" size: %u\n", wphdr.ckSize);
495             DEBUG_TRACE(" version: %d\n", wphdr.version);
496             DEBUG_TRACE(" bits: 0x%x\n", wphdr.bits);
497             DEBUG_TRACE(" flags: 0x%x\n", wphdr.flags);
498             DEBUG_TRACE(" shift: 0x%x\n", wphdr.shift);
499             DEBUG_TRACE(" total_samples: %d\n", wphdr.total_samples);
500              
501 2           my_hv_store( wvp->info, "encoder_version", newSVuv(wphdr.version) );
502 2           my_hv_store( wvp->info, "bits_per_sample", newSVuv(wavhdr.BitsPerSample) );
503 2           my_hv_store( wvp->info, "channels", newSVuv(wavhdr.NumChannels) );
504 2           my_hv_store( wvp->info, "samplerate", newSVuv(wavhdr.SampleRate) );
505 2           my_hv_store( wvp->info, "total_samples", newSVuv(total_samples) );
506              
507 2           song_length_ms = ((total_samples * 1.0) / wavhdr.SampleRate) * 1000;
508 2           my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) );
509 2           my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) );
510              
511 2           out:
512 2           return ret;
513             }
514