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