Initial revision 6759
[qball-mpd.git] / src / inputPlugins / .svn / text-base / oggvorbis_plugin.c.svn-base
blob6631aef686f7b077978a7b240737a151c9a11950
1 /* the Music Player Daemon (MPD)
2  * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3  * This project's homepage is: http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
19 /* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */
21 #include "../inputPlugin.h"
23 #ifdef HAVE_OGGVORBIS
25 #include "_ogg_common.h"
27 #include "../utils.h"
28 #include "../audio.h"
29 #include "../log.h"
30 #include "../pcm_utils.h"
31 #include "../inputStream.h"
32 #include "../outputBuffer.h"
33 #include "../replayGain.h"
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <string.h>
40 #ifndef HAVE_TREMOR
41 #include <vorbis/vorbisfile.h>
42 #else
43 #include <tremor/ivorbisfile.h>
44 /* Macros to make Tremor's API look like libogg. Tremor always
45    returns host-byte-order 16-bit signed data, and uses integer
46    milliseconds where libogg uses double seconds. 
48 #define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
49         ov_read(VF, BUFFER, LENGTH, BITSTREAM)
50 #define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000)
51 #define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000)
52 #define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
53 #endif /* HAVE_TREMOR */
55 #include <errno.h>
57 #ifdef WORDS_BIGENDIAN
58 #define OGG_DECODE_USE_BIGENDIAN        1
59 #else
60 #define OGG_DECODE_USE_BIGENDIAN        0
61 #endif
63 typedef struct _OggCallbackData {
64         InputStream *inStream;
65         DecoderControl *dc;
66 } OggCallbackData;
68 static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
70         size_t ret = 0;
71         OggCallbackData *data = (OggCallbackData *) vdata;
73         while (1) {
74                 ret = readFromInputStream(data->inStream, ptr, size, nmemb);
75                 if (ret == 0 && !inputStreamAtEOF(data->inStream) &&
76                     !data->dc->stop) {
77                         my_usleep(10000);
78                 } else
79                         break;
80         }
81         errno = 0;
82         /*if(ret<0) errno = ((InputStream *)inStream)->error; */
84         return ret;
87 static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence)
89         OggCallbackData *data = (OggCallbackData *) vdata;
91         return seekInputStream(data->inStream, offset, whence);
94 static int ogg_close_cb(void *vdata)
96         OggCallbackData *data = (OggCallbackData *) vdata;
98         return closeInputStream(data->inStream);
101 static long ogg_tell_cb(void *vdata)
103         OggCallbackData *data = (OggCallbackData *) vdata;
105         return (long)(data->inStream->offset);
108 static char *ogg_parseComment(char *comment, char *needle)
110         int len = strlen(needle);
112         if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') {
113                 return comment + len + 1;
114         }
116         return NULL;
119 static void ogg_getReplayGainInfo(char **comments, ReplayGainInfo ** infoPtr)
121         char *temp;
122         int found = 0;
124         if (*infoPtr)
125                 freeReplayGainInfo(*infoPtr);
126         *infoPtr = newReplayGainInfo();
128         while (*comments) {
129                 if ((temp =
130                      ogg_parseComment(*comments, "replaygain_track_gain"))) {
131                         (*infoPtr)->trackGain = atof(temp);
132                         found = 1;
133                 } else if ((temp = ogg_parseComment(*comments,
134                                                     "replaygain_album_gain"))) {
135                         (*infoPtr)->albumGain = atof(temp);
136                         found = 1;
137                 } else if ((temp = ogg_parseComment(*comments,
138                                                     "replaygain_track_peak"))) {
139                         (*infoPtr)->trackPeak = atof(temp);
140                         found = 1;
141                 } else if ((temp = ogg_parseComment(*comments,
142                                                     "replaygain_album_peak"))) {
143                         (*infoPtr)->albumPeak = atof(temp);
144                         found = 1;
145                 }
147                 comments++;
148         }
150         if (!found) {
151                 freeReplayGainInfo(*infoPtr);
152                 *infoPtr = NULL;
153         }
156 static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
157 static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
159 static unsigned int ogg_parseCommentAddToTag(char *comment,
160                                              unsigned int itemType,
161                                              MpdTag ** tag)
163         const char *needle;
164         unsigned int len;
165         switch (itemType) {
166         case TAG_ITEM_TRACK:
167                 needle = VORBIS_COMMENT_TRACK_KEY;
168                 break;
169         case TAG_ITEM_DISC:
170                 needle = VORBIS_COMMENT_DISC_KEY;
171                 break;
172         default:
173                 needle = mpdTagItemKeys[itemType];
174         }
175         len = strlen(needle);
177         if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') {
178                 if (!*tag)
179                         *tag = newMpdTag();
181                 addItemToMpdTag(*tag, itemType, comment + len + 1);
183                 return 1;
184         }
186         return 0;
189 static MpdTag *oggCommentsParse(char **comments)
191         MpdTag *tag = NULL;
193         while (*comments) {
194                 int j;
195                 for (j = TAG_NUM_OF_ITEM_TYPES; --j >= 0;) {
196                         if (ogg_parseCommentAddToTag(*comments, j, &tag))
197                                 break;
198                 }
199                 comments++;
200         }
202         return tag;
205 static void putOggCommentsIntoOutputBuffer(OutputBuffer * cb, char *streamName,
206                                            char **comments)
208         MpdTag *tag;
210         tag = oggCommentsParse(comments);
211         if (!tag && streamName) {
212                 tag = newMpdTag();
213         }
214         if (!tag)
215                 return;
217         if (streamName) {
218                 clearItemsFromMpdTag(tag, TAG_ITEM_NAME);
219                 addItemToMpdTag(tag, TAG_ITEM_NAME, streamName);
220         }
222         copyMpdTagToOutputBuffer(cb, tag);
224         freeMpdTag(tag);
227 /* public */
228 static int oggvorbis_decode(OutputBuffer * cb, DecoderControl * dc,
229                             InputStream * inStream)
231         OggVorbis_File vf;
232         ov_callbacks callbacks;
233         OggCallbackData data;
234         int current_section;
235         int prev_section = -1;
236         int eof = 0;
237         long ret;
238 #define OGG_CHUNK_SIZE 4096
239         char chunk[OGG_CHUNK_SIZE];
240         int chunkpos = 0;
241         long bitRate = 0;
242         long test;
243         ReplayGainInfo *replayGainInfo = NULL;
244         char **comments;
245         char *errorStr;
247         data.inStream = inStream;
248         data.dc = dc;
250         callbacks.read_func = ogg_read_cb;
251         callbacks.seek_func = ogg_seek_cb;
252         callbacks.close_func = ogg_close_cb;
253         callbacks.tell_func = ogg_tell_cb;
255         if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
256                 closeInputStream(inStream);
257                 if (!dc->stop) {
258                         switch (ret) {
259                         case OV_EREAD:
260                                 errorStr = "read error";
261                                 break;
262                         case OV_ENOTVORBIS:
263                                 errorStr = "not vorbis stream";
264                                 break;
265                         case OV_EVERSION:
266                                 errorStr = "vorbis version mismatch";
267                                 break;
268                         case OV_EBADHEADER:
269                                 errorStr = "invalid vorbis header";
270                                 break;
271                         case OV_EFAULT:
272                                 errorStr = "internal logic error";
273                                 break;
274                         default:
275                                 errorStr = "unknown error";
276                                 break;
277                         }
278                         ERROR("Error decoding Ogg Vorbis stream: %s\n",
279                               errorStr);
280                         return -1;
281                 } else {
282                         dc->state = DECODE_STATE_STOP;
283                         dc->stop = 0;
284                 }
285                 return 0;
286         }
288         dc->totalTime = ov_time_total(&vf, -1);
289         if (dc->totalTime < 0)
290                 dc->totalTime = 0;
292         dc->audioFormat.bits = 16;
294         while (!eof) {
295                 if (dc->seek) {
296                         if (0 == ov_time_seek_page(&vf, dc->seekWhere)) {
297                                 clearOutputBuffer(cb);
298                                 chunkpos = 0;
299                         } else
300                                 dc->seekError = 1;
301                         dc->seek = 0;
302                 }
303                 ret = ov_read(&vf, chunk + chunkpos,
304                               OGG_CHUNK_SIZE - chunkpos,
305                               OGG_DECODE_USE_BIGENDIAN, 2, 1, &current_section);
307                 if (current_section != prev_section) {
308                         /*printf("new song!\n"); */
309                         vorbis_info *vi = ov_info(&vf, -1);
310                         dc->audioFormat.channels = vi->channels;
311                         dc->audioFormat.sampleRate = vi->rate;
312                         if (dc->state == DECODE_STATE_START) {
313                                 getOutputAudioFormat(&(dc->audioFormat),
314                                                      &(cb->audioFormat));
315                                 dc->state = DECODE_STATE_DECODE;
316                         }
317                         comments = ov_comment(&vf, -1)->user_comments;
318                         putOggCommentsIntoOutputBuffer(cb, inStream->metaName,
319                                                        comments);
320                         ogg_getReplayGainInfo(comments, &replayGainInfo);
321                 }
323                 prev_section = current_section;
325                 if (ret <= 0 && ret != OV_HOLE) {
326                         eof = 1;
327                         break;
328                 }
329                 if (ret == OV_HOLE)
330                         ret = 0;
332                 chunkpos += ret;
334                 if (chunkpos >= OGG_CHUNK_SIZE) {
335                         if ((test = ov_bitrate_instant(&vf)) > 0) {
336                                 bitRate = test / 1000;
337                         }
338                         sendDataToOutputBuffer(cb, inStream, dc,
339                                                inStream->seekable,
340                                                chunk, chunkpos,
341                                                ov_pcm_tell(&vf) /
342                                                dc->audioFormat.sampleRate,
343                                                bitRate, replayGainInfo);
344                         chunkpos = 0;
345                         if (dc->stop)
346                                 break;
347                 }
348         }
350         if (!dc->stop && chunkpos > 0) {
351                 sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable,
352                                        chunk, chunkpos,
353                                        ov_time_tell(&vf), bitRate,
354                                        replayGainInfo);
355         }
357         if (replayGainInfo)
358                 freeReplayGainInfo(replayGainInfo);
360         ov_clear(&vf);
362         flushOutputBuffer(cb);
364         if (dc->stop) {
365                 dc->state = DECODE_STATE_STOP;
366                 dc->stop = 0;
367         } else
368                 dc->state = DECODE_STATE_STOP;
370         return 0;
373 static MpdTag *oggvorbis_TagDup(char *file)
375         MpdTag *ret = NULL;
376         FILE *fp;
377         OggVorbis_File vf;
379         fp = fopen(file, "r");
380         if (!fp) {
381                 DEBUG("oggvorbis_TagDup: Failed to open file: '%s', %s\n",
382                       file, strerror(errno));
383                 return NULL;
384         }
385         if (ov_open(fp, &vf, NULL, 0) < 0) {
386                 fclose(fp);
387                 return NULL;
388         }
390         ret = oggCommentsParse(ov_comment(&vf, -1)->user_comments);
392         if (!ret)
393                 ret = newMpdTag();
394         ret->time = (int)(ov_time_total(&vf, -1) + 0.5);
396         ov_clear(&vf);
398         return ret;
401 static unsigned int oggvorbis_try_decode(InputStream * inStream)
403         return (ogg_stream_type_detect(inStream) == VORBIS) ? 1 : 0;
406 static char *oggvorbis_Suffixes[] = { "ogg", NULL };
407 static char *oggvorbis_MimeTypes[] = { "application/ogg",
408                                        "audio/x-vorbis+ogg",
409                                        "application/x-ogg",
410                                        NULL };
412 InputPlugin oggvorbisPlugin = {
413         "oggvorbis",
414         NULL,
415         NULL,
416         oggvorbis_try_decode,
417         oggvorbis_decode,
418         NULL,
419         oggvorbis_TagDup,
420         INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
421         oggvorbis_Suffixes,
422         oggvorbis_MimeTypes
425 #else /* !HAVE_OGGVORBIS */
427 InputPlugin oggvorbisPlugin;
429 #endif /* HAVE_OGGVORBIS */