music_opus.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 07 Nov 2018 07:47:50 -0800
changeset 924 9be60a9582a6
parent 918 05c9e6169a89
child 926 d6c9518fb5ee
permissions -rw-r--r--
The Debian maintainers aren't using these rules, so enable dynamic loading of shared libraries by default for the Steam Linux Runtime
     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_OPUS
    23 
    24 /* This file supports Ogg Opus music streams */
    25 
    26 #include "SDL_loadso.h"
    27 
    28 #include "music_opus.h"
    29 
    30 #if defined(OPUS_HEADER)
    31 #include OPUS_HEADER
    32 #else
    33 #include <opus/opusfile.h>
    34 #endif
    35 
    36 typedef struct {
    37     int loaded;
    38     void *handle;
    39     OggOpusFile *(*op_open_callbacks)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *);
    40     void (*op_free)(OggOpusFile *);
    41     const OpusHead *(*op_head)(const OggOpusFile *,int);
    42     int (*op_seekable)(const OggOpusFile *);
    43     int (*op_read)(OggOpusFile *, opus_int16 *,int,int *);
    44     int (*op_pcm_seek)(OggOpusFile *,ogg_int64_t);
    45 } opus_loader;
    46 
    47 static opus_loader opus = {
    48     0, NULL
    49 };
    50 
    51 #ifdef OPUS_DYNAMIC
    52 #define FUNCTION_LOADER(FUNC, SIG) \
    53     opus.FUNC = (SIG) SDL_LoadFunction(opus.handle, #FUNC); \
    54     if (opus.FUNC == NULL) { SDL_UnloadObject(opus.handle); return -1; }
    55 #else
    56 #define FUNCTION_LOADER(FUNC, SIG) \
    57     opus.FUNC = FUNC;
    58 #endif
    59 
    60 static int OPUS_Load(void)
    61 {
    62     if (opus.loaded == 0) {
    63 #ifdef OPUS_DYNAMIC
    64         opus.handle = SDL_LoadObject(OPUS_DYNAMIC);
    65         if (opus.handle == NULL) {
    66             return -1;
    67         }
    68 #elif defined(__MACOSX__)
    69         extern OggOpusFile *op_open_callbacks(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *) __attribute__((weak_import));
    70         if (op_open_callbacks == NULL) {
    71             /* Missing weakly linked framework */
    72             Mix_SetError("Missing OpusFile.framework");
    73             return -1;
    74         }
    75 #endif
    76         FUNCTION_LOADER(op_open_callbacks, OggOpusFile *(*)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *))
    77         FUNCTION_LOADER(op_free, void (*)(OggOpusFile *))
    78         FUNCTION_LOADER(op_head, const OpusHead *(*)(const OggOpusFile *,int))
    79         FUNCTION_LOADER(op_seekable, int (*)(const OggOpusFile *))
    80         FUNCTION_LOADER(op_read, int (*)(OggOpusFile *, opus_int16 *,int,int *))
    81         FUNCTION_LOADER(op_pcm_seek, int (*)(OggOpusFile *,ogg_int64_t))
    82     }
    83     ++opus.loaded;
    84 
    85     return 0;
    86 }
    87 
    88 static void OPUS_Unload(void)
    89 {
    90     if (opus.loaded == 0) {
    91         return;
    92     }
    93     if (opus.loaded == 1) {
    94 #ifdef OPUS_DYNAMIC
    95         SDL_UnloadObject(opus.handle);
    96 #endif
    97     }
    98     --opus.loaded;
    99 }
   100 
   101 
   102 typedef struct {
   103     SDL_RWops *src;
   104     int freesrc;
   105     int play_count;
   106     int volume;
   107     OggOpusFile *of;
   108     const OpusHead *op_info;
   109     int section;
   110     SDL_AudioStream *stream;
   111     char *buffer;
   112     int buffer_size;
   113 } OPUS_music;
   114 
   115 
   116 static int set_op_error(const char *function, int error)
   117 {
   118 #define HANDLE_ERROR_CASE(X)    case X: Mix_SetError("%s: %s", function, #X); break;
   119     switch (error) {
   120     HANDLE_ERROR_CASE(OP_FALSE);
   121     HANDLE_ERROR_CASE(OP_EOF);
   122     HANDLE_ERROR_CASE(OP_HOLE);
   123     HANDLE_ERROR_CASE(OP_EREAD);
   124     HANDLE_ERROR_CASE(OP_EFAULT);
   125     HANDLE_ERROR_CASE(OP_EIMPL);
   126     HANDLE_ERROR_CASE(OP_EINVAL);
   127     HANDLE_ERROR_CASE(OP_ENOTFORMAT);
   128     HANDLE_ERROR_CASE(OP_EBADHEADER);
   129     HANDLE_ERROR_CASE(OP_EVERSION);
   130     HANDLE_ERROR_CASE(OP_ENOTAUDIO);
   131     HANDLE_ERROR_CASE(OP_EBADPACKET);
   132     HANDLE_ERROR_CASE(OP_EBADLINK);
   133     HANDLE_ERROR_CASE(OP_ENOSEEK);
   134     HANDLE_ERROR_CASE(OP_EBADTIMESTAMP);
   135     default:
   136         Mix_SetError("%s: unknown error %d\n", function, error);
   137         break;
   138     }
   139     return -1;
   140 }
   141 
   142 static int sdl_read_func(void *datasource, unsigned char *ptr, int size)
   143 {
   144     return (int)SDL_RWread((SDL_RWops*)datasource, ptr, 1, size);
   145 }
   146 
   147 static int sdl_seek_func(void *datasource, opus_int64  offset, int whence)
   148 {
   149     return (SDL_RWseek((SDL_RWops*)datasource, offset, whence) < 0)? -1 : 0;
   150 }
   151 
   152 static opus_int64 sdl_tell_func(void *datasource)
   153 {
   154     return SDL_RWtell((SDL_RWops*)datasource);
   155 }
   156 
   157 static int OPUS_Seek(void*, double);
   158 static void OPUS_Delete(void*);
   159 
   160 static int OPUS_UpdateSection(OPUS_music *music)
   161 {
   162     const OpusHead *op_info;
   163 
   164     op_info = opus.op_head(music->of, -1);
   165     if (!op_info) {
   166         Mix_SetError("op_head returned NULL");
   167         return -1;
   168     }
   169 
   170     if (music->op_info && op_info->channel_count == music->op_info->channel_count) {
   171         return 0;
   172     }
   173     music->op_info = op_info;
   174 
   175     if (music->buffer) {
   176         SDL_free(music->buffer);
   177         music->buffer = NULL;
   178     }
   179 
   180     if (music->stream) {
   181         SDL_FreeAudioStream(music->stream);
   182         music->stream = NULL;
   183     }
   184 
   185     music->stream = SDL_NewAudioStream(AUDIO_S16, op_info->channel_count, 48000,
   186                                        music_spec.format, music_spec.channels, music_spec.freq);
   187     if (!music->stream) {
   188         return -1;
   189     }
   190 
   191     music->buffer_size = music_spec.samples * sizeof(opus_int16) * op_info->channel_count;
   192     music->buffer = (char *)SDL_malloc(music->buffer_size);
   193     if (!music->buffer) {
   194         return -1;
   195     }
   196     return 0;
   197 }
   198 
   199 /* Load an Opus stream from an SDL_RWops object */
   200 static void *OPUS_CreateFromRW(SDL_RWops *src, int freesrc)
   201 {
   202     OPUS_music *music;
   203     OpusFileCallbacks callbacks;
   204     int err = 0;
   205 
   206     music = (OPUS_music *)SDL_calloc(1, sizeof *music);
   207     if (!music) {
   208         SDL_OutOfMemory();
   209         return NULL;
   210     }
   211     music->src = src;
   212     music->volume = MIX_MAX_VOLUME;
   213     music->section = -1;
   214 
   215     SDL_zero(callbacks);
   216     callbacks.read = sdl_read_func;
   217     callbacks.seek = sdl_seek_func;
   218     callbacks.tell = sdl_tell_func;
   219 
   220     music->of = opus.op_open_callbacks(src, &callbacks, NULL, 0, &err);
   221     if (music->of == NULL) {
   222     /*  set_op_error("op_open_callbacks", err);*/
   223         SDL_SetError("Not an Opus audio stream");
   224         SDL_free(music);
   225         return NULL;
   226     }
   227 
   228     if (!opus.op_seekable(music->of)) {
   229         OPUS_Delete(music);
   230         Mix_SetError("Opus stream not seekable");
   231         return NULL;
   232     }
   233 
   234     if (OPUS_UpdateSection(music) < 0) {
   235         OPUS_Delete(music);
   236         return NULL;
   237     }
   238 
   239     music->freesrc = freesrc;
   240     return music;
   241 }
   242 
   243 /* Set the volume for an Opus stream */
   244 static void OPUS_SetVolume(void *context, int volume)
   245 {
   246     OPUS_music *music = (OPUS_music *)context;
   247     music->volume = volume;
   248 }
   249 
   250 /* Start playback of a given Opus stream */
   251 static int OPUS_Play(void *context, int play_count)
   252 {
   253     OPUS_music *music = (OPUS_music *)context;
   254     music->play_count = play_count;
   255     return OPUS_Seek(music, 0.0);
   256 }
   257 
   258 /* Play some of a stream previously started with OPUS_Play() */
   259 static int OPUS_GetSome(void *context, void *data, int bytes, SDL_bool *done)
   260 {
   261     OPUS_music *music = (OPUS_music *)context;
   262     int filled, samples, section;
   263 
   264     filled = SDL_AudioStreamGet(music->stream, data, bytes);
   265     if (filled != 0) {
   266         return filled;
   267     }
   268 
   269     if (!music->play_count) {
   270         /* All done */
   271         *done = SDL_TRUE;
   272         return 0;
   273     }
   274 
   275     section = music->section;
   276     samples = opus.op_read(music->of, (opus_int16 *)music->buffer, music->buffer_size / sizeof(opus_int16), &section);
   277     if (samples < 0) {
   278         set_op_error("op_read", samples);
   279         return -1;
   280     }
   281 
   282     if (section != music->section) {
   283         music->section = section;
   284         if (OPUS_UpdateSection(music) < 0) {
   285             return -1;
   286         }
   287     }
   288 
   289     if (samples > 0) {
   290         filled = samples * music->op_info->channel_count * 2;
   291         if (SDL_AudioStreamPut(music->stream, music->buffer, filled) < 0) {
   292             return -1;
   293         }
   294     } else {
   295         if (music->play_count == 1) {
   296             music->play_count = 0;
   297             SDL_AudioStreamFlush(music->stream);
   298         } else {
   299             int play_count = -1;
   300             if (music->play_count > 0) {
   301                 play_count = (music->play_count - 1);
   302             }
   303             if (OPUS_Play(music, play_count) < 0) {
   304                 return -1;
   305             }
   306         }
   307     }
   308     return 0;
   309 }
   310 
   311 static int OPUS_GetAudio(void *context, void *data, int bytes)
   312 {
   313     OPUS_music *music = (OPUS_music *)context;
   314     return music_pcm_getaudio(context, data, bytes, music->volume, OPUS_GetSome);
   315 }
   316 
   317 /* Jump (seek) to a given position (time is in seconds) */
   318 static int OPUS_Seek(void *context, double time)
   319 {
   320     OPUS_music *music = (OPUS_music *)context;
   321     int result;
   322     result = opus.op_pcm_seek(music->of, (ogg_int64_t)(time * 48000));
   323     if (result < 0) {
   324         return set_op_error("op_pcm_seek", result);
   325     }
   326     return 0;
   327 }
   328 
   329 /* Close the given Opus stream */
   330 static void OPUS_Delete(void *context)
   331 {
   332     OPUS_music *music = (OPUS_music *)context;
   333     opus.op_free(music->of);
   334     if (music->stream) {
   335         SDL_FreeAudioStream(music->stream);
   336     }
   337     if (music->buffer) {
   338         SDL_free(music->buffer);
   339     }
   340     if (music->freesrc) {
   341         SDL_RWclose(music->src);
   342     }
   343     SDL_free(music);
   344 }
   345 
   346 Mix_MusicInterface Mix_MusicInterface_Opus =
   347 {
   348     "OPUS",
   349     MIX_MUSIC_OPUS,
   350     MUS_OPUS,
   351     SDL_FALSE,
   352     SDL_FALSE,
   353 
   354     OPUS_Load,
   355     NULL,   /* Open */
   356     OPUS_CreateFromRW,
   357     NULL,   /* CreateFromFile */
   358     OPUS_SetVolume,
   359     OPUS_Play,
   360     NULL,   /* IsPlaying */
   361     OPUS_GetAudio,
   362     OPUS_Seek,
   363     NULL,   /* Pause */
   364     NULL,   /* Resume */
   365     NULL,   /* Stop */
   366     OPUS_Delete,
   367     NULL,   /* Close */
   368     OPUS_Unload,
   369 };
   370 
   371 #endif /* MUSIC_OPUS */
   372 
   373 /* vi: set ts=4 sw=4 expandtab: */