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