music_ogg.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 04 Jan 2019 22:02:19 -0800
changeset 926 d6c9518fb5ee
parent 921 cab8eaca3eac
permissions -rw-r--r--
Updated copyright for 2019
     1 /*
     2   SDL_mixer:  An audio mixer library based on the SDL library
     3   Copyright (C) 1997-2019 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 
    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 
   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 
   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 
   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 
   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 
   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 
   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 */
   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 */
   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 */
   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() */
   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 }
   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) */
   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 */
   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: */