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 | |
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')
-rw-r--r-- | apps/SOURCES | 1 | ||||
-rw-r--r-- | apps/metadata.c | 342 | ||||
-rw-r--r-- | apps/metadata.h | 31 | ||||
-rw-r--r-- | apps/playback.c | 357 | ||||
-rw-r--r-- | apps/playback.h | 29 |
5 files changed, 419 insertions, 341 deletions
diff --git a/apps/SOURCES b/apps/SOURCES index 5e58816d4e..d6ea2c9491 100644 --- a/apps/SOURCES +++ b/apps/SOURCES | |||
@@ -50,4 +50,5 @@ recorder/recording.c | |||
50 | #endif | 50 | #endif |
51 | #ifdef IRIVER_H100 | 51 | #ifdef IRIVER_H100 |
52 | playback.c | 52 | playback.c |
53 | metadata.c | ||
53 | #endif | 54 | #endif |
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 | } | ||
diff --git a/apps/metadata.h b/apps/metadata.h new file mode 100644 index 0000000000..79ca2a21a9 --- /dev/null +++ b/apps/metadata.h | |||
@@ -0,0 +1,31 @@ | |||
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 | |||
20 | #ifndef _METADATA_H | ||
21 | #define _METADATA_H | ||
22 | |||
23 | #include "playback.h" | ||
24 | |||
25 | int probe_file_format(const char *filename); | ||
26 | bool get_metadata(struct track_info* track, int fd, const char* trackname, | ||
27 | bool v1first); | ||
28 | |||
29 | #endif | ||
30 | |||
31 | |||
diff --git a/apps/playback.c b/apps/playback.c index 0828d58ce0..d0b2882204 100644 --- a/apps/playback.c +++ b/apps/playback.c | |||
@@ -58,6 +58,7 @@ | |||
58 | #include "bookmark.h" | 58 | #include "bookmark.h" |
59 | #include "misc.h" | 59 | #include "misc.h" |
60 | #include "sound.h" | 60 | #include "sound.h" |
61 | #include "metadata.h" | ||
61 | 62 | ||
62 | static volatile bool playing; | 63 | static volatile bool playing; |
63 | static volatile bool paused; | 64 | static volatile bool paused; |
@@ -124,23 +125,6 @@ static volatile int codecbufused; | |||
124 | static volatile int buf_ridx; | 125 | static volatile int buf_ridx; |
125 | static volatile int buf_widx; | 126 | static volatile int buf_widx; |
126 | 127 | ||
127 | #define MAX_TRACK 10 | ||
128 | struct track_info { | ||
129 | struct mp3entry id3; /* TAG metadata */ | ||
130 | struct mp3info mp3data; /* MP3 metadata */ | ||
131 | char *codecbuf; /* Pointer to codec buffer */ | ||
132 | size_t codecsize; /* Codec length in bytes */ | ||
133 | int codectype; /* Codec type (example AFMT_MPA_L3) */ | ||
134 | |||
135 | off_t filerem; /* Remaining bytes of file NOT in buffer */ | ||
136 | off_t filesize; /* File total length */ | ||
137 | off_t filepos; /* Read position of file for next buffer fill */ | ||
138 | off_t start_pos; /* Position to first bytes of file in buffer */ | ||
139 | volatile int available; /* Available bytes to read from buffer */ | ||
140 | bool taginfo_ready; /* Is metadata read */ | ||
141 | int playlist_offset; /* File location in playlist */ | ||
142 | }; | ||
143 | |||
144 | /* Track information (count in file buffer, read/write indexes for | 128 | /* Track information (count in file buffer, read/write indexes for |
145 | track ring structure. */ | 129 | track ring structure. */ |
146 | static int track_count; | 130 | static int track_count; |
@@ -394,49 +378,6 @@ void codec_configure_callback(int setting, void *value) | |||
394 | } | 378 | } |
395 | } | 379 | } |
396 | 380 | ||
397 | /* Simple file type probing by looking filename extension. */ | ||
398 | int probe_file_format(const char *filename) | ||
399 | { | ||
400 | char *suffix; | ||
401 | |||
402 | suffix = strrchr(filename, '.'); | ||
403 | if (suffix == NULL) | ||
404 | return AFMT_UNKNOWN; | ||
405 | suffix += 1; | ||
406 | |||
407 | if (!strcasecmp("mp1", suffix)) | ||
408 | return AFMT_MPA_L1; | ||
409 | else if (!strcasecmp("mp2", suffix)) | ||
410 | return AFMT_MPA_L2; | ||
411 | else if (!strcasecmp("mpa", suffix)) | ||
412 | return AFMT_MPA_L2; | ||
413 | else if (!strcasecmp("mp3", suffix)) | ||
414 | return AFMT_MPA_L3; | ||
415 | else if (!strcasecmp("ogg", suffix)) | ||
416 | return AFMT_OGG_VORBIS; | ||
417 | else if (!strcasecmp("wav", suffix)) | ||
418 | return AFMT_PCM_WAV; | ||
419 | else if (!strcasecmp("flac", suffix)) | ||
420 | return AFMT_FLAC; | ||
421 | else if (!strcasecmp("mpc", suffix)) | ||
422 | return AFMT_MPC; | ||
423 | else if (!strcasecmp("aac", suffix)) | ||
424 | return AFMT_AAC; | ||
425 | else if (!strcasecmp("ape", suffix)) | ||
426 | return AFMT_APE; | ||
427 | else if (!strcasecmp("wma", suffix)) | ||
428 | return AFMT_WMA; | ||
429 | else if ((!strcasecmp("a52", suffix)) || (!strcasecmp("ac3", suffix))) | ||
430 | return AFMT_A52; | ||
431 | else if (!strcasecmp("rm", suffix)) | ||
432 | return AFMT_REAL; | ||
433 | else if (!strcasecmp("wv", suffix)) | ||
434 | return AFMT_WAVPACK; | ||
435 | |||
436 | return AFMT_UNKNOWN; | ||
437 | |||
438 | } | ||
439 | |||
440 | void yield_codecs(void) | 381 | void yield_codecs(void) |
441 | { | 382 | { |
442 | yield(); | 383 | yield(); |
@@ -620,10 +561,6 @@ bool audio_load_track(int offset, bool start_play, int peek_offset) | |||
620 | off_t size; | 561 | off_t size; |
621 | int rc, i; | 562 | int rc, i; |
622 | int copy_n; | 563 | int copy_n; |
623 | /* Used by the metadata parsers */ | ||
624 | unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; | ||
625 | unsigned char* buf; | ||
626 | int j,eof; | ||
627 | 564 | ||
628 | if (track_count >= MAX_TRACK) | 565 | if (track_count >= MAX_TRACK) |
629 | return false; | 566 | return false; |
@@ -673,283 +610,25 @@ bool audio_load_track(int offset, bool start_play, int peek_offset) | |||
673 | close(fd); | 610 | close(fd); |
674 | return false; | 611 | return false; |
675 | } | 612 | } |
676 | |||
677 | /* Load codec specific track tag information. */ | ||
678 | switch (tracks[track_widx].codectype) { | ||
679 | case AFMT_MPA_L2: | ||
680 | case AFMT_MPA_L3: | ||
681 | /* Should check the return value. */ | ||
682 | mp3info(&tracks[track_widx].id3, trackname, v1first); | ||
683 | lseek(fd, 0, SEEK_SET); | ||
684 | /* This is too slow to execute on some files. */ | ||
685 | get_mp3file_info(fd, &tracks[track_widx].mp3data); | ||
686 | if (offset) { | ||
687 | lseek(fd, offset, SEEK_SET); | ||
688 | tracks[track_widx].id3.offset = offset; | ||
689 | mp3_set_elapsed(&tracks[track_widx].id3); | ||
690 | tracks[track_widx].filepos = offset; | ||
691 | tracks[track_widx].filerem = tracks[track_widx].filesize - offset; | ||
692 | ci.curpos = offset; | ||
693 | tracks[track_widx].start_pos = offset; | ||
694 | } else { | ||
695 | lseek(fd, 0, SEEK_SET); | ||
696 | } | ||
697 | /* | ||
698 | logf("T:%s", tracks[track_widx].id3.title); | ||
699 | logf("L:%d", tracks[track_widx].id3.length); | ||
700 | logf("O:%d", tracks[track_widx].id3.first_frame_offset); | ||
701 | logf("F:%d", tracks[track_widx].id3.frequency); | ||
702 | */ | ||
703 | tracks[track_widx].taginfo_ready = true; | ||
704 | break ; | ||
705 | |||
706 | case AFMT_PCM_WAV: | ||
707 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
708 | buf=tracks[track_widx].id3.path; | ||
709 | |||
710 | lseek(fd, 0, SEEK_SET); | ||
711 | |||
712 | rc = read(fd, buf, 44); | ||
713 | if (rc < 44) { | ||
714 | close(fd); | ||
715 | return false; | ||
716 | } | ||
717 | |||
718 | if ((memcmp(buf,"RIFF",4)!=0) || | ||
719 | (memcmp(&buf[8],"WAVEfmt",7)!=0)) { | ||
720 | logf("%s is not a WAV file\n",trackname); | ||
721 | close(fd); | ||
722 | return(false); | ||
723 | } | ||
724 | |||
725 | /* FIX: Correctly parse WAV header - we assume canonical | ||
726 | 44-byte header */ | ||
727 | |||
728 | bitspersample=buf[34]; | ||
729 | channels=buf[22]; | ||
730 | |||
731 | if ((bitspersample!=16) || (channels != 2)) { | ||
732 | logf("Unsupported WAV file - %d bitspersample, %d channels\n", | ||
733 | bitspersample,channels); | ||
734 | close(fd); | ||
735 | return(false); | ||
736 | } | ||
737 | |||
738 | bytespersample=((bitspersample/8)*channels); | ||
739 | numbytes=(buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24)); | ||
740 | totalsamples=numbytes/bytespersample; | ||
741 | 613 | ||
742 | tracks[track_widx].id3.vbr=false; /* All WAV files are CBR */ | 614 | if (!get_metadata(&tracks[track_widx],fd,trackname,v1first)) { |
743 | tracks[track_widx].id3.filesize=filesize(fd); | 615 | close(fd); |
744 | tracks[track_widx].id3.frequency=buf[24]|(buf[25]<<8)|(buf[26]<<16)|(buf[27]<<24); | 616 | return false; |
745 | |||
746 | /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ | ||
747 | tracks[track_widx].id3.length=(totalsamples/tracks[track_widx].id3.frequency)*1000; | ||
748 | tracks[track_widx].id3.bitrate=(tracks[track_widx].id3.frequency*bytespersample)/(1000/8); | ||
749 | |||
750 | lseek(fd, 0, SEEK_SET); | ||
751 | strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); | ||
752 | tracks[track_widx].taginfo_ready = true; | ||
753 | |||
754 | break; | ||
755 | |||
756 | case AFMT_FLAC: | ||
757 | /* A simple parser to read vital metadata from a FLAC file - length, frequency, bitrate etc. */ | ||
758 | /* This code should either be moved to a seperate file, or discarded in favour of the libFLAC code */ | ||
759 | /* The FLAC stream specification can be found at http://flac.sourceforge.net/format.html#stream */ | ||
760 | |||
761 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
762 | buf=tracks[track_widx].id3.path; | ||
763 | |||
764 | lseek(fd, 0, SEEK_SET); | ||
765 | |||
766 | rc = read(fd, buf, 4); | ||
767 | if (rc < 4) { | ||
768 | close(fd); | ||
769 | return false; | ||
770 | } | ||
771 | |||
772 | if (memcmp(buf,"fLaC",4)!=0) { | ||
773 | logf("%s is not a FLAC file\n",trackname); | ||
774 | close(fd); | ||
775 | return(false); | ||
776 | } | ||
777 | |||
778 | while (1) { | ||
779 | rc = read(fd, buf, 4); | ||
780 | i = (buf[1]<<16)|(buf[2]<<8)|buf[3]; /* The length of the block */ | ||
781 | |||
782 | if ((buf[0]&0x7f)==0) { /* 0 is the STREAMINFO block */ | ||
783 | rc = read(fd, buf, i); /* FIXME: Don't trust the value of i */ | ||
784 | if (rc < 0) { | ||
785 | close(fd); | ||
786 | return false; | ||
787 | } | ||
788 | tracks[track_widx].id3.vbr=true; /* All FLAC files are VBR */ | ||
789 | tracks[track_widx].id3.filesize=filesize(fd); | ||
790 | |||
791 | tracks[track_widx].id3.frequency=(buf[10]<<12)|(buf[11]<<4)|((buf[12]&0xf0)>>4); | ||
792 | |||
793 | /* NOT NEEDED: bitspersample=(((buf[12]&0x01)<<4)|((buf[13]&0xf0)>>4))+1; */ | ||
794 | |||
795 | /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */ | ||
796 | totalsamples=(buf[14]<<24)|(buf[15]<<16)|(buf[16]<<8)|buf[17]; | ||
797 | |||
798 | /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ | ||
799 | tracks[track_widx].id3.length=(totalsamples/tracks[track_widx].id3.frequency)*1000; | ||
800 | tracks[track_widx].id3.bitrate=(filesize(fd)*8)/tracks[track_widx].id3.length; | ||
801 | } else if ((buf[0]&0x7f)==4) { /* 4 is the VORBIS_COMMENT block */ | ||
802 | |||
803 | /* The next i bytes of the file contain the VORBIS COMMENTS - just skip them for now. */ | ||
804 | lseek(fd, i, SEEK_CUR); | ||
805 | |||
806 | } else { | ||
807 | if (buf[0]&0x80) { /* If we have reached the last metadata block, abort. */ | ||
808 | break; | ||
809 | } else { | ||
810 | lseek(fd, i, SEEK_CUR); /* Skip to next metadata block */ | ||
811 | } | ||
812 | } | ||
813 | } | ||
814 | |||
815 | lseek(fd, 0, SEEK_SET); | ||
816 | strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); | ||
817 | tracks[track_widx].taginfo_ready = true; | ||
818 | break; | ||
819 | |||
820 | case AFMT_OGG_VORBIS: | ||
821 | /* A simple parser to read vital metadata from an Ogg Vorbis file */ | ||
822 | |||
823 | /* An Ogg File is split into pages, each starting with the string | ||
824 | "OggS". Each page has a timestamp (in PCM samples) referred to as | ||
825 | the "granule position". | ||
826 | |||
827 | An Ogg Vorbis has the following structure: | ||
828 | 1) Identification header (containing samplerate, numchannels, etc) | ||
829 | 2) Comment header - containing the Vorbis Comments | ||
830 | 3) Setup header - containing codec setup information | ||
831 | 4) Many audio packets... | ||
832 | |||
833 | */ | ||
834 | |||
835 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
836 | buf=tracks[track_widx].id3.path; | ||
837 | |||
838 | lseek(fd, 0, SEEK_SET); | ||
839 | |||
840 | rc = read(fd, buf, 58); | ||
841 | if (rc < 4) { | ||
842 | close(fd); | ||
843 | return false; | ||
844 | } | ||
845 | |||
846 | if ((memcmp(buf,"OggS",4)!=0) || (memcmp(&buf[29],"vorbis",6)!=0)) { | ||
847 | logf("%s is not an Ogg Vorbis file\n",trackname); | ||
848 | close(fd); | ||
849 | return(false); | ||
850 | } | ||
851 | |||
852 | /* Ogg stores integers in little-endian format. */ | ||
853 | tracks[track_widx].id3.filesize=filesize(fd); | ||
854 | tracks[track_widx].id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24); | ||
855 | channels=buf[39]; | ||
856 | |||
857 | /* We now need to search for the last page in the file - identified by | ||
858 | by ('O','g','g','S',0) and retrieve totalsamples */ | ||
859 | |||
860 | lseek(fd, -32*1024, SEEK_END); | ||
861 | eof=0; | ||
862 | j=0; /* The number of bytes currently in buffer */ | ||
863 | i=0; | ||
864 | totalsamples=0; | ||
865 | while (!eof) { | ||
866 | rc = read(fd, &buf[j], MAX_PATH-j); | ||
867 | if (rc <= 0) { | ||
868 | eof=1; | ||
869 | } else { | ||
870 | j+=rc; | ||
871 | } | ||
872 | /* Inefficient (but simple) search */ | ||
873 | i=0; | ||
874 | while (i < (j-5)) { | ||
875 | if (memcmp(&buf[i],"OggS",5)==0) { | ||
876 | if (i < (j-10)) { | ||
877 | totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24); | ||
878 | j=0; /* We can discard the rest of the buffer */ | ||
879 | } else { | ||
880 | break; | ||
881 | } | ||
882 | } else { | ||
883 | i++; | ||
884 | } | ||
885 | } | ||
886 | if (i < (j-5)) { | ||
887 | /* Move OggS to start of buffer */ | ||
888 | while(i>0) buf[i--]=buf[j--]; | ||
889 | } else { | ||
890 | j=0; | ||
891 | } | ||
892 | } | ||
893 | |||
894 | tracks[track_widx].id3.length=(totalsamples/tracks[track_widx].id3.frequency)*1000; | ||
895 | |||
896 | /* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */ | ||
897 | tracks[track_widx].id3.bitrate=(filesize(fd)*8)/tracks[track_widx].id3.length; | ||
898 | tracks[track_widx].id3.vbr=true; | ||
899 | |||
900 | lseek(fd, 0, SEEK_SET); | ||
901 | strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); | ||
902 | tracks[track_widx].taginfo_ready = true; | ||
903 | break; | ||
904 | |||
905 | case AFMT_WAVPACK: | ||
906 | /* A simple parser to read basic information from a WavPack file. | ||
907 | * This will fail on WavPack files that don't have the WavPack header | ||
908 | * as the first thing (i.e. self-extracting WavPack files) or WavPack | ||
909 | * files that have so much extra RIFF data stored in the first block | ||
910 | * that they don't have samples (very rare, I would think). | ||
911 | */ | ||
912 | |||
913 | /* Use the trackname part of the id3 structure as a temporary buffer */ | ||
914 | buf=tracks[track_widx].id3.path; | ||
915 | |||
916 | lseek(fd, 0, SEEK_SET); | ||
917 | |||
918 | rc = read(fd, buf, 32); | ||
919 | if (rc < 32) { | ||
920 | close(fd); | ||
921 | return false; | ||
922 | } | ||
923 | |||
924 | if (memcmp (buf, "wvpk", 4) != 0 || buf [9] != 4 || buf [8] < 2) { | ||
925 | logf ("%s is not a WavPack file\n", trackname); | ||
926 | close (fd); | ||
927 | return (false); | ||
928 | } | ||
929 | |||
930 | tracks[track_widx].id3.vbr = true; /* All WavPack files are VBR */ | ||
931 | tracks[track_widx].id3.filesize = filesize (fd); | ||
932 | tracks[track_widx].id3.frequency = 44100; | ||
933 | |||
934 | if ((buf [20] | buf [21] | buf [22] | buf [23]) && | ||
935 | (buf [12] & buf [13] & buf [14] & buf [15]) != 0xff) { | ||
936 | totalsamples = (buf[15] << 24) | (buf[14] << 16) | (buf[13] << 8) | buf[12]; | ||
937 | tracks[track_widx].id3.length = (totalsamples + 220) / 441 * 10; | ||
938 | tracks[track_widx].id3.bitrate = filesize (fd) / | ||
939 | (tracks[track_widx].id3.length / 8); | ||
940 | } | ||
941 | |||
942 | lseek (fd, 0, SEEK_SET); | ||
943 | strncpy (tracks[track_widx].id3.path, trackname, sizeof (tracks[track_widx].id3.path)); | ||
944 | tracks[track_widx].taginfo_ready = true; | ||
945 | break; | ||
946 | |||
947 | /* If we don't know how to read the metadata, just store the filename */ | ||
948 | default: | ||
949 | strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); | ||
950 | tracks[track_widx].taginfo_ready = true; | ||
951 | break; | ||
952 | } | 617 | } |
618 | |||
619 | /* Starting playback from an offset is only support in MPA at the moment */ | ||
620 | if (offset > 0) { | ||
621 | if ((tracks[track_widx].codectype==AFMT_MPA_L2) || | ||
622 | (tracks[track_widx].codectype==AFMT_MPA_L3)) { | ||
623 | lseek(fd, offset, SEEK_SET); | ||
624 | tracks[track_widx].id3.offset = offset; | ||
625 | mp3_set_elapsed(&tracks[track_widx].id3); | ||
626 | tracks[track_widx].filepos = offset; | ||
627 | tracks[track_widx].filerem = tracks[track_widx].filesize - offset; | ||
628 | ci.curpos = offset; | ||
629 | tracks[track_widx].start_pos = offset; | ||
630 | } | ||
631 | } | ||
953 | 632 | ||
954 | if (start_play) { | 633 | if (start_play) { |
955 | track_count++; | 634 | track_count++; |
diff --git a/apps/playback.h b/apps/playback.h index 4cdece75c9..1df283e6b8 100644 --- a/apps/playback.h +++ b/apps/playback.h | |||
@@ -17,8 +17,15 @@ | |||
17 | * | 17 | * |
18 | ****************************************************************************/ | 18 | ****************************************************************************/ |
19 | 19 | ||
20 | #ifndef _AUDIO_H | 20 | #ifndef _PLAYBACK_H |
21 | #define _AUDIO_H | 21 | #define _PLAYBACK_H |
22 | |||
23 | #include <stdlib.h> | ||
24 | #include <ctype.h> | ||
25 | #include <stdbool.h> | ||
26 | |||
27 | #include "id3.h" | ||
28 | #include "mp3data.h" | ||
22 | 29 | ||
23 | /* Supported file types. */ | 30 | /* Supported file types. */ |
24 | #define AFMT_MPA_L1 0x0001 // MPEG Audio layer 1 | 31 | #define AFMT_MPA_L1 0x0001 // MPEG Audio layer 1 |
@@ -45,6 +52,24 @@ | |||
45 | /* Not yet implemented. */ | 52 | /* Not yet implemented. */ |
46 | #define CODEC_SET_AUDIOBUF_WATERMARK 4 | 53 | #define CODEC_SET_AUDIOBUF_WATERMARK 4 |
47 | 54 | ||
55 | #define MAX_TRACK 10 | ||
56 | struct track_info { | ||
57 | struct mp3entry id3; /* TAG metadata */ | ||
58 | struct mp3info mp3data; /* MP3 metadata */ | ||
59 | char *codecbuf; /* Pointer to codec buffer */ | ||
60 | size_t codecsize; /* Codec length in bytes */ | ||
61 | int codectype; /* Codec type (example AFMT_MPA_L3) */ | ||
62 | |||
63 | off_t filerem; /* Remaining bytes of file NOT in buffer */ | ||
64 | off_t filesize; /* File total length */ | ||
65 | off_t filepos; /* Read position of file for next buffer fill */ | ||
66 | off_t start_pos; /* Position to first bytes of file in buffer */ | ||
67 | volatile int available; /* Available bytes to read from buffer */ | ||
68 | bool taginfo_ready; /* Is metadata read */ | ||
69 | int playlist_offset; /* File location in playlist */ | ||
70 | }; | ||
71 | |||
72 | |||
48 | /* Codec Interface */ | 73 | /* Codec Interface */ |
49 | struct codec_api { | 74 | struct codec_api { |
50 | off_t filesize; /* Total file length */ | 75 | off_t filesize; /* Total file length */ |