diff options
author | Dave Chapman <dave@dchapman.com> | 2005-06-14 22:27:57 +0000 |
---|---|---|
committer | Dave Chapman <dave@dchapman.com> | 2005-06-14 22:27:57 +0000 |
commit | 3ad485b15a51e92a23540429b64e89656555bba6 (patch) | |
tree | a84ff392ab5e5cc6a6e170e7607996d7fd4d13d3 /apps/metadata.c | |
parent | 88a89e0cdbfc4d745c48ff607b0774192d66218c (diff) | |
download | rockbox-3ad485b15a51e92a23540429b64e89656555bba6.tar.gz rockbox-3ad485b15a51e92a23540429b64e89656555bba6.zip |
Move metadata parsing code from playback.c into metadata.c
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@6714 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/metadata.c')
-rw-r--r-- | apps/metadata.c | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/apps/metadata.c b/apps/metadata.c new file mode 100644 index 0000000000..35ed900efb --- /dev/null +++ b/apps/metadata.c | |||
@@ -0,0 +1,342 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2005 Dave Chapman | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | #include <stdio.h> | ||
20 | #include <string.h> | ||
21 | #include <stdlib.h> | ||
22 | #include <ctype.h> | ||
23 | |||
24 | #include "metadata.h" | ||
25 | #include "mp3_playback.h" | ||
26 | #include "mp3data.h" | ||
27 | #include "logf.h" | ||
28 | |||
29 | /* Simple file type probing by looking filename extension. */ | ||
30 | int probe_file_format(const char *filename) | ||
31 | { | ||
32 | char *suffix; | ||
33 | |||
34 | suffix = strrchr(filename, '.'); | ||
35 | if (suffix == NULL) | ||
36 | return AFMT_UNKNOWN; | ||
37 | suffix += 1; | ||
38 | |||
39 | if (!strcasecmp("mp1", suffix)) | ||
40 | return AFMT_MPA_L1; | ||
41 | else if (!strcasecmp("mp2", suffix)) | ||
42 | return AFMT_MPA_L2; | ||
43 | else if (!strcasecmp("mpa", suffix)) | ||
44 | return AFMT_MPA_L2; | ||
45 | else if (!strcasecmp("mp3", suffix)) | ||
46 | return AFMT_MPA_L3; | ||
47 | else if (!strcasecmp("ogg", suffix)) | ||
48 | return AFMT_OGG_VORBIS; | ||
49 | else if (!strcasecmp("wav", suffix)) | ||
50 | return AFMT_PCM_WAV; | ||
51 | else if (!strcasecmp("flac", suffix)) | ||
52 | return AFMT_FLAC; | ||
53 | else if (!strcasecmp("mpc", suffix)) | ||
54 | return AFMT_MPC; | ||
55 | else if (!strcasecmp("aac", suffix)) | ||
56 | return AFMT_AAC; | ||
57 | else if (!strcasecmp("ape", suffix)) | ||
58 | return AFMT_APE; | ||
59 | else if (!strcasecmp("wma", suffix)) | ||
60 | return AFMT_WMA; | ||
61 | else if ((!strcasecmp("a52", suffix)) || (!strcasecmp("ac3", suffix))) | ||
62 | return AFMT_A52; | ||
63 | else if (!strcasecmp("rm", suffix)) | ||
64 | return AFMT_REAL; | ||
65 | else if (!strcasecmp("wv", suffix)) | ||
66 | return AFMT_WAVPACK; | ||
67 | |||
68 | return AFMT_UNKNOWN; | ||
69 | |||
70 | } | ||
71 | |||
72 | /* Get metadata for track - return false if parsing showed problems with the | ||
73 | file that would prevent playback. */ | ||
74 | |||
75 | bool get_metadata(struct track_info* track, int fd, const char* trackname, | ||
76 | bool v1first) { | ||
77 | unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; | ||
78 | unsigned char* buf; | ||
79 | int i,j,eof; | ||
80 | int rc; | ||
81 | |||
82 | /* Load codec specific track tag information. */ | ||
83 | switch (track->codectype) { | ||
84 | case AFMT_MPA_L2: | ||
85 | case AFMT_MPA_L3: | ||
86 | /* Should check the return value. */ | ||
87 | mp3info(&track->id3, trackname, v1first); | ||
88 | lseek(fd, 0, SEEK_SET); | ||
89 | |||
90 | /* This is too slow to execute on some files. */ | ||
91 | get_mp3file_info(fd, &track->mp3data); | ||
92 | lseek(fd, 0, SEEK_SET); | ||
93 | |||
94 | /* | ||
95 | logf("T:%s", track->id3.title); | ||
96 | logf("L:%d", track->id3.length); | ||
97 | logf("O:%d", track->id3.first_frame_offset); | ||
98 | logf("F:%d", track->id3.frequency); | ||
99 | */ | ||
100 | track->taginfo_ready = true; | ||
101 | break ; | ||
102 | |||
103 | case AFMT_PCM_WAV: | ||
104 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
105 | buf=track->id3.path; | ||
106 | |||
107 | lseek(fd, 0, SEEK_SET); | ||
108 | |||
109 | rc = read(fd, buf, 44); | ||
110 | if (rc < 44) { | ||
111 | return false; | ||
112 | } | ||
113 | |||
114 | if ((memcmp(buf,"RIFF",4)!=0) || | ||
115 | (memcmp(&buf[8],"WAVEfmt",7)!=0)) { | ||
116 | logf("%s is not a WAV file\n",trackname); | ||
117 | return(false); | ||
118 | } | ||
119 | |||
120 | /* FIX: Correctly parse WAV header - we assume canonical | ||
121 | 44-byte header */ | ||
122 | |||
123 | bitspersample=buf[34]; | ||
124 | channels=buf[22]; | ||
125 | |||
126 | if ((bitspersample!=16) || (channels != 2)) { | ||
127 | logf("Unsupported WAV file - %d bitspersample, %d channels\n", | ||
128 | bitspersample,channels); | ||
129 | return(false); | ||
130 | } | ||
131 | |||
132 | bytespersample=((bitspersample/8)*channels); | ||
133 | numbytes=(buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24)); | ||
134 | totalsamples=numbytes/bytespersample; | ||
135 | |||
136 | track->id3.vbr=false; /* All WAV files are CBR */ | ||
137 | track->id3.filesize=filesize(fd); | ||
138 | track->id3.frequency=buf[24]|(buf[25]<<8)|(buf[26]<<16)|(buf[27]<<24); | ||
139 | |||
140 | /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ | ||
141 | track->id3.length=(totalsamples/track->id3.frequency)*1000; | ||
142 | track->id3.bitrate=(track->id3.frequency*bytespersample)/(1000/8); | ||
143 | |||
144 | lseek(fd, 0, SEEK_SET); | ||
145 | strncpy(track->id3.path,trackname,sizeof(track->id3.path)); | ||
146 | track->taginfo_ready = true; | ||
147 | |||
148 | break; | ||
149 | |||
150 | case AFMT_FLAC: | ||
151 | /* A simple parser to read vital metadata from a FLAC file - length, frequency, bitrate etc. */ | ||
152 | /* This code should either be moved to a seperate file, or discarded in favour of the libFLAC code */ | ||
153 | /* The FLAC stream specification can be found at http://flac.sourceforge.net/format.html#stream */ | ||
154 | |||
155 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
156 | buf=track->id3.path; | ||
157 | |||
158 | lseek(fd, 0, SEEK_SET); | ||
159 | |||
160 | rc = read(fd, buf, 4); | ||
161 | if (rc < 4) { | ||
162 | return false; | ||
163 | } | ||
164 | |||
165 | if (memcmp(buf,"fLaC",4)!=0) { | ||
166 | logf("%s is not a FLAC file\n",trackname); | ||
167 | return(false); | ||
168 | } | ||
169 | |||
170 | while (1) { | ||
171 | rc = read(fd, buf, 4); | ||
172 | i = (buf[1]<<16)|(buf[2]<<8)|buf[3]; /* The length of the block */ | ||
173 | |||
174 | if ((buf[0]&0x7f)==0) { /* 0 is the STREAMINFO block */ | ||
175 | rc = read(fd, buf, i); /* FIXME: Don't trust the value of i */ | ||
176 | if (rc < 0) { | ||
177 | return false; | ||
178 | } | ||
179 | track->id3.vbr=true; /* All FLAC files are VBR */ | ||
180 | track->id3.filesize=filesize(fd); | ||
181 | |||
182 | track->id3.frequency=(buf[10]<<12)|(buf[11]<<4)|((buf[12]&0xf0)>>4); | ||
183 | |||
184 | /* NOT NEEDED: bitspersample=(((buf[12]&0x01)<<4)|((buf[13]&0xf0)>>4))+1; */ | ||
185 | |||
186 | /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */ | ||
187 | totalsamples=(buf[14]<<24)|(buf[15]<<16)|(buf[16]<<8)|buf[17]; | ||
188 | |||
189 | /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ | ||
190 | track->id3.length=(totalsamples/track->id3.frequency)*1000; | ||
191 | track->id3.bitrate=(filesize(fd)*8)/track->id3.length; | ||
192 | } else if ((buf[0]&0x7f)==4) { /* 4 is the VORBIS_COMMENT block */ | ||
193 | |||
194 | /* The next i bytes of the file contain the VORBIS COMMENTS - just skip them for now. */ | ||
195 | lseek(fd, i, SEEK_CUR); | ||
196 | |||
197 | } else { | ||
198 | if (buf[0]&0x80) { /* If we have reached the last metadata block, abort. */ | ||
199 | break; | ||
200 | } else { | ||
201 | lseek(fd, i, SEEK_CUR); /* Skip to next metadata block */ | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | lseek(fd, 0, SEEK_SET); | ||
207 | strncpy(track->id3.path,trackname,sizeof(track->id3.path)); | ||
208 | track->taginfo_ready = true; | ||
209 | break; | ||
210 | |||
211 | case AFMT_OGG_VORBIS: | ||
212 | /* A simple parser to read vital metadata from an Ogg Vorbis file */ | ||
213 | |||
214 | /* An Ogg File is split into pages, each starting with the string | ||
215 | "OggS". Each page has a timestamp (in PCM samples) referred to as | ||
216 | the "granule position". | ||
217 | |||
218 | An Ogg Vorbis has the following structure: | ||
219 | 1) Identification header (containing samplerate, numchannels, etc) | ||
220 | 2) Comment header - containing the Vorbis Comments | ||
221 | 3) Setup header - containing codec setup information | ||
222 | 4) Many audio packets... | ||
223 | |||
224 | */ | ||
225 | |||
226 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
227 | buf=track->id3.path; | ||
228 | |||
229 | lseek(fd, 0, SEEK_SET); | ||
230 | |||
231 | rc = read(fd, buf, 58); | ||
232 | if (rc < 4) { | ||
233 | return false; | ||
234 | } | ||
235 | |||
236 | if ((memcmp(buf,"OggS",4)!=0) || (memcmp(&buf[29],"vorbis",6)!=0)) { | ||
237 | logf("%s is not an Ogg Vorbis file\n",trackname); | ||
238 | return(false); | ||
239 | } | ||
240 | |||
241 | /* Ogg stores integers in little-endian format. */ | ||
242 | track->id3.filesize=filesize(fd); | ||
243 | track->id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24); | ||
244 | channels=buf[39]; | ||
245 | |||
246 | /* We now need to search for the last page in the file - identified by | ||
247 | by ('O','g','g','S',0) and retrieve totalsamples */ | ||
248 | |||
249 | lseek(fd, -32*1024, SEEK_END); | ||
250 | eof=0; | ||
251 | j=0; /* The number of bytes currently in buffer */ | ||
252 | i=0; | ||
253 | totalsamples=0; | ||
254 | while (!eof) { | ||
255 | rc = read(fd, &buf[j], MAX_PATH-j); | ||
256 | if (rc <= 0) { | ||
257 | eof=1; | ||
258 | } else { | ||
259 | j+=rc; | ||
260 | } | ||
261 | /* Inefficient (but simple) search */ | ||
262 | i=0; | ||
263 | while (i < (j-5)) { | ||
264 | if (memcmp(&buf[i],"OggS",5)==0) { | ||
265 | if (i < (j-10)) { | ||
266 | totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24); | ||
267 | j=0; /* We can discard the rest of the buffer */ | ||
268 | } else { | ||
269 | break; | ||
270 | } | ||
271 | } else { | ||
272 | i++; | ||
273 | } | ||
274 | } | ||
275 | if (i < (j-5)) { | ||
276 | /* Move OggS to start of buffer */ | ||
277 | while(i>0) buf[i--]=buf[j--]; | ||
278 | } else { | ||
279 | j=0; | ||
280 | } | ||
281 | } | ||
282 | |||
283 | track->id3.length=(totalsamples/track->id3.frequency)*1000; | ||
284 | |||
285 | /* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */ | ||
286 | track->id3.bitrate=(filesize(fd)*8)/track->id3.length; | ||
287 | track->id3.vbr=true; | ||
288 | |||
289 | lseek(fd, 0, SEEK_SET); | ||
290 | strncpy(track->id3.path,trackname,sizeof(track->id3.path)); | ||
291 | track->taginfo_ready = true; | ||
292 | break; | ||
293 | |||
294 | case AFMT_WAVPACK: | ||
295 | /* A simple parser to read basic information from a WavPack file. | ||
296 | * This will fail on WavPack files that don't have the WavPack header | ||
297 | * as the first thing (i.e. self-extracting WavPack files) or WavPack | ||
298 | * files that have so much extra RIFF data stored in the first block | ||
299 | * that they don't have samples (very rare, I would think). | ||
300 | */ | ||
301 | |||
302 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
303 | buf=track->id3.path; | ||
304 | |||
305 | lseek(fd, 0, SEEK_SET); | ||
306 | |||
307 | rc = read(fd, buf, 32); | ||
308 | if (rc < 32) { | ||
309 | return false; | ||
310 | } | ||
311 | |||
312 | if (memcmp (buf, "wvpk", 4) != 0 || buf [9] != 4 || buf [8] < 2) { | ||
313 | logf ("%s is not a WavPack file\n", trackname); | ||
314 | return (false); | ||
315 | } | ||
316 | |||
317 | track->id3.vbr = true; /* All WavPack files are VBR */ | ||
318 | track->id3.filesize = filesize (fd); | ||
319 | track->id3.frequency = 44100; | ||
320 | |||
321 | if ((buf [20] | buf [21] | buf [22] | buf [23]) && | ||
322 | (buf [12] & buf [13] & buf [14] & buf [15]) != 0xff) { | ||
323 | totalsamples = (buf[15] << 24) | (buf[14] << 16) | (buf[13] << 8) | buf[12]; | ||
324 | track->id3.length = (totalsamples + 220) / 441 * 10; | ||
325 | track->id3.bitrate = filesize (fd) / | ||
326 | (track->id3.length / 8); | ||
327 | } | ||
328 | |||
329 | lseek (fd, 0, SEEK_SET); | ||
330 | strncpy (track->id3.path, trackname, sizeof (track->id3.path)); | ||
331 | track->taginfo_ready = true; | ||
332 | break; | ||
333 | |||
334 | /* If we don't know how to read the metadata, just store the filename */ | ||
335 | default: | ||
336 | strncpy(track->id3.path,trackname,sizeof(track->id3.path)); | ||
337 | track->taginfo_ready = true; | ||
338 | break; | ||
339 | } | ||
340 | |||
341 | return true; | ||
342 | } | ||