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
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.
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
19 /* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */
21 #include "../inputPlugin.h"
25 #include "_ogg_common.h"
30 #include "../pcm_utils.h"
31 #include "../inputStream.h"
32 #include "../outputBuffer.h"
33 #include "../replayGain.h"
41 #include <vorbis/vorbisfile.h>
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 */
57 #ifdef WORDS_BIGENDIAN
58 #define OGG_DECODE_USE_BIGENDIAN 1
60 #define OGG_DECODE_USE_BIGENDIAN 0
63 typedef struct _OggCallbackData {
64 InputStream *inStream;
68 static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
71 OggCallbackData *data = (OggCallbackData *) vdata;
74 ret = readFromInputStream(data->inStream, ptr, size, nmemb);
75 if (ret == 0 && !inputStreamAtEOF(data->inStream) &&
82 /*if(ret<0) errno = ((InputStream *)inStream)->error; */
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;
119 static void ogg_getReplayGainInfo(char **comments, ReplayGainInfo ** infoPtr)
125 freeReplayGainInfo(*infoPtr);
126 *infoPtr = newReplayGainInfo();
130 ogg_parseComment(*comments, "replaygain_track_gain"))) {
131 (*infoPtr)->trackGain = atof(temp);
133 } else if ((temp = ogg_parseComment(*comments,
134 "replaygain_album_gain"))) {
135 (*infoPtr)->albumGain = atof(temp);
137 } else if ((temp = ogg_parseComment(*comments,
138 "replaygain_track_peak"))) {
139 (*infoPtr)->trackPeak = atof(temp);
141 } else if ((temp = ogg_parseComment(*comments,
142 "replaygain_album_peak"))) {
143 (*infoPtr)->albumPeak = atof(temp);
151 freeReplayGainInfo(*infoPtr);
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,
167 needle = VORBIS_COMMENT_TRACK_KEY;
170 needle = VORBIS_COMMENT_DISC_KEY;
173 needle = mpdTagItemKeys[itemType];
175 len = strlen(needle);
177 if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') {
181 addItemToMpdTag(*tag, itemType, comment + len + 1);
189 static MpdTag *oggCommentsParse(char **comments)
195 for (j = TAG_NUM_OF_ITEM_TYPES; --j >= 0;) {
196 if (ogg_parseCommentAddToTag(*comments, j, &tag))
205 static void putOggCommentsIntoOutputBuffer(OutputBuffer * cb, char *streamName,
210 tag = oggCommentsParse(comments);
211 if (!tag && streamName) {
218 clearItemsFromMpdTag(tag, TAG_ITEM_NAME);
219 addItemToMpdTag(tag, TAG_ITEM_NAME, streamName);
222 copyMpdTagToOutputBuffer(cb, tag);
228 static int oggvorbis_decode(OutputBuffer * cb, DecoderControl * dc,
229 InputStream * inStream)
232 ov_callbacks callbacks;
233 OggCallbackData data;
235 int prev_section = -1;
238 #define OGG_CHUNK_SIZE 4096
239 char chunk[OGG_CHUNK_SIZE];
243 ReplayGainInfo *replayGainInfo = NULL;
247 data.inStream = inStream;
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);
260 errorStr = "read error";
263 errorStr = "not vorbis stream";
266 errorStr = "vorbis version mismatch";
269 errorStr = "invalid vorbis header";
272 errorStr = "internal logic error";
275 errorStr = "unknown error";
278 ERROR("Error decoding Ogg Vorbis stream: %s\n",
282 dc->state = DECODE_STATE_STOP;
288 dc->totalTime = ov_time_total(&vf, -1);
289 if (dc->totalTime < 0)
292 dc->audioFormat.bits = 16;
296 if (0 == ov_time_seek_page(&vf, dc->seekWhere)) {
297 clearOutputBuffer(cb);
303 ret = ov_read(&vf, chunk + chunkpos,
304 OGG_CHUNK_SIZE - chunkpos,
305 OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_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),
315 dc->state = DECODE_STATE_DECODE;
317 comments = ov_comment(&vf, -1)->user_comments;
318 putOggCommentsIntoOutputBuffer(cb, inStream->metaName,
320 ogg_getReplayGainInfo(comments, &replayGainInfo);
323 prev_section = current_section;
325 if (ret <= 0 && ret != OV_HOLE) {
334 if (chunkpos >= OGG_CHUNK_SIZE) {
335 if ((test = ov_bitrate_instant(&vf)) > 0) {
336 bitRate = test / 1000;
338 sendDataToOutputBuffer(cb, inStream, dc,
342 dc->audioFormat.sampleRate,
343 bitRate, replayGainInfo);
350 if (!dc->stop && chunkpos > 0) {
351 sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable,
353 ov_time_tell(&vf), bitRate,
358 freeReplayGainInfo(replayGainInfo);
362 flushOutputBuffer(cb);
365 dc->state = DECODE_STATE_STOP;
368 dc->state = DECODE_STATE_STOP;
373 static MpdTag *oggvorbis_TagDup(char *file)
379 fp = fopen(file, "r");
381 DEBUG("oggvorbis_TagDup: Failed to open file: '%s', %s\n",
382 file, strerror(errno));
385 if (ov_open(fp, &vf, NULL, 0) < 0) {
390 ret = oggCommentsParse(ov_comment(&vf, -1)->user_comments);
394 ret->time = (int)(ov_time_total(&vf, -1) + 0.5);
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",
412 InputPlugin oggvorbisPlugin = {
416 oggvorbis_try_decode,
420 INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
425 #else /* !HAVE_OGGVORBIS */
427 InputPlugin oggvorbisPlugin;
429 #endif /* HAVE_OGGVORBIS */