From 69cd7f4ff536cf50c1248d660740caf9fadbeb0f Mon Sep 17 00:00:00 2001 From: Ozkan Sezer Date: Fri, 15 Jun 2018 08:32:56 +0300 Subject: [PATCH] opus support using opusfile library (bug #4200) --- SDL_mixer.h | 6 +- configure | 254 ++++++++++++++++++++++++++++++++--- configure.in | 44 ++++++ mixer.c | 8 ++ music.c | 24 +++- music.h | 1 + music_opus.c | 369 +++++++++++++++++++++++++++++++++++++++++++++++++++ music_opus.h | 28 ++++ 8 files changed, 715 insertions(+), 19 deletions(-) create mode 100644 music_opus.c create mode 100644 music_opus.h diff --git a/SDL_mixer.h b/SDL_mixer.h index 8c80995b..918cece6 100644 --- a/SDL_mixer.h +++ b/SDL_mixer.h @@ -80,7 +80,8 @@ typedef enum MIX_INIT_MOD = 0x00000002, MIX_INIT_MP3 = 0x00000008, MIX_INIT_OGG = 0x00000010, - MIX_INIT_MID = 0x00000020 + MIX_INIT_MID = 0x00000020, + MIX_INIT_OPUS = 0x00000040 } MIX_InitFlags; /* Loads dynamic libraries and prepares them for use. Flags should be @@ -134,7 +135,8 @@ typedef enum { MUS_MP3, MUS_MP3_MAD_UNUSED, MUS_FLAC, - MUS_MODPLUG_UNUSED + MUS_MODPLUG_UNUSED, + MUS_OPUS } Mix_MusicType; /* The internal format for a music chunk interpreted via mikmod */ diff --git a/configure b/configure index 1ff6d061..a1ec89b0 100755 --- a/configure +++ b/configure @@ -781,6 +781,8 @@ PLAYWAVE_OBJECTS VERSION_OBJECTS OBJECTS ac_aux_dir +OPUSFILE_LIBS +OPUSFILE_CFLAGS SMPEG_LIBS SMPEG_CFLAGS SMPEG_CONFIG @@ -922,6 +924,8 @@ enable_music_mp3_mad_gpl enable_music_mp3_mad_gpl_dithering enable_music_mp3_mpg123 enable_music_mp3_mpg123_shared +enable_music_opus +enable_music_opus_shared ' ac_precious_vars='build_alias host_alias @@ -936,7 +940,9 @@ PKG_CONFIG SDL_CFLAGS SDL_LIBS MODPLUG_CFLAGS -MODPLUG_LIBS' +MODPLUG_LIBS +OPUSFILE_CFLAGS +OPUSFILE_LIBS' # Initialize some variables set by options. @@ -1598,6 +1604,9 @@ Optional Features: enable MP3 music via libmpg123 [[default=yes]] --enable-music-mp3-mpg123-shared dynamically load libmpg123 library [[default=yes]] + --enable-music-opus enable Opus music [[default=yes]] + --enable-music-opus-shared + dynamically load opusfile library [[default=yes]] Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1627,6 +1636,10 @@ Some influential environment variables: C compiler flags for MODPLUG, overriding pkg-config MODPLUG_LIBS linker flags for MODPLUG, overriding pkg-config + OPUSFILE_CFLAGS + C compiler flags for OPUSFILE, overriding pkg-config + OPUSFILE_LIBS + linker flags for OPUSFILE, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -3930,13 +3943,13 @@ if ${lt_cv_nm_interface+:} false; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3933: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3946: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3936: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3949: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3939: output\"" >&5) + (eval echo "\"\$as_me:3952: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -5147,7 +5160,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5150 "configure"' > conftest.$ac_ext + echo '#line 5163 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6977,11 +6990,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:6980: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6993: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6984: \$? = $ac_status" >&5 + echo "$as_me:6997: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7316,11 +7329,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7319: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7332: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7323: \$? = $ac_status" >&5 + echo "$as_me:7336: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7421,11 +7434,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7424: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7437: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7428: \$? = $ac_status" >&5 + echo "$as_me:7441: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -7476,11 +7489,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7479: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7492: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7483: \$? = $ac_status" >&5 + echo "$as_me:7496: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -9845,7 +9858,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9848 "configure" +#line 9861 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -9941,7 +9954,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 9944 "configure" +#line 9957 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -12997,6 +13010,217 @@ else $as_echo "$as_me: WARNING: MP3 support disabled" >&2;} fi +# Check whether --enable-music-opus was given. +if test "${enable_music_opus+set}" = set; then : + enableval=$enable_music_opus; +else + enable_music_opus=yes +fi + + +# Check whether --enable-music-opus-shared was given. +if test "${enable_music_opus_shared+set}" = set; then : + enableval=$enable_music_opus_shared; +else + enable_music_opus_shared=yes +fi + +if test x$enable_music_opus = xyes; then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for OPUSFILE" >&5 +$as_echo_n "checking for OPUSFILE... " >&6; } + +if test -n "$PKG_CONFIG"; then + if test -n "$OPUSFILE_CFLAGS"; then + pkg_cv_OPUSFILE_CFLAGS="$OPUSFILE_CFLAGS" + else + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"opusfile >= 0.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "opusfile >= 0.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_OPUSFILE_CFLAGS=`$PKG_CONFIG --cflags "opusfile >= 0.2" 2>/dev/null` +else + pkg_failed=yes +fi + fi +else + pkg_failed=untried +fi +if test -n "$PKG_CONFIG"; then + if test -n "$OPUSFILE_LIBS"; then + pkg_cv_OPUSFILE_LIBS="$OPUSFILE_LIBS" + else + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"opusfile >= 0.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "opusfile >= 0.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_OPUSFILE_LIBS=`$PKG_CONFIG --libs "opusfile >= 0.2" 2>/dev/null` +else + pkg_failed=yes +fi + fi +else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + OPUSFILE_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "opusfile >= 0.2"` + else + OPUSFILE_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "opusfile >= 0.2"` + fi + # Put the nasty error message in config.log where it belongs + echo "$OPUSFILE_PKG_ERRORS" >&5 + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ac_fn_c_check_header_mongrel "$LINENO" "opus/opusfile.h" "ac_cv_header_opus_opusfile_h" "$ac_includes_default" +if test "x$ac_cv_header_opus_opusfile_h" = xyes; then : + have_opusfile_hdr=yes +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for op_open_callbacks in -lopusfile" >&5 +$as_echo_n "checking for op_open_callbacks in -lopusfile... " >&6; } +if ${ac_cv_lib_opusfile_op_open_callbacks+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lopusfile $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char op_open_callbacks (); +int +main () +{ +return op_open_callbacks (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_opusfile_op_open_callbacks=yes +else + ac_cv_lib_opusfile_op_open_callbacks=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_opusfile_op_open_callbacks" >&5 +$as_echo "$ac_cv_lib_opusfile_op_open_callbacks" >&6; } +if test "x$ac_cv_lib_opusfile_op_open_callbacks" = xyes; then : + have_opusfile_lib=yes +fi + + +elif test $pkg_failed = untried; then + ac_fn_c_check_header_mongrel "$LINENO" "opus/opusfile.h" "ac_cv_header_opus_opusfile_h" "$ac_includes_default" +if test "x$ac_cv_header_opus_opusfile_h" = xyes; then : + have_opusfile_hdr=yes +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for op_open_callbacks in -lopusfile" >&5 +$as_echo_n "checking for op_open_callbacks in -lopusfile... " >&6; } +if ${ac_cv_lib_opusfile_op_open_callbacks+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lopusfile $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char op_open_callbacks (); +int +main () +{ +return op_open_callbacks (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_opusfile_op_open_callbacks=yes +else + ac_cv_lib_opusfile_op_open_callbacks=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_opusfile_op_open_callbacks" >&5 +$as_echo "$ac_cv_lib_opusfile_op_open_callbacks" >&6; } +if test "x$ac_cv_lib_opusfile_op_open_callbacks" = xyes; then : + have_opusfile_lib=yes +fi + + +else + OPUSFILE_CFLAGS=$pkg_cv_OPUSFILE_CFLAGS + OPUSFILE_LIBS=$pkg_cv_OPUSFILE_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + have_opusfile_hdr=yes + have_opusfile_lib=yes + +fi + + if test x$have_opusfile_hdr = xyes -a x$have_opusfile_lib = xyes; then + have_opusfile=yes + case "$host" in + *-*-darwin*) + opusfile_lib=`find_lib libopusfile.dylib` + ;; + *-*-cygwin* | *-*-mingw32*) + opusfile_lib=`find_lib "libopusfile*.dll"` + ;; + *) + opusfile_lib=`find_lib "libopusfile[0-9]*.so.*"` + if test x$opusfile_lib = x; then + opusfile_lib=`find_lib "libopusfile.so.*"` + fi + ;; + esac + EXTRA_CFLAGS="$EXTRA_CFLAGS -DMUSIC_OPUS $OPUSFILE_CFLAGS" + if test x$enable_music_opus_shared = xyes && test x$opusfile_lib != x; then + echo "-- dynamic opusfile -> $opusfile_lib" + EXTRA_CFLAGS="$EXTRA_CFLAGS -DOPUS_DYNAMIC=\\\"$opusfile_lib\\\"" + else + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $OPUSFILE_LIBS" + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** Unable to find opusfile library (http://opus-codec.org/)" >&5 +$as_echo "$as_me: WARNING: *** Unable to find opusfile library (http://opus-codec.org/)" >&2;} + fi +fi + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBM" OBJECTS=`echo $SOURCES` diff --git a/configure.in b/configure.in index 68c4e308..5d7c1f29 100644 --- a/configure.in +++ b/configure.in @@ -674,6 +674,50 @@ else AC_MSG_WARN([MP3 support disabled]) fi +AC_ARG_ENABLE([music-opus], +AC_HELP_STRING([--enable-music-opus], [enable Opus music [[default=yes]]]), + [], [enable_music_opus=yes]) + +AC_ARG_ENABLE([music-opus-shared], +AC_HELP_STRING([--enable-music-opus-shared], [dynamically load opusfile library [[default=yes]]]), + [], [enable_music_opus_shared=yes]) +if test x$enable_music_opus = xyes; then + PKG_CHECK_MODULES([OPUSFILE], [opusfile >= 0.2], [dnl + have_opusfile_hdr=yes + have_opusfile_lib=yes + ], [dnl + AC_CHECK_HEADER([opus/opusfile.h], [have_opusfile_hdr=yes]) + AC_CHECK_LIB([opusfile], [op_open_callbacks], [have_opusfile_lib=yes]) + ]) + + if test x$have_opusfile_hdr = xyes -a x$have_opusfile_lib = xyes; then + have_opusfile=yes + case "$host" in + *-*-darwin*) + opusfile_lib=[`find_lib libopusfile.dylib`] + ;; + *-*-cygwin* | *-*-mingw32*) + opusfile_lib=[`find_lib "libopusfile*.dll"`] + ;; + *) + opusfile_lib=[`find_lib "libopusfile[0-9]*.so.*"`] + if test x$opusfile_lib = x; then + opusfile_lib=[`find_lib "libopusfile.so.*"`] + fi + ;; + esac + EXTRA_CFLAGS="$EXTRA_CFLAGS -DMUSIC_OPUS $OPUSFILE_CFLAGS" + if test x$enable_music_opus_shared = xyes && test x$opusfile_lib != x; then + echo "-- dynamic opusfile -> $opusfile_lib" + EXTRA_CFLAGS="$EXTRA_CFLAGS -DOPUS_DYNAMIC=\\\"$opusfile_lib\\\"" + else + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $OPUSFILE_LIBS" + fi + else + AC_MSG_WARN([*** Unable to find opusfile library (http://opus-codec.org/)]) + fi +fi + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBM" OBJECTS=`echo $SOURCES` diff --git a/mixer.c b/mixer.c index 19392b4a..ba2b407b 100644 --- a/mixer.c +++ b/mixer.c @@ -182,6 +182,14 @@ int Mix_Init(int flags) Mix_SetError("OGG support not available"); } } + if (flags & MIX_INIT_OPUS) { + if (load_music_type(MUS_OPUS)) { + open_music_type(MUS_OPUS); + result |= MIX_INIT_OPUS; + } else { + Mix_SetError("OPUS support not available"); + } + } if (flags & MIX_INIT_MID) { if (load_music_type(MUS_MID)) { open_music_type(MUS_MID); diff --git a/music.c b/music.c index 067f4dbf..83d938f1 100644 --- a/music.c +++ b/music.c @@ -36,6 +36,7 @@ #include "music_fluidsynth.h" #include "music_timidity.h" #include "music_ogg.h" +#include "music_opus.h" #include "music_mpg123.h" #include "music_mad.h" #include "music_smpeg.h" @@ -92,6 +93,9 @@ static Mix_MusicInterface *s_music_interfaces[] = #ifdef MUSIC_OGG &Mix_MusicInterface_OGG, #endif +#ifdef MUSIC_OPUS + &Mix_MusicInterface_Opus, +#endif #ifdef MUSIC_MP3_MPG123 &Mix_MusicInterface_MPG123, #endif @@ -354,6 +358,10 @@ SDL_bool open_music_type(Mix_MusicType type) add_music_decoder("OGG"); add_chunk_decoder("OGG"); } + if (has_music(MUS_OPUS)) { + add_music_decoder("OPUS"); + add_chunk_decoder("OPUS"); + } if (has_music(MUS_MP3)) { add_music_decoder("MP3"); add_chunk_decoder("MP3"); @@ -438,6 +446,7 @@ Mix_MusicType detect_music_type_from_magic(const Uint8 *magic) static Mix_MusicType detect_music_type(SDL_RWops *src) { Uint8 magic[12]; + Mix_MusicType t; if (SDL_RWread(src, magic, 1, 12) != 12) { Mix_SetError("Couldn't read first 12 bytes of audio data"); @@ -451,8 +460,17 @@ static Mix_MusicType detect_music_type(SDL_RWops *src) (SDL_memcmp(magic, "FORM", 4) == 0)) { return MUS_WAV; } - - return detect_music_type_from_magic(magic); + t = detect_music_type_from_magic(magic); + if (t == MUS_OGG) { + Sint64 pos = SDL_RWtell(src); + SDL_RWseek(src, 28, RW_SEEK_CUR); + SDL_RWread(src, magic, 1, 8); + SDL_RWseek(src, pos, RW_SEEK_SET); + if (SDL_memcmp(magic, "OpusHead", 8) == 0) { + return MUS_OPUS; + } + } + return t; } /* Load a music file */ @@ -503,6 +521,8 @@ Mix_Music *Mix_LoadMUS(const char *file) type = MUS_MID; } else if (SDL_strcasecmp(ext, "OGG") == 0) { type = MUS_OGG; + } else if (SDL_strcasecmp(ext, "OPUS") == 0) { + type = MUS_OPUS; } else if (SDL_strcasecmp(ext, "FLAC") == 0) { type = MUS_FLAC; } else if (SDL_strcasecmp(ext, "MPG") == 0 || diff --git a/music.h b/music.h index cbebfe94..415a9581 100644 --- a/music.h +++ b/music.h @@ -39,6 +39,7 @@ typedef enum MIX_MUSIC_MAD, MIX_MUSIC_SMPEG, MIX_MUSIC_FLAC, + MIX_MUSIC_OPUS, MIX_MUSIC_LAST } Mix_MusicAPI; diff --git a/music_opus.c b/music_opus.c new file mode 100644 index 00000000..d6c1cfdc --- /dev/null +++ b/music_opus.c @@ -0,0 +1,369 @@ +/* + SDL_mixer: An audio mixer library based on the SDL library + Copyright (C) 1997-2018 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifdef MUSIC_OPUS + +/* This file supports Ogg Opus music streams */ + +#include "SDL_loadso.h" + +#include "music_opus.h" + +#include + +typedef struct { + int loaded; + void *handle; + OggOpusFile *(*op_open_callbacks)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *); + void (*op_free)(OggOpusFile *); + const OpusHead *(*op_head)(const OggOpusFile *,int); + int (*op_seekable)(const OggOpusFile *); + int (*op_read)(OggOpusFile *, opus_int16 *,int,int *); + int (*op_pcm_seek)(OggOpusFile *,ogg_int64_t); +} opus_loader; + +static opus_loader opus = { + 0, NULL +}; + +#ifdef OPUS_DYNAMIC +#define FUNCTION_LOADER(FUNC, SIG) \ + opus.FUNC = (SIG) SDL_LoadFunction(opus.handle, #FUNC); \ + if (opus.FUNC == NULL) { SDL_UnloadObject(opus.handle); return -1; } +#else +#define FUNCTION_LOADER(FUNC, SIG) \ + opus.FUNC = FUNC; +#endif + +static int OPUS_Load(void) +{ + if (opus.loaded == 0) { +#ifdef OPUS_DYNAMIC + opus.handle = SDL_LoadObject(OPUS_DYNAMIC); + if (opus.handle == NULL) { + return -1; + } +#elif defined(__MACOSX__) + extern OggOpusFile *op_open_callbacks(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *) __attribute__((weak_import)); + if (op_open_callbacks == NULL) { + /* Missing weakly linked framework */ + Mix_SetError("Missing Opus.framework"); + return -1; + } +#endif + FUNCTION_LOADER(op_open_callbacks, OggOpusFile *(*)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *)) + FUNCTION_LOADER(op_free, void (*)(OggOpusFile *)) + FUNCTION_LOADER(op_head, const OpusHead *(*)(const OggOpusFile *,int)) + FUNCTION_LOADER(op_seekable, int (*)(const OggOpusFile *)) + FUNCTION_LOADER(op_read, int (*)(OggOpusFile *, opus_int16 *,int,int *)) + FUNCTION_LOADER(op_pcm_seek, int (*)(OggOpusFile *,ogg_int64_t)) + } + ++opus.loaded; + + return 0; +} + +static void OPUS_Unload(void) +{ + if (opus.loaded == 0) { + return; + } + if (opus.loaded == 1) { +#ifdef OPUS_DYNAMIC + SDL_UnloadObject(opus.handle); +#endif + } + --opus.loaded; +} + + +typedef struct { + SDL_RWops *src; + int freesrc; + int play_count; + int volume; + OggOpusFile *of; + const OpusHead *op_info; + int section; + SDL_AudioStream *stream; + char *buffer; + int buffer_size; +} OPUS_music; + + +static int set_op_error(const char *function, int error) +{ +#define HANDLE_ERROR_CASE(X) case X: Mix_SetError("%s: %s", function, #X); break; + switch (error) { + HANDLE_ERROR_CASE(OP_FALSE); + HANDLE_ERROR_CASE(OP_EOF); + HANDLE_ERROR_CASE(OP_HOLE); + HANDLE_ERROR_CASE(OP_EREAD); + HANDLE_ERROR_CASE(OP_EFAULT); + HANDLE_ERROR_CASE(OP_EIMPL); + HANDLE_ERROR_CASE(OP_EINVAL); + HANDLE_ERROR_CASE(OP_ENOTFORMAT); + HANDLE_ERROR_CASE(OP_EBADHEADER); + HANDLE_ERROR_CASE(OP_EVERSION); + HANDLE_ERROR_CASE(OP_ENOTAUDIO); + HANDLE_ERROR_CASE(OP_EBADPACKET); + HANDLE_ERROR_CASE(OP_EBADLINK); + HANDLE_ERROR_CASE(OP_ENOSEEK); + HANDLE_ERROR_CASE(OP_EBADTIMESTAMP); + default: + Mix_SetError("%s: unknown error %d\n", function, error); + break; + } + return -1; +} + +static int sdl_read_func(void *datasource, unsigned char *ptr, int size) +{ + return (int)SDL_RWread((SDL_RWops*)datasource, ptr, 1, size); +} + +static int sdl_seek_func(void *datasource, ogg_int64_t offset, int whence) +{ + return (SDL_RWseek((SDL_RWops*)datasource, offset, whence) < 0)? -1 : 0; +} + +static opus_int64 sdl_tell_func(void *datasource) +{ + return SDL_RWtell((SDL_RWops*)datasource); +} + +static int OPUS_Seek(void*, double); +static void OPUS_Delete(void*); + +static int OPUS_UpdateSection(OPUS_music *music) +{ + const OpusHead *op_info; + + op_info = opus.op_head(music->of, -1); + if (!op_info) { + Mix_SetError("op_head returned NULL"); + return -1; + } + + if (music->op_info && op_info->channel_count == music->op_info->channel_count) { + return 0; + } + music->op_info = op_info; + + if (music->buffer) { + SDL_free(music->buffer); + music->buffer = NULL; + } + + if (music->stream) { + SDL_FreeAudioStream(music->stream); + music->stream = NULL; + } + + music->stream = SDL_NewAudioStream(AUDIO_S16, op_info->channel_count, 48000, + music_spec.format, music_spec.channels, music_spec.freq); + if (!music->stream) { + return -1; + } + + music->buffer_size = music_spec.samples * sizeof(opus_int16) * op_info->channel_count; + music->buffer = (char *)SDL_malloc(music->buffer_size); + if (!music->buffer) { + return -1; + } + return 0; +} + +/* Load an Opus stream from an SDL_RWops object */ +static void *OPUS_CreateFromRW(SDL_RWops *src, int freesrc) +{ + OPUS_music *music; + OpusFileCallbacks callbacks; + int err = 0; + + music = (OPUS_music *)SDL_calloc(1, sizeof *music); + if (!music) { + SDL_OutOfMemory(); + return NULL; + } + music->src = src; + music->volume = MIX_MAX_VOLUME; + music->section = -1; + + SDL_zero(callbacks); + callbacks.read = sdl_read_func; + callbacks.seek = sdl_seek_func; + callbacks.tell = sdl_tell_func; + + music->of = opus.op_open_callbacks(src, &callbacks, NULL, 0, &err); + if (music->of == NULL) { + /* set_op_error("op_open_callbacks", err);*/ + SDL_SetError("Not an Opus audio stream"); + SDL_free(music); + return NULL; + } + + if (!opus.op_seekable(music->of)) { + OPUS_Delete(music); + Mix_SetError("Opus stream not seekable"); + return NULL; + } + + if (OPUS_UpdateSection(music) < 0) { + OPUS_Delete(music); + return NULL; + } + + music->freesrc = freesrc; + return music; +} + +/* Set the volume for an Opus stream */ +static void OPUS_SetVolume(void *context, int volume) +{ + OPUS_music *music = (OPUS_music *)context; + music->volume = volume; +} + +/* Start playback of a given Opus stream */ +static int OPUS_Play(void *context, int play_count) +{ + OPUS_music *music = (OPUS_music *)context; + music->play_count = play_count; + return OPUS_Seek(music, 0.0); +} + +/* Play some of a stream previously started with OPUS_Play() */ +static int OPUS_GetSome(void *context, void *data, int bytes, SDL_bool *done) +{ + OPUS_music *music = (OPUS_music *)context; + int filled, samples, section; + + filled = SDL_AudioStreamGet(music->stream, data, bytes); + if (filled != 0) { + return filled; + } + + if (!music->play_count) { + /* All done */ + *done = SDL_TRUE; + return 0; + } + + section = music->section; + samples = opus.op_read(music->of, (opus_int16 *)music->buffer, music->buffer_size / sizeof(opus_int16), §ion); + if (samples < 0) { + set_op_error("op_read", samples); + return -1; + } + + if (section != music->section) { + music->section = section; + if (OPUS_UpdateSection(music) < 0) { + return -1; + } + } + + if (samples > 0) { + filled = samples * music->op_info->channel_count * 2; + if (SDL_AudioStreamPut(music->stream, music->buffer, filled) < 0) { + return -1; + } + } else { + if (music->play_count == 1) { + music->play_count = 0; + SDL_AudioStreamFlush(music->stream); + } else { + int play_count = -1; + if (music->play_count > 0) { + play_count = (music->play_count - 1); + } + if (OPUS_Play(music, play_count) < 0) { + return -1; + } + } + } + return 0; +} + +static int OPUS_GetAudio(void *context, void *data, int bytes) +{ + OPUS_music *music = (OPUS_music *)context; + return music_pcm_getaudio(context, data, bytes, music->volume, OPUS_GetSome); +} + +/* Jump (seek) to a given position (time is in seconds) */ +static int OPUS_Seek(void *context, double time) +{ + OPUS_music *music = (OPUS_music *)context; + int result; + result = opus.op_pcm_seek(music->of, (ogg_int64_t)(time * 48000)); + if (result < 0) { + return set_op_error("op_pcm_seek", result); + } + return 0; +} + +/* Close the given Opus stream */ +static void OPUS_Delete(void *context) +{ + OPUS_music *music = (OPUS_music *)context; + opus.op_free(music->of); + if (music->stream) { + SDL_FreeAudioStream(music->stream); + } + if (music->buffer) { + SDL_free(music->buffer); + } + if (music->freesrc) { + SDL_RWclose(music->src); + } + SDL_free(music); +} + +Mix_MusicInterface Mix_MusicInterface_Opus = +{ + "OPUS", + MIX_MUSIC_OPUS, + MUS_OPUS, + SDL_FALSE, + SDL_FALSE, + + OPUS_Load, + NULL, /* Open */ + OPUS_CreateFromRW, + NULL, /* CreateFromFile */ + OPUS_SetVolume, + OPUS_Play, + NULL, /* IsPlaying */ + OPUS_GetAudio, + OPUS_Seek, + NULL, /* Pause */ + NULL, /* Resume */ + NULL, /* Stop */ + OPUS_Delete, + NULL, /* Close */ + OPUS_Unload, +}; + +#endif /* MUSIC_OPUS */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/music_opus.h b/music_opus.h new file mode 100644 index 00000000..a207bc51 --- /dev/null +++ b/music_opus.h @@ -0,0 +1,28 @@ +/* + SDL_mixer: An audio mixer library based on the SDL library + Copyright (C) 1997-2018 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* This file supports Ogg Opus music streams */ + +#include "music.h" + +extern Mix_MusicInterface Mix_MusicInterface_Opus; + +/* vi: set ts=4 sw=4 expandtab: */