File Coverage

src/dsdiff.c
Criterion Covered Total %
statement 83 141 58.8
branch 31 82 37.8
condition n/a
subroutine n/a
pod n/a
total 114 223 51.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             #define PROP_CK (uint8_t)1
18             #define DSD_CK (uint8_t)2
19             #define DIIN_CK (uint8_t)4
20             #define ERROR_CK (uint8_t)128
21             #include "dsdiff.h"
22              
23             typedef struct {
24             PerlIO *infile;
25             Buffer *buf;
26             char *file;
27             HV *info;
28             HV *tags;
29             uint32_t channel_num;
30             uint32_t sampling_frequency;
31             uint64_t metadata_offset;
32             uint64_t sample_count;
33             uint64_t offset;
34             uint64_t audio_offset;
35             char *tag_diar_artist;
36             char *tag_diti_title;
37             } dsdiff_info;
38              
39             static uint8_t
40 0           parse_diin_chunk(dsdiff_info *dsdiff, uint64_t size)
41             {
42 0           uint64_t ck_offset = 0;
43              
44 0 0         while (ck_offset < size) {
45             char chunk_id[5];
46             uint64_t chunk_size;
47             uint32_t count;
48              
49 0           buffer_clear(dsdiff->buf);
50 0           PerlIO_seek(dsdiff->infile, dsdiff->offset + ck_offset, SEEK_SET);
51              
52 0 0         if ( !_check_buf(dsdiff->infile, dsdiff->buf, 12, DSDIFF_BLOCK_SIZE) ) return ERROR_CK;
53 0           strncpy(chunk_id, (char *)buffer_ptr(dsdiff->buf), 4);
54 0           chunk_id[4] = '\0';
55 0           buffer_consume(dsdiff->buf, 4);
56 0           chunk_size = buffer_get_int64(dsdiff->buf);
57 0           ck_offset += 12;
58              
59             DEBUG_TRACE(" diin: %s : %" PRIu64 ",%" PRIu64 "\n", chunk_id, dsdiff->offset + ck_offset, chunk_size);
60              
61 0 0         if ( !strcmp(chunk_id, "DIAR") ) {
62 0 0         if ( !_check_buf(dsdiff->infile, dsdiff->buf, chunk_size, DSDIFF_BLOCK_SIZE) ) return ERROR_CK;
63 0           count = buffer_get_int(dsdiff->buf);;
64 0           dsdiff->tag_diar_artist = (char *)malloc(count + 1);
65 0           strncpy(dsdiff->tag_diar_artist, (char *)buffer_ptr(dsdiff->buf), count);
66 0           dsdiff->tag_diar_artist[count] = '\0';
67 0 0         } else if ( !strcmp(chunk_id, "DITI") ) {
68 0 0         if ( !_check_buf(dsdiff->infile, dsdiff->buf, chunk_size, DSDIFF_BLOCK_SIZE) ) return ERROR_CK;
69 0           count = buffer_get_int(dsdiff->buf);;
70 0           dsdiff->tag_diti_title = (char *)malloc(count + 1);
71 0           strncpy(dsdiff->tag_diti_title, (char *)buffer_ptr(dsdiff->buf), count);
72 0           dsdiff->tag_diti_title[count] = '\0';
73             }
74              
75 0           ck_offset += chunk_size;
76             }
77              
78 0           return DIIN_CK;
79             }
80              
81             static uint8_t
82 2           parse_prop_chunk(dsdiff_info *dsdiff, uint64_t size)
83             {
84 2           uint64_t ck_offset = 0;
85              
86 2 50         if ( !_check_buf(dsdiff->infile, dsdiff->buf, 4, DSDIFF_BLOCK_SIZE) ) return ERROR_CK;
87 2 50         if ( strncmp( (char *)buffer_ptr(dsdiff->buf), "SND ", 4 ) ) return 0;
88 2           ck_offset += 4;
89              
90 8 100         while (ck_offset < size) {
91             char chunk_id[5];
92             uint64_t chunk_size;
93              
94 6           buffer_clear(dsdiff->buf);
95 6           PerlIO_seek(dsdiff->infile, dsdiff->offset + ck_offset, SEEK_SET);
96              
97 6 50         if ( !_check_buf(dsdiff->infile, dsdiff->buf, 16, DSDIFF_BLOCK_SIZE) ) return ERROR_CK;
98 6           strncpy(chunk_id, (char *)buffer_ptr(dsdiff->buf), 4);
99 6           chunk_id[4] = '\0';
100 6           buffer_consume(dsdiff->buf, 4);
101 6           chunk_size = buffer_get_int64(dsdiff->buf);
102 6           ck_offset += 12;
103              
104             DEBUG_TRACE(" prop: %s : %" PRIu64 ",%" PRIu64 "\n", chunk_id, dsdiff->offset + ck_offset, chunk_size);
105              
106 6 100         if ( !strcmp(chunk_id, "FS ") ) {
107 2           dsdiff->sampling_frequency = buffer_get_int(dsdiff->buf);
108 4 100         } else if ( !strcmp(chunk_id, "CHNL") ) {
109 2           dsdiff->channel_num = (uint32_t)buffer_get_short(dsdiff->buf);
110 2 50         } else if ( !strcmp(chunk_id, "ID3 ") ) {
111 0           dsdiff->metadata_offset = dsdiff->offset + ck_offset;
112             }
113 6           ck_offset += chunk_size;
114             }
115              
116 2 50         if (dsdiff->channel_num == 0 || dsdiff->sampling_frequency == 0) return ERROR_CK;
    50          
117              
118 2           return PROP_CK;
119             }
120              
121             int
122 2           get_dsdiff_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
123             {
124             Buffer buf;
125 2           uint8_t flags = 0;
126             off_t file_size;
127 2           int err = 0;
128             uint64_t total_size;
129             dsdiff_info dsdiff;
130             unsigned char *bptr;
131             uint32_t song_length_ms;
132              
133 2           dsdiff.infile = infile;
134 2           dsdiff.buf = &buf;
135 2           dsdiff.file = file;
136 2           dsdiff.info = info;
137 2           dsdiff.tags = tags;
138 2           dsdiff.channel_num = 0;
139 2           dsdiff.sampling_frequency = 0;
140 2           dsdiff.metadata_offset = 0;
141 2           dsdiff.sample_count = 0;
142 2           dsdiff.offset = 0;
143 2           dsdiff.audio_offset = 0;
144 2           dsdiff.tag_diar_artist = NULL;
145 2           dsdiff.tag_diti_title = NULL;
146              
147 2           file_size = _file_size(infile);
148              
149 2           buffer_init(&buf, DSDIFF_BLOCK_SIZE);
150              
151 2 50         if ( !_check_buf(infile, &buf, 16, DSDIFF_BLOCK_SIZE) ) {
152 0           err = -1;
153 0           goto out;
154             }
155              
156 2 50         if ( !strncmp( (char *)buffer_ptr(&buf), "FRM8", 4 ) ) {
157 2           buffer_consume(&buf, 4);
158 2           total_size = buffer_get_int64(&buf) + 12;
159 2           dsdiff.offset += 12;
160              
161 2 50         if (strncmp( (char *)buffer_ptr(&buf), "DSD ", 4 ) ) {
162 0           PerlIO_printf(PerlIO_stderr(), "Invalid DSDIFF file header: %s\n", file);
163 0           err = -1;
164 0           goto out;
165             }
166 2           dsdiff.offset += 4;
167              
168 2           my_hv_store( info, "file_size", newSVuv(file_size) );
169              
170 10 100         while (dsdiff.offset <= total_size - 12) {
171             char chunk_id[5];
172             uint64_t chunk_size;
173              
174 8           buffer_clear(&buf);
175 8           PerlIO_seek(infile, dsdiff.offset, SEEK_SET);
176              
177 8 50         if ( !_check_buf(infile, &buf, 12, DSDIFF_BLOCK_SIZE) ) {
178 0           PerlIO_printf(PerlIO_stderr(), "DSDIFF file error: %s\n", file);
179 0           err = -1;
180 0           goto out;
181             };
182              
183 8           strncpy(chunk_id, (char *)buffer_ptr(&buf), 4);
184 8           chunk_id[4] = '\0';
185 8           buffer_consume(&buf, 4);
186 8           chunk_size = buffer_get_int64(&buf);
187 8           dsdiff.offset += 12;
188              
189             DEBUG_TRACE("%s: %" PRIu64 ",%" PRIu64 "\n", chunk_id, dsdiff.offset, chunk_size);
190              
191 8 100         if (!strcmp(chunk_id, "PROP")) {
192 2           flags |= parse_prop_chunk(&dsdiff, chunk_size);
193 6 50         } else if (!strcmp(chunk_id, "DIIN")) {
194 0           flags |= parse_diin_chunk(&dsdiff, chunk_size);
195 6 100         } else if (!strcmp(chunk_id, "DSD ")) {
196 2           dsdiff.sample_count = 8 * chunk_size / dsdiff.channel_num;
197 2           dsdiff.audio_offset = dsdiff.offset;
198 2           flags |= DSD_CK;
199 4 50         } else if ( !strcmp(chunk_id, "ID3 ") ) {
200 0           dsdiff.metadata_offset = dsdiff.offset;
201             }
202              
203 8 50         if ( flags & ERROR_CK ) {
204 0           PerlIO_printf(PerlIO_stderr(), "DSDIFF chunk error: %s\n", file);
205 0           err = -1;
206 0           goto out;
207             };
208              
209 8           dsdiff.offset += chunk_size;
210             }
211              
212             DEBUG_TRACE("Finished parsing...\n");
213              
214 2 50         if ((flags & DSD_CK) == 0 || (flags & PROP_CK) == 0) {
    50          
215 0           PerlIO_printf(PerlIO_stderr(), "DSDIFF file error: %s\n", file);
216 0           err = -1;
217 0           goto out;
218             };
219              
220 2           song_length_ms = ((dsdiff.sample_count * 1.0) / dsdiff.sampling_frequency) * 1000;
221              
222             DEBUG_TRACE("audio_offset: %" PRIu64 "\n", dsdiff.audio_offset);
223             DEBUG_TRACE("audio_size: %" PRIu64 "\n", dsdiff.sample_count / 8 * dsdiff.channel_num);
224             DEBUG_TRACE("samplerate: %" PRIu32 "\n", dsdiff.sampling_frequency);
225             DEBUG_TRACE("song_length_ms: %u\n", song_length_ms);
226             DEBUG_TRACE("channels: %" PRIu32 "\n", dsdiff.channel_num);
227              
228 2           my_hv_store( info, "audio_offset", newSVuv(dsdiff.audio_offset) );
229 2           my_hv_store( info, "audio_size", newSVuv(dsdiff.sample_count / 8 * dsdiff.channel_num) );
230 2           my_hv_store( info, "samplerate", newSVuv(dsdiff.sampling_frequency) );
231 2           my_hv_store( info, "song_length_ms", newSVuv(song_length_ms) );
232 2           my_hv_store( info, "channels", newSVuv(dsdiff.channel_num) );
233 2           my_hv_store( info, "bits_per_sample", newSVuv(1) );
234 2           my_hv_store( info, "bitrate", newSVuv( _bitrate(file_size - dsdiff.audio_offset, song_length_ms) ) );
235              
236 2 50         if (dsdiff.tag_diar_artist) {
237 0           my_hv_store( info, "tag_diar_artist", newSVpv(dsdiff.tag_diar_artist, 0) );
238 0           free(dsdiff.tag_diar_artist);
239             }
240              
241 2 50         if (dsdiff.tag_diti_title) {
242 0           my_hv_store( info, "tag_diti_title", newSVpv(dsdiff.tag_diti_title, 0) );
243 0           free(dsdiff.tag_diti_title);
244             }
245              
246             DEBUG_TRACE("Stored info values...\n");
247              
248 2 50         if (dsdiff.metadata_offset) {
249 0           PerlIO_seek(infile, dsdiff.metadata_offset, SEEK_SET);
250 0           buffer_clear(&buf);
251 0 0         if ( !_check_buf(infile, &buf, 10, DSDIFF_BLOCK_SIZE) ) {
252 0           goto out;
253             }
254              
255 0           bptr = buffer_ptr(&buf);
256 0 0         if (
257 0 0         (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') &&
    0          
    0          
258 0 0         bptr[3] < 0xff && bptr[4] < 0xff &&
    0          
259 0 0         bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
    0          
    0          
260             ) {
261 2           parse_id3(infile, file, info, tags, dsdiff.metadata_offset, file_size);
262             }
263             }
264             } else {
265 0           PerlIO_printf(PerlIO_stderr(), "Invalid DSF file: missing DSD header: %s\n", file);
266 0           err = -1;
267 0           goto out;
268             }
269              
270             out:
271 2           buffer_free(&buf);
272              
273 2 50         if (err) return err;
274              
275 2           return 0;
276             }
277