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