1 /*
2   SDL_mixer:  An audio mixer library based on the SDL library
3   Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 
22 #ifdef MUSIC_OGG
23 
24 /* This file supports Ogg Vorbis music streams */
25 
26 #include "SDL_loadso.h"
27 
28 #include "music_ogg.h"
29 
30 #define OV_EXCLUDE_STATIC_CALLBACKS
31 #if defined(OGG_HEADER)
32 #include OGG_HEADER
33 #elif defined(OGG_USE_TREMOR)
34 #include <tremor/ivorbisfile.h>
35 #else
36 #include <vorbis/vorbisfile.h>
37 #endif
38 
39 typedef struct {
40     int loaded;
41     void *handle;
42     int (*ov_clear)(OggVorbis_File *vf);
43     vorbis_info *(*ov_info)(OggVorbis_File *vf,int link);
44     vorbis_comment *(*ov_comment)(OggVorbis_File *vf,int link);
45     int (*ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks);
46     ogg_int64_t (*ov_pcm_total)(OggVorbis_File *vf,int i);
47 #ifdef OGG_USE_TREMOR
48     long (*ov_read)(OggVorbis_File *vf,char *buffer,int length, int *bitstream);
49 #else
50     long (*ov_read)(OggVorbis_File *vf,char *buffer,int length, int bigendianp,int word,int sgned,int *bitstream);
51 #endif
52 #ifdef OGG_USE_TREMOR
53     int (*ov_time_seek)(OggVorbis_File *vf,ogg_int64_t pos);
54 #else
55     int (*ov_time_seek)(OggVorbis_File *vf,double pos);
56 #endif
57     int (*ov_pcm_seek)(OggVorbis_File *vf, ogg_int64_t pos);
58     ogg_int64_t (*ov_pcm_tell)(OggVorbis_File *vf);
59 } vorbis_loader;
60 
61 static vorbis_loader vorbis = {
62     0, NULL
63 };
64 
65 #ifdef OGG_DYNAMIC
66 #define FUNCTION_LOADER(FUNC, SIG) \
67     vorbis.FUNC = (SIG) SDL_LoadFunction(vorbis.handle, #FUNC); \
68     if (vorbis.FUNC == NULL) { SDL_UnloadObject(vorbis.handle); return -1; }
69 #else
70 #define FUNCTION_LOADER(FUNC, SIG) \
71     vorbis.FUNC = FUNC;
72 #endif
73 
OGG_Load(void)74 static int OGG_Load(void)
75 {
76     if (vorbis.loaded == 0) {
77 #ifdef OGG_DYNAMIC
78         vorbis.handle = SDL_LoadObject(OGG_DYNAMIC);
79         if (vorbis.handle == NULL) {
80             return -1;
81         }
82 #elif defined(__MACOSX__)
83         extern int ov_open_callbacks(void*, OggVorbis_File*, const char*, long, ov_callbacks) __attribute__((weak_import));
84         if (ov_open_callbacks == NULL)
85         {
86             /* Missing weakly linked framework */
87             Mix_SetError("Missing Vorbis.framework");
88             return -1;
89         }
90 #endif
91         FUNCTION_LOADER(ov_clear, int (*)(OggVorbis_File *))
92         FUNCTION_LOADER(ov_info, vorbis_info *(*)(OggVorbis_File *,int))
93         FUNCTION_LOADER(ov_comment, vorbis_comment *(*)(OggVorbis_File *,int))
94         FUNCTION_LOADER(ov_open_callbacks, int (*)(void *, OggVorbis_File *, const char *, long, ov_callbacks))
95         FUNCTION_LOADER(ov_pcm_total, ogg_int64_t (*)(OggVorbis_File *,int))
96 #ifdef OGG_USE_TREMOR
97         FUNCTION_LOADER(ov_read, long (*)(OggVorbis_File *,char *,int,int *))
98         FUNCTION_LOADER(ov_time_seek, long (*)(OggVorbis_File *,ogg_int64_t))
99 #else
100         FUNCTION_LOADER(ov_read, long (*)(OggVorbis_File *,char *,int,int,int,int,int *))
101         FUNCTION_LOADER(ov_time_seek, int (*)(OggVorbis_File *,double))
102 #endif
103         FUNCTION_LOADER(ov_pcm_seek, int (*)(OggVorbis_File *,ogg_int64_t))
104         FUNCTION_LOADER(ov_pcm_tell, ogg_int64_t (*)(OggVorbis_File *))
105     }
106     ++vorbis.loaded;
107 
108     return 0;
109 }
110 
OGG_Unload(void)111 static void OGG_Unload(void)
112 {
113     if (vorbis.loaded == 0) {
114         return;
115     }
116     if (vorbis.loaded == 1) {
117 #ifdef OGG_DYNAMIC
118         SDL_UnloadObject(vorbis.handle);
119 #endif
120     }
121     --vorbis.loaded;
122 }
123 
124 
125 typedef struct {
126     SDL_RWops *src;
127     int freesrc;
128     int play_count;
129     int volume;
130     OggVorbis_File vf;
131     vorbis_info vi;
132     int section;
133     SDL_AudioStream *stream;
134     char *buffer;
135     int buffer_size;
136     int loop;
137     ogg_int64_t loop_start;
138     ogg_int64_t loop_end;
139     ogg_int64_t loop_len;
140     ogg_int64_t channels;
141 } OGG_music;
142 
143 
set_ov_error(const char * function,int error)144 static int set_ov_error(const char *function, int error)
145 {
146 #define HANDLE_ERROR_CASE(X)    case X: Mix_SetError("%s: %s", function, #X); break;
147     switch (error) {
148     HANDLE_ERROR_CASE(OV_FALSE);
149     HANDLE_ERROR_CASE(OV_EOF);
150     HANDLE_ERROR_CASE(OV_HOLE);
151     HANDLE_ERROR_CASE(OV_EREAD);
152     HANDLE_ERROR_CASE(OV_EFAULT);
153     HANDLE_ERROR_CASE(OV_EIMPL);
154     HANDLE_ERROR_CASE(OV_EINVAL);
155     HANDLE_ERROR_CASE(OV_ENOTVORBIS);
156     HANDLE_ERROR_CASE(OV_EBADHEADER);
157     HANDLE_ERROR_CASE(OV_EVERSION);
158     HANDLE_ERROR_CASE(OV_ENOTAUDIO);
159     HANDLE_ERROR_CASE(OV_EBADPACKET);
160     HANDLE_ERROR_CASE(OV_EBADLINK);
161     HANDLE_ERROR_CASE(OV_ENOSEEK);
162     default:
163         Mix_SetError("%s: unknown error %d\n", function, error);
164         break;
165     }
166     return -1;
167 }
168 
sdl_read_func(void * ptr,size_t size,size_t nmemb,void * datasource)169 static size_t sdl_read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
170 {
171     return SDL_RWread((SDL_RWops*)datasource, ptr, size, nmemb);
172 }
173 
sdl_seek_func(void * datasource,ogg_int64_t offset,int whence)174 static int sdl_seek_func(void *datasource, ogg_int64_t offset, int whence)
175 {
176     return (int)SDL_RWseek((SDL_RWops*)datasource, offset, whence);
177 }
178 
sdl_tell_func(void * datasource)179 static long sdl_tell_func(void *datasource)
180 {
181     return (long)SDL_RWtell((SDL_RWops*)datasource);
182 }
183 
184 static int OGG_Seek(void *context, double time);
185 static void OGG_Delete(void *context);
186 
OGG_UpdateSection(OGG_music * music)187 static int OGG_UpdateSection(OGG_music *music)
188 {
189     vorbis_info *vi;
190 
191     vi = vorbis.ov_info(&music->vf, -1);
192     if (!vi) {
193         Mix_SetError("ov_info returned NULL");
194         return -1;
195     }
196 
197     if (vi->channels == music->vi.channels && vi->rate == music->vi.rate) {
198         return 0;
199     }
200     SDL_memcpy(&music->vi, vi, sizeof(*vi));
201 
202     if (music->buffer) {
203         SDL_free(music->buffer);
204         music->buffer = NULL;
205     }
206 
207     if (music->stream) {
208         SDL_FreeAudioStream(music->stream);
209         music->stream = NULL;
210     }
211 
212     music->stream = SDL_NewAudioStream(AUDIO_S16, vi->channels, (int)vi->rate,
213                                        music_spec.format, music_spec.channels, music_spec.freq);
214     if (!music->stream) {
215         return -1;
216     }
217 
218     music->buffer_size = music_spec.samples * sizeof(Sint16) * vi->channels;
219     music->buffer = (char *)SDL_malloc(music->buffer_size);
220     if (!music->buffer) {
221         return -1;
222     }
223     return 0;
224 }
225 
226 /* Load an OGG stream from an SDL_RWops object */
OGG_CreateFromRW(SDL_RWops * src,int freesrc)227 static void *OGG_CreateFromRW(SDL_RWops *src, int freesrc)
228 {
229     OGG_music *music;
230     ov_callbacks callbacks;
231     vorbis_comment *vc;
232     int isLoopLength = 0, i;
233     ogg_int64_t fullLength;
234 
235     music = (OGG_music *)SDL_calloc(1, sizeof *music);
236     if (!music) {
237         SDL_OutOfMemory();
238         return NULL;
239     }
240     music->src = src;
241     music->volume = MIX_MAX_VOLUME;
242     music->section = -1;
243     music->loop = -1;
244     music->loop_start = -1;
245     music->loop_end = 0;
246     music->loop_len = 0;
247 
248     SDL_zero(callbacks);
249     callbacks.read_func = sdl_read_func;
250     callbacks.seek_func = sdl_seek_func;
251     callbacks.tell_func = sdl_tell_func;
252 
253     if (vorbis.ov_open_callbacks(src, &music->vf, NULL, 0, callbacks) < 0) {
254         SDL_SetError("Not an Ogg Vorbis audio stream");
255         SDL_free(music);
256         return NULL;
257     }
258 
259     if (OGG_UpdateSection(music) < 0) {
260         OGG_Delete(music);
261         return NULL;
262     }
263 
264     vc = vorbis.ov_comment(&music->vf, -1);
265     for (i = 0; i < vc->comments; i++) {
266         char *param = SDL_strdup(vc->user_comments[i]);
267         char *argument = param;
268         char *value = SDL_strchr(param, '=');
269         if (value == NULL) {
270             value = param + SDL_strlen(param);
271         } else {
272             *(value++) = '\0';
273         }
274 
275         if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
276             music->loop_start = SDL_strtoull(value, NULL, 0);
277         else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) {
278             music->loop_len = SDL_strtoull(value, NULL, 0);
279             isLoopLength = 1;
280         } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
281             isLoopLength = 0;
282             music->loop_end = SDL_strtoull(value, NULL, 0);
283         }
284         SDL_free(param);
285     }
286 
287     if (isLoopLength == 1) {
288         music->loop_end = music->loop_start + music->loop_len;
289     } else {
290         music->loop_len = music->loop_end - music->loop_start;
291     }
292 
293     fullLength = vorbis.ov_pcm_total(&music->vf, -1);
294     if (((music->loop_start >= 0) || (music->loop_end > 0)) &&
295         ((music->loop_start < music->loop_end) || (music->loop_end == 0)) &&
296          (music->loop_start < fullLength) &&
297          (music->loop_end <= fullLength)) {
298         if (music->loop_start < 0) music->loop_start = 0;
299         if (music->loop_end == 0)  music->loop_end = fullLength;
300         music->loop = 1;
301     }
302 
303     music->freesrc = freesrc;
304     return music;
305 }
306 
307 /* Set the volume for an OGG stream */
OGG_SetVolume(void * context,int volume)308 static void OGG_SetVolume(void *context, int volume)
309 {
310     OGG_music *music = (OGG_music *)context;
311     music->volume = volume;
312 }
313 
314 /* Start playback of a given OGG stream */
OGG_Play(void * context,int play_count)315 static int OGG_Play(void *context, int play_count)
316 {
317     OGG_music *music = (OGG_music *)context;
318     music->play_count = play_count;
319     return OGG_Seek(music, 0.0);
320 }
321 
322 /* Play some of a stream previously started with OGG_play() */
OGG_GetSome(void * context,void * data,int bytes,SDL_bool * done)323 static int OGG_GetSome(void *context, void *data, int bytes, SDL_bool *done)
324 {
325     OGG_music *music = (OGG_music *)context;
326     SDL_bool looped = SDL_FALSE;
327     int filled, amount, result;
328     int section;
329     ogg_int64_t pcmPos;
330 
331     filled = SDL_AudioStreamGet(music->stream, data, bytes);
332     if (filled != 0) {
333         return filled;
334     }
335 
336     if (!music->play_count) {
337         /* All done */
338         *done = SDL_TRUE;
339         return 0;
340     }
341 
342     section = music->section;
343 #ifdef OGG_USE_TREMOR
344     amount = vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, &section);
345 #else
346     amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, 0, 2, 1, &section);
347 #endif
348     if (amount < 0) {
349         set_ov_error("ov_read", amount);
350         return -1;
351     }
352 
353     if (section != music->section) {
354         music->section = section;
355         if (OGG_UpdateSection(music) < 0) {
356             return -1;
357         }
358     }
359 
360     pcmPos = vorbis.ov_pcm_tell(&music->vf);
361     if ((music->loop == 1) && (pcmPos >= music->loop_end)) {
362         amount -= (int)((pcmPos - music->loop_end) * music->channels) * sizeof(Sint16);
363         result = vorbis.ov_pcm_seek(&music->vf, music->loop_start);
364         if (result < 0) {
365             set_ov_error("ov_pcm_seek", result);
366             return -1;
367         }
368         looped = SDL_TRUE;
369     }
370 
371     if (amount > 0) {
372         if (SDL_AudioStreamPut(music->stream, music->buffer, amount) < 0) {
373             return -1;
374         }
375     } else if (!looped) {
376         if (music->play_count == 1) {
377             music->play_count = 0;
378             SDL_AudioStreamFlush(music->stream);
379         } else {
380             int play_count = -1;
381             if (music->play_count > 0) {
382                 play_count = (music->play_count - 1);
383             }
384             if (OGG_Play(music, play_count) < 0) {
385                 return -1;
386             }
387         }
388     }
389     return 0;
390 }
OGG_GetAudio(void * context,void * data,int bytes)391 static int OGG_GetAudio(void *context, void *data, int bytes)
392 {
393     OGG_music *music = (OGG_music *)context;
394     return music_pcm_getaudio(context, data, bytes, music->volume, OGG_GetSome);
395 }
396 
397 /* Jump (seek) to a given position (time is in seconds) */
OGG_Seek(void * context,double time)398 static int OGG_Seek(void *context, double time)
399 {
400     OGG_music *music = (OGG_music *)context;
401     int result;
402 #ifdef OGG_USE_TREMOR
403     result = vorbis.ov_time_seek(&music->vf, (ogg_int64_t)(time * 1000.0));
404 #else
405     result = vorbis.ov_time_seek(&music->vf, time);
406 #endif
407     if (result < 0) {
408         return set_ov_error("ov_time_seek", result);
409     }
410     return 0;
411 }
412 
413 /* Close the given OGG stream */
OGG_Delete(void * context)414 static void OGG_Delete(void *context)
415 {
416     OGG_music *music = (OGG_music *)context;
417     vorbis.ov_clear(&music->vf);
418     if (music->stream) {
419         SDL_FreeAudioStream(music->stream);
420     }
421     if (music->buffer) {
422         SDL_free(music->buffer);
423     }
424     if (music->freesrc) {
425         SDL_RWclose(music->src);
426     }
427     SDL_free(music);
428 }
429 
430 Mix_MusicInterface Mix_MusicInterface_OGG =
431 {
432     "OGG",
433     MIX_MUSIC_OGG,
434     MUS_OGG,
435     SDL_FALSE,
436     SDL_FALSE,
437 
438     OGG_Load,
439     NULL,   /* Open */
440     OGG_CreateFromRW,
441     NULL,   /* CreateFromFile */
442     OGG_SetVolume,
443     OGG_Play,
444     NULL,   /* IsPlaying */
445     OGG_GetAudio,
446     OGG_Seek,
447     NULL,   /* Pause */
448     NULL,   /* Resume */
449     NULL,   /* Stop */
450     OGG_Delete,
451     NULL,   /* Close */
452     OGG_Unload,
453 };
454 
455 #endif /* MUSIC_OGG */
456 
457 /* vi: set ts=4 sw=4 expandtab: */
458