diff --git a/.github/actions/setup-ninja/action.yml b/.github/actions/setup-ninja/action.yml index b9283598..690c248c 100644 --- a/.github/actions/setup-ninja/action.yml +++ b/.github/actions/setup-ninja/action.yml @@ -3,7 +3,7 @@ description: 'Download ninja and add it to the PATH environment variable' inputs: version: description: 'Ninja version' - default: '1.12.1' + default: '1.13.1' runs: using: 'composite' steps: @@ -41,17 +41,17 @@ runs: path: '${{ runner.temp }}/${{ steps.calc.outputs.archive }}' key: ${{ steps.calc.outputs.cache-key }} - name: 'Download ninja ${{ inputs.version }} for ${{ runner.os }} (${{ runner.arch }})' - if: ${{ !steps.cache-restore.outputs.cache-hit }} + if: ${{ (!steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false') }} shell: pwsh run: | Invoke-WebRequest "https://github.com/ninja-build/ninja/releases/download/v${{ inputs.version }}/${{ steps.calc.outputs.archive }}" -OutFile "${{ runner.temp }}/${{ steps.calc.outputs.archive }}" - name: 'Cache ${{ steps.calc.outputs.archive }}' - if: ${{ !steps.cache-restore.outputs.cache-hit }} + if: ${{ (!steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false') }} uses: actions/cache/save@v4 with: path: '${{ runner.temp }}/${{ steps.calc.outputs.archive }}' key: ${{ steps.calc.outputs.cache-key }} - - name: 'Extract libusb' + - name: 'Extract ninja' shell: pwsh run: | 7z "-o${{ runner.temp }}/ninja-${{ inputs.version }}-${{ runner.arch }}" x "${{ runner.temp }}/${{ steps.calc.outputs.archive }}" diff --git a/.github/actions/setup-yasm/action.yml b/.github/actions/setup-yasm/action.yml new file mode 100644 index 00000000..a962d3c0 --- /dev/null +++ b/.github/actions/setup-yasm/action.yml @@ -0,0 +1,60 @@ +# The pre-built yasm binaries are built with an older MSVCRT, +# which is no longer installed on current Windows images. + +name: 'Setup yasm' +description: 'Build yasm and add it to the PATH environment variable' +runs: + using: 'composite' + steps: + - name: 'Calculate variables' + id: calc + shell: sh + run: | + echo "cache-key=yasm-${{ runner.os }}-${{ runner.arch }}" >> ${GITHUB_OUTPUT} + - name: 'Restore yasm' + id: restore-yasm + uses: actions/cache/restore@v4 + with: + path: | + ${{ github.workspace }}/yasm + key: ${{ steps.calc.outputs.cache-key }} + - name: 'Verify cached yasm' + if: ${{ steps.restore-yasm.outputs.cache-hit }} + shell: pwsh + run: | + ${{ github.workspace }}\yasm\bin\yasm --help + - name: 'Download yasm sources' + if: ${{ !steps.restore-yasm.outputs.cache-hit }} + uses: actions/checkout@v4 + with: + repository: yasm/yasm + path: yasm-src + - name: 'Configure, build and install yasm' + if: ${{ !steps.restore-yasm.outputs.cache-hit }} + shell: pwsh + run: | + cmake -S yasm-src -B yasm-build -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/yasm -DCMAKE_INSTALL_LIBDIR=bin -DCMAKE_INSTALL_BINDIR=bin -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded + cmake --build yasm-build --parallel + cmake --install yasm-build + - name: 'Verify built yasm' + if: ${{ !steps.restore-yasm.outputs.cache-hit }} + shell: sh + run: | + set -e + if [ ! -f "yasm/bin/yasm.exe" -a -f "yasm/bin/vsyasm.exe" ]; then + cp "yasm/bin/vsyasm.exe" "yasm/bin/yasm.exe" + fi + yasm/bin/yasm --help + - name: 'Cache yasm' + if: ${{ !steps.restore-yasm.outputs.cache-hit }} + uses: actions/cache/save@v4 + with: + path: | + ${{ github.workspace }}/yasm + key: ${{ steps.calc.outputs.cache-key }} + enableCrossOsArchive: true + - name: 'Set output variables' + id: final + shell: pwsh + run: | + echo "${{ github.workspace }}/yasm/bin" >> $env:GITHUB_PATH diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7465a37b..79765dd1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,16 +15,15 @@ jobs: fail-fast: false matrix: platform: - - { name: Windows (MSVC+CMake), os: windows-2019, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -GNinja', msvc: 1, shared: 1, static: 0 } + - { name: Windows (MSVC+CMake), os: windows-latest, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -GNinja', msvc: 1, shared: 1, static: 0 } - { name: Windows (mingw32+autotools), os: windows-latest, shell: 'msys2 {0}', msystem: mingw32, msys-env: mingw-w64-i686, shared: 1, static: 1 } - { name: Windows (mingw64+CMake), os: windows-latest, shell: 'msys2 {0}', msystem: mingw64, msys-env: mingw-w64-x86_64, shared: 1, static: 0, cmake: '-DSDL2MIXER_VENDORED=OFF -G "Ninja Multi-Config"' } - - { name: Linux (autotools), os: ubuntu-20.04, shell: sh, shared: 1, static: 1} - - { name: Linux (CMake), os: ubuntu-20.04, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -GNinja', shared: 1, static: 0 } - - { name: 'Linux (CMake, static)', os: ubuntu-20.04, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -DBUILD_SHARED_LIBS=OFF -GNinja', shared: 0, static: 1 } - - { name: Macos (autotools), os: macos-latest, shell: sh, shared: 1, static: 1 } - - { name: Macos (CMake), os: macos-latest, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -GNinja', shared: 1, static: 0 } - + - { name: Linux (autotools), os: ubuntu-latest, shell: sh, shared: 1, static: 1} + - { name: Linux (CMake), os: ubuntu-latest, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -GNinja', shared: 1, static: 0 } + - { name: 'Linux (CMake, static)', os: ubuntu-latest, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -DBUILD_SHARED_LIBS=OFF -GNinja', shared: 0, static: 1 } + - { name: Macos (autotools), os: macos-latest, shell: sh, shared: 1, static: 1 } + - { name: Macos (CMake), os: macos-latest, shell: sh, cmake: '-DSDL2MIXER_VENDORED=ON -GNinja', shared: 1, static: 0 } steps: - name: Set up MSYS2 @@ -95,13 +94,9 @@ jobs: .github/fetch_sdl_vc.ps1 echo "SDL2_DIR=$Env:GITHUB_WORKSPACE/SDL2-devel-VC" >> $Env:GITHUB_ENV echo "::endgroup::" - echo "::group::Downloading yasm" - .github/fetch_yasm.ps1 - echo "${{ github.workspace }}" >> $Env:GITHUB_PATH - echo "::endgroup::" - - name: Setup Ninja for MSVC + - name: Set up ninja + uses: ./.github/actions/setup-ninja if: ${{ !contains(matrix.platform.shell, 'msys2') }} - uses: aseprite/get-ninja@main - uses: ilammy/msvc-dev-cmd@v1 if: "matrix.platform.msvc" with: @@ -112,6 +107,10 @@ jobs: if: "runner.os == 'Linux' && matrix.platform.cmake" run: ./build-scripts/test-versioning.sh + - name: Set up yasm for mpg123 (MSVC only) + uses: ./.github/actions/setup-yasm + if: ${{ matrix.platform.msvc }} + - name: Configure (CMake) if: "matrix.platform.cmake" run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 801d0107..c0a6a4cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -262,7 +262,7 @@ jobs: msvc: needs: [src] - runs-on: windows-2019 + runs-on: windows-2025 outputs: VC-x86: ${{ steps.releaser.outputs.VC-x86 }} VC-x64: ${{ steps.releaser.outputs.VC-x64 }} diff --git a/.gitmodules b/.gitmodules index 146670a9..3e31df3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,11 +13,11 @@ [submodule "external/opus"] path = external/opus url = https://github.com/libsdl-org/opus.git - branch = v1.4-SDL + branch = v1.4.x-SDL [submodule "external/opusfile"] path = external/opusfile url = https://github.com/libsdl-org/opusfile.git - branch = v0.12-SDL + branch = v0.13-git-SDL [submodule "external/tremor"] path = external/tremor url = https://github.com/libsdl-org/tremor.git @@ -26,10 +26,10 @@ path = external/mpg123 url = https://github.com/libsdl-org/mpg123.git branch = v1.31.3-SDL -[submodule "libxmp"] +[submodule "external/libxmp"] path = external/libxmp url = https://github.com/libsdl-org/libxmp.git - branch = 4.6.2-SDL + branch = 4.6.3-SDL [submodule "external/wavpack"] path = external/wavpack url = https://github.com/libsdl-org/wavpack.git diff --git a/Android.mk b/Android.mk index dc0b2954..fb6257d6 100644 --- a/Android.mk +++ b/Android.mk @@ -31,7 +31,7 @@ SUPPORT_WAVPACK ?= true WAVPACK_LIBRARY_PATH := external/wavpack # Enable this if you want to support loading music via libgme -SUPPORT_GME ?= true +SUPPORT_GME ?= false GME_LIBRARY_PATH := external/libgme # Enable this if you want to support loading MOD music via XMP-lite @@ -92,7 +92,6 @@ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/src/ \ $(LOCAL_PATH)/src/codecs \ - LOCAL_SRC_FILES := \ $(subst $(LOCAL_PATH)/,, \ $(wildcard $(LOCAL_PATH)/src/*.c) \ diff --git a/CMakeLists.txt b/CMakeLists.txt index e102cc27..1a962e87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,6 +233,7 @@ add_library(SDL2_mixer src/codecs/music_wav.c src/codecs/music_wavpack.c src/codecs/music_xmp.c + src/codecs/remap_channels.c src/effect_position.c src/effect_stereoreverse.c src/effects_internal.c @@ -472,7 +473,7 @@ if(SDL2MIXER_VORBIS_TREMOR) message(STATUS "Using system tremor") find_package(tremor REQUIRED) if(NOT SDL2MIXER_VORBIS_TREMOR_SHARED) - list(APPEND PC_REQUIRES tremor) + list(APPEND PC_REQUIRES vorbisidec) endif() endif() if(SDL2MIXER_VORBIS_TREMOR_SHARED) diff --git a/Makefile.os2 b/Makefile.os2 index 64f150ef..09601948 100644 --- a/Makefile.os2 +++ b/Makefile.os2 @@ -59,6 +59,7 @@ SRCS = utils.c effect_position.c effects_internal.c effect_stereoreverse.c mixer # codec sources: SRCS+= load_aiff.c load_voc.c music_wav.c & music_ogg.c music_ogg_stb.c music_opus.c & + remap_channels.c & music_flac.c music_drflac.c music_wavpack.c & mp3utils.c music_mpg123.c music_minimp3.c & music_xmp.c music_modplug.c music_gme.c & diff --git a/README.txt b/README.txt index 0a62a6ab..b853164e 100644 --- a/README.txt +++ b/README.txt @@ -15,7 +15,7 @@ If you have built with FluidSynth support, you'll need to set the SDL_SOUNDFONTS (On some Linux distributions you can install the fluid-soundfont-gm package) To play MIDI files using Timidity, you'll need to get a complete set of GUS patches from: -http://www.libsdl.org/projects/mixer/timidity/timidity.tar.gz +https://www.libsdl.org/projects/old/SDL_mixer/timidity/timidity.tar.gz and unpack them in /usr/local/lib under UNIX, and C:\ under Win32. This library is under the zlib license, see the file "LICENSE.txt" for details. diff --git a/SDL2_mixerConfig.cmake.in b/SDL2_mixerConfig.cmake.in index 74517129..f4d5e351 100644 --- a/SDL2_mixerConfig.cmake.in +++ b/SDL2_mixerConfig.cmake.in @@ -43,17 +43,13 @@ set(SDL2MIXER_WAVE @SDL2MIXER_WAVE@) set(SDL2MIXER_WAVPACK @SDL2MIXER_WAVPACK@) set(SDL2MIXER_SDL2_REQUIRED_VERSION @SDL_REQUIRED_VERSION@) - -if(NOT SDL2MIXER_VENDORED) - set(_sdl_cmake_module_path "${CMAKE_MODULE_PATH}") - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") -endif() - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL2_mixer-shared-targets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/SDL2_mixer-shared-targets.cmake") endif() if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL2_mixer-static-targets.cmake") + set(_sdl_cmake_module_path "${CMAKE_MODULE_PATH}") + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") include(CMakeFindDependencyMacro) include(PkgConfigHelper) @@ -114,9 +110,7 @@ if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL2_mixer-static-targets.cmake") endif() endif() include("${CMAKE_CURRENT_LIST_DIR}/SDL2_mixer-static-targets.cmake") -endif() -if(NOT SDL2MIXER_VENDORED) set(CMAKE_MODULE_PATH "${_sdl_cmake_module_path}") unset(_sdl_cmake_module_path) endif() diff --git a/VisualC/external/include/opus/opusfile.h b/VisualC/external/include/opus/opusfile.h index 296d8d9a..ffc7abb9 100644 --- a/VisualC/external/include/opus/opusfile.h +++ b/VisualC/external/include/opus/opusfile.h @@ -147,18 +147,18 @@ typedef struct OggOpusFile OggOpusFile; /**@endcond*/ /**\defgroup error_codes Error Codes*/ -/*@{*/ +/**@{*/ /**\name List of possible error codes Many of the functions in this library return a negative error code when a function fails. This list provides a brief explanation of the common errors. See each individual function for more details on what a specific error code means in that context.*/ -/*@{*/ +/**@{*/ /**A request did not succeed.*/ #define OP_FALSE (-1) -/*Currently not used externally.*/ +/**Currently not used externally.**/ #define OP_EOF (-2) /**There was a hole in the page sequence numbers (e.g., a page was corrupt or missing).*/ @@ -185,7 +185,7 @@ typedef struct OggOpusFile OggOpusFile; #define OP_EBADHEADER (-133) /**The ID header contained an unrecognized version number.*/ #define OP_EVERSION (-134) -/*Currently not used at all.*/ +/**Currently not used at all.**/ #define OP_ENOTAUDIO (-135) /**An audio packet failed to decode properly. This is usually caused by a multistream Ogg packet where the durations of @@ -200,11 +200,11 @@ typedef struct OggOpusFile OggOpusFile; /**The first or last granule position of a link failed basic validity checks.*/ #define OP_EBADTIMESTAMP (-139) -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ /**\defgroup header_info Header Information*/ -/*@{*/ +/**@{*/ /**The maximum number of channels in an Ogg Opus stream.*/ #define OPUS_CHANNEL_COUNT_MAX (255) @@ -311,7 +311,7 @@ struct OpusTags{ }; /**\name Picture tag image formats*/ -/*@{*/ +/**@{*/ /**The MIME type was not recognized, or the image data did not match the declared MIME type.*/ @@ -325,7 +325,7 @@ struct OpusTags{ /**The image is a GIF.*/ #define OP_PIC_FORMAT_GIF (3) -/*@}*/ +/**@}*/ /**The contents of a METADATA_BLOCK_PICTURE tag.*/ struct OpusPictureTag{ @@ -398,7 +398,7 @@ struct OpusPictureTag{ These can be used to query the headers returned by libopusfile, or to parse Opus headers from sources other than an Ogg Opus stream, provided they use the same format.*/ -/*@{*/ +/**@{*/ /**Parses the contents of the ID header packet of an Ogg Opus stream. \param[out] _head Returns the contents of the parsed packet. @@ -671,12 +671,12 @@ void opus_picture_tag_init(OpusPictureTag *_pic) OP_ARG_NONNULL(1); \param _pic The #OpusPictureTag structure to clear.*/ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1); -/*@}*/ +/**@}*/ -/*@}*/ +/**@}*/ /**\defgroup url_options URL Reading Options*/ -/*@{*/ +/**@{*/ /**\name URL reading options Options for op_url_stream_create() and associated functions. These allow you to provide proxy configuration parameters, skip SSL @@ -685,7 +685,7 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1); times, only the value specified by the last occurrence has an effect (unless otherwise specified). They may be expanded in the future.*/ -/*@{*/ +/**@{*/ /**@cond PRIVATE*/ @@ -843,11 +843,11 @@ void opus_server_info_clear(OpusServerInfo *_info) OP_ARG_NONNULL(1); #define OP_GET_SERVER_INFO(_info) \ OP_URL_OPT(OP_GET_SERVER_INFO_REQUEST),OP_CHECK_SERVER_INFO_PTR(_info) -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ /**\defgroup stream_callbacks Abstract Stream Reading Interface*/ -/*@{*/ +/**@{*/ /**\name Functions for reading from streams These functions define the interface used to read from and seek in a stream of data. @@ -856,7 +856,7 @@ void opus_server_info_clear(OpusServerInfo *_info) OP_ARG_NONNULL(1); These functions also include some convenience routines for working with standard FILE pointers, complete streams stored in a single block of memory, or URLs.*/ -/*@{*/ +/**@{*/ /**Reads up to \a _nbytes bytes of data from \a _stream. \param _stream The stream to read from. @@ -1034,18 +1034,18 @@ OP_WARN_UNUSED_RESULT void *op_url_stream_vcreate(OpusFileCallbacks *_cb, OP_WARN_UNUSED_RESULT void *op_url_stream_create(OpusFileCallbacks *_cb, const char *_url,...) OP_ARG_NONNULL(1) OP_ARG_NONNULL(2); -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ /**\defgroup stream_open_close Opening and Closing*/ -/*@{*/ +/**@{*/ /**\name Functions for opening and closing streams These functions allow you to test a stream to see if it is Opus, open it, and close it. Several flavors are provided for each of the built-in stream types, plus a more general version which takes a set of application-provided callbacks.*/ -/*@{*/ +/**@{*/ /**Test to see if this is an Opus stream. For good results, you will need at least 57 bytes (for a pure Opus-only @@ -1159,20 +1159,16 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url, This value will be passed verbatim as the first argument to all of the callbacks. \param _cb The callbacks with which to access the stream. - read() must - be implemented. - seek() and - tell() may - be NULL, or may always return -1 to - indicate a stream is unseekable, but if - seek() is - implemented and succeeds on a particular stream, then - tell() must - also. - close() may - be NULL, but if it is not, it will be - called when the \c OggOpusFile is destroyed by - op_free(). + \ref op_read_func "read()" must be implemented. + \ref op_seek_func "seek()" and \ref op_tell_func + "tell()" may be NULL, or may always + return -1 to indicate a stream is unseekable, but if + \ref op_seek_func "seek()" is implemented and + succeeds on a particular stream, then \ref + op_tell_func "tell()" must also. + \ref op_close_func "close()" may be NULL, + but if it is not, it will be called when the \c + OggOpusFile is destroyed by op_free(). It will not be called if op_open_callbacks() fails with an error. \param _initial_data An initial buffer of data from the start of the @@ -1183,10 +1179,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url, stream to be opened, even if it is unseekable. \param _initial_bytes The number of bytes in \a _initial_data. If the stream is seekable, its current position (as - reported by - tell() - at the start of this function) must be equal to - \a _initial_bytes. + reported by \ref op_tell_func "tell()" at the start + of this function) must be equal to \a _initial_bytes. Otherwise, seeking to absolute positions will generate inconsistent results. \param[out] _error Returns 0 on success, or a failure code on error. @@ -1206,11 +1200,10 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url, implemented, such as an unsupported channel family.
#OP_EINVAL
-
seek() - was implemented and succeeded on this source, but - tell() - did not, or the starting position indicator was - not equal to \a _initial_bytes.
+
\ref op_seek_func "seek()" was implemented and + succeeded on this source, but \ref op_tell_func + "tell()" did not, or the starting position + indicator was not equal to \a _initial_bytes.
#OP_ENOTFORMAT
The stream contained a link that did not have any logical Opus streams in it.
@@ -1341,20 +1334,16 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url, This value will be passed verbatim as the first argument to all of the callbacks. \param _cb The callbacks with which to access the stream. - read() must - be implemented. - seek() and - tell() may - be NULL, or may always return -1 to - indicate a stream is unseekable, but if - seek() is - implemented and succeeds on a particular stream, then - tell() must - also. - close() may - be NULL, but if it is not, it will be - called when the \c OggOpusFile is destroyed by - op_free(). + \ref op_read_func "read()" must be implemented. + \ref op_seek_func "seek()" and \ref op_tell_func + "tell()" may be NULL, or may always + return -1 to indicate a stream is unseekable, but if + \ref op_seek_func "seek()" is implemented and + succeeds on a particular stream, then \ref + op_tell_func "tell()" must also. + \ref op_close_func "close()" may be NULL, + but if it is not, it will be called when the \c + OggOpusFile is destroyed by op_free(). It will not be called if op_open_callbacks() fails with an error. \param _initial_data An initial buffer of data from the start of the @@ -1367,9 +1356,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url, \param _initial_bytes The number of bytes in \a _initial_data. If the stream is seekable, its current position (as reported by - tell() - at the start of this function) must be equal to - \a _initial_bytes. + \ref op_tell_func "tell()" at the start of this + function) must be equal to \a _initial_bytes. Otherwise, seeking to absolute positions will generate inconsistent results. \param[out] _error Returns 0 on success, or a failure code on error. @@ -1418,11 +1406,11 @@ int op_test_open(OggOpusFile *_of) OP_ARG_NONNULL(1); \param _of The \c OggOpusFile to free.*/ void op_free(OggOpusFile *_of); -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ /**\defgroup stream_info Stream Information*/ -/*@{*/ +/**@{*/ /**\name Functions for obtaining information about streams These functions allow you to get basic information about a stream, including @@ -1437,18 +1425,17 @@ void op_free(OggOpusFile *_of); streams returned by op_test_callbacks() or one of the associated convenience functions. Their documention will indicate so explicitly.*/ -/*@{*/ +/**@{*/ /**Returns whether or not the stream being read is seekable. This is true if
    -
  1. The seek() and - tell() callbacks are both - non-NULL,
  2. -
  3. The seek() callback was - successfully executed at least once, and
  4. -
  5. The tell() callback was - successfully able to report the position indicator afterwards.
  6. +
  7. The \ref op_seek_func "seek()" and \ref op_tell_func "tell()" + callbacks are both non-NULL,
  8. +
  9. The \ref op_seek_func "seek()" callback was successfully executed at + least once, and
  10. +
  11. The \ref op_tell_func "tell()" callback was successfully able to report + the position indicator afterwards.
This function may be called on partially-opened streams. \param _of The \c OggOpusFile whose seekable status is to be returned. @@ -1638,11 +1625,11 @@ opus_int64 op_raw_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1); \retval #OP_EINVAL The stream was only partially open.*/ ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1); -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ /**\defgroup stream_seeking Seeking*/ -/*@{*/ +/**@{*/ /**\name Functions for seeking in Opus streams These functions let you seek in Opus streams, if the underlying stream @@ -1667,7 +1654,7 @@ ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1); values as would be obtained by decoding the stream straight through. However, such differences are expected to be smaller than the loss introduced by Opus's lossy compression.*/ -/*@{*/ +/**@{*/ /**Seek to a byte offset relative to the compressed data. This also scans packets to update the PCM cursor. @@ -1702,11 +1689,11 @@ int op_raw_seek(OggOpusFile *_of,opus_int64 _byte_offset) OP_ARG_NONNULL(1); seeking to the target destination was impossible.*/ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1); -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ /**\defgroup stream_decoding Decoding*/ -/*@{*/ +/**@{*/ /**\name Functions for decoding audio data These functions retrieve actual decoded audio data from the stream. @@ -1744,7 +1731,7 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1); If you are reading from an URL (particularly if seeking is not supported), you should make sure to check for this error and warn the user appropriately.*/ -/*@{*/ +/**@{*/ /**Indicates that the decoding callback should produce signed 16-bit native-endian output samples.*/ @@ -1829,7 +1816,7 @@ void op_set_decode_callback(OggOpusFile *_of, #define OP_TRACK_GAIN (3008) /**Gain offset type that indicates that the provided offset should be used as - the gain directly, without applying any the header or track gains.*/ + the gain directly, without applying any of the header or track gains.*/ #define OP_ABSOLUTE_GAIN (3009) /**Sets the gain to be used for decoded output. @@ -2150,8 +2137,8 @@ OP_WARN_UNUSED_RESULT int op_read_stereo(OggOpusFile *_of, OP_WARN_UNUSED_RESULT int op_read_float_stereo(OggOpusFile *_of, float *_pcm,int _buf_size) OP_ARG_NONNULL(1); -/*@}*/ -/*@}*/ +/**@}*/ +/**@}*/ # if OP_GNUC_PREREQ(4,0) # pragma GCC visibility pop diff --git a/VisualC/external/include/xmp.h b/VisualC/external/include/xmp.h index aaeb1649..2c940ef8 100644 --- a/VisualC/external/include/xmp.h +++ b/VisualC/external/include/xmp.h @@ -9,11 +9,11 @@ extern "C" { #endif -#define XMP_VERSION "4.6.2" -#define XMP_VERCODE 0x040602 +#define XMP_VERSION "4.6.3" +#define XMP_VERCODE 0x040603 #define XMP_VER_MAJOR 4 #define XMP_VER_MINOR 6 -#define XMP_VER_RELEASE 2 +#define XMP_VER_RELEASE 3 #if defined(_WIN32) && !defined(__CYGWIN__) # if defined(LIBXMP_STATIC) diff --git a/VisualC/external/optional/x64/LICENSE.ogg-vorbis.txt b/VisualC/external/optional/x64/LICENSE.ogg-vorbis.txt index c15bc2b7..f23d66ea 100644 --- a/VisualC/external/optional/x64/LICENSE.ogg-vorbis.txt +++ b/VisualC/external/optional/x64/LICENSE.ogg-vorbis.txt @@ -1,8 +1,4 @@ -The source code to this library used with SDL_mixer can be found here: -https://hg.libsdl.org/SDL_mixer/file/default/external ---- - -Copyright (c) 2002-2018 Xiph.org Foundation +Copyright (c) 2002-2020 Xiph.org Foundation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/VisualC/external/optional/x64/LICENSE.opus.txt b/VisualC/external/optional/x64/LICENSE.opus.txt index c43fe9c0..fb6f3e40 100644 --- a/VisualC/external/optional/x64/LICENSE.opus.txt +++ b/VisualC/external/optional/x64/LICENSE.opus.txt @@ -1,7 +1,3 @@ -The source code to this library used with SDL_mixer can be found here: -https://hg.libsdl.org/SDL_mixer/file/default/external ---- - Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, diff --git a/VisualC/external/optional/x64/LICENSE.opusfile.txt b/VisualC/external/optional/x64/LICENSE.opusfile.txt index e62a20bd..d7461f15 100644 --- a/VisualC/external/optional/x64/LICENSE.opusfile.txt +++ b/VisualC/external/optional/x64/LICENSE.opusfile.txt @@ -1,7 +1,3 @@ -The source code to this library used with SDL_mixer can be found here: -https://hg.libsdl.org/SDL_mixer/file/default/external ---- - Copyright (c) 1994-2013 Xiph.Org Foundation and contributors Redistribution and use in source and binary forms, with or without diff --git a/VisualC/external/optional/x64/libogg-0.dll b/VisualC/external/optional/x64/libogg-0.dll index 5ec1f1e6..e656f78f 100644 Binary files a/VisualC/external/optional/x64/libogg-0.dll and b/VisualC/external/optional/x64/libogg-0.dll differ diff --git a/VisualC/external/optional/x64/libopus-0.dll b/VisualC/external/optional/x64/libopus-0.dll index 5991eb7e..21becbc1 100644 Binary files a/VisualC/external/optional/x64/libopus-0.dll and b/VisualC/external/optional/x64/libopus-0.dll differ diff --git a/VisualC/external/optional/x64/libopusfile-0.dll b/VisualC/external/optional/x64/libopusfile-0.dll index 8a5134c2..0d4eaaca 100644 Binary files a/VisualC/external/optional/x64/libopusfile-0.dll and b/VisualC/external/optional/x64/libopusfile-0.dll differ diff --git a/VisualC/external/optional/x64/libwavpack-1.dll b/VisualC/external/optional/x64/libwavpack-1.dll index a0d7874e..fc76fbbd 100644 Binary files a/VisualC/external/optional/x64/libwavpack-1.dll and b/VisualC/external/optional/x64/libwavpack-1.dll differ diff --git a/VisualC/external/optional/x64/libxmp.dll b/VisualC/external/optional/x64/libxmp.dll index 11e9fd32..4d12aa50 100644 Binary files a/VisualC/external/optional/x64/libxmp.dll and b/VisualC/external/optional/x64/libxmp.dll differ diff --git a/VisualC/external/optional/x86/LICENSE.ogg-vorbis.txt b/VisualC/external/optional/x86/LICENSE.ogg-vorbis.txt index c15bc2b7..f23d66ea 100644 --- a/VisualC/external/optional/x86/LICENSE.ogg-vorbis.txt +++ b/VisualC/external/optional/x86/LICENSE.ogg-vorbis.txt @@ -1,8 +1,4 @@ -The source code to this library used with SDL_mixer can be found here: -https://hg.libsdl.org/SDL_mixer/file/default/external ---- - -Copyright (c) 2002-2018 Xiph.org Foundation +Copyright (c) 2002-2020 Xiph.org Foundation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/VisualC/external/optional/x86/LICENSE.opus.txt b/VisualC/external/optional/x86/LICENSE.opus.txt index c43fe9c0..fb6f3e40 100644 --- a/VisualC/external/optional/x86/LICENSE.opus.txt +++ b/VisualC/external/optional/x86/LICENSE.opus.txt @@ -1,7 +1,3 @@ -The source code to this library used with SDL_mixer can be found here: -https://hg.libsdl.org/SDL_mixer/file/default/external ---- - Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, diff --git a/VisualC/external/optional/x86/LICENSE.opusfile.txt b/VisualC/external/optional/x86/LICENSE.opusfile.txt index e62a20bd..d7461f15 100644 --- a/VisualC/external/optional/x86/LICENSE.opusfile.txt +++ b/VisualC/external/optional/x86/LICENSE.opusfile.txt @@ -1,7 +1,3 @@ -The source code to this library used with SDL_mixer can be found here: -https://hg.libsdl.org/SDL_mixer/file/default/external ---- - Copyright (c) 1994-2013 Xiph.Org Foundation and contributors Redistribution and use in source and binary forms, with or without diff --git a/VisualC/external/optional/x86/libogg-0.dll b/VisualC/external/optional/x86/libogg-0.dll index f2487a5c..795432e1 100644 Binary files a/VisualC/external/optional/x86/libogg-0.dll and b/VisualC/external/optional/x86/libogg-0.dll differ diff --git a/VisualC/external/optional/x86/libopus-0.dll b/VisualC/external/optional/x86/libopus-0.dll index a963b651..b1804755 100644 Binary files a/VisualC/external/optional/x86/libopus-0.dll and b/VisualC/external/optional/x86/libopus-0.dll differ diff --git a/VisualC/external/optional/x86/libopusfile-0.dll b/VisualC/external/optional/x86/libopusfile-0.dll index 672ea2a9..fe10e03a 100644 Binary files a/VisualC/external/optional/x86/libopusfile-0.dll and b/VisualC/external/optional/x86/libopusfile-0.dll differ diff --git a/VisualC/external/optional/x86/libwavpack-1.dll b/VisualC/external/optional/x86/libwavpack-1.dll index 4b155fca..ebf190fe 100644 Binary files a/VisualC/external/optional/x86/libwavpack-1.dll and b/VisualC/external/optional/x86/libwavpack-1.dll differ diff --git a/VisualC/external/optional/x86/libxmp.dll b/VisualC/external/optional/x86/libxmp.dll index dcc8958a..48644563 100644 Binary files a/VisualC/external/optional/x86/libxmp.dll and b/VisualC/external/optional/x86/libxmp.dll differ diff --git a/build-scripts/build-release.py b/build-scripts/build-release.py index d3825a47..e25bda8c 100755 --- a/build-scripts/build-release.py +++ b/build-scripts/build-release.py @@ -37,6 +37,8 @@ GIT_HASH_FILENAME = ".git-hash" REVISION_TXT = "REVISION.txt" +RE_ILLEGAL_MINGW_LIBRARIES = re.compile(r"(?:lib)?(?:gcc|(?:std)?c[+][+]|(?:win)?pthread).*", flags=re.I) + def safe_isotime_to_datetime(str_isotime: str) -> datetime.datetime: try: @@ -532,6 +534,16 @@ def _get_file_times(self, paths: tuple[str, ...]) -> dict[str, datetime.datetime return path_times +class AndroidApiVersion: + def __init__(self, name: str, ints: tuple[int, ...]): + self.name = name + self.ints = ints + + def __repr__(self) -> str: + return f"<{self.name} ({'.'.join(str(v) for v in self.ints)})>" + +ANDROID_ABI_EXTRA_LINK_OPTIONS = {} + class Releaser: def __init__(self, release_info: dict, commit: str, revision: str, root: Path, dist_path: Path, section_printer: SectionPrinter, executer: Executer, cmake_generator: str, deps_path: Path, overwrite: bool, github: bool, fast: bool): self.release_info = release_info @@ -684,6 +696,15 @@ def create_dmg(self, configuration: str="Release") -> None: def git_hash_data(self) -> bytes: return f"{self.commit}\n".encode() + def verify_mingw_library(self, triplet: str, path: Path): + objdump_output = self.executer.check_output([f"{triplet}-objdump", "-p", str(path)]) + libraries = re.findall(r"DLL Name: ([^\n]+)", objdump_output) + logger.info("%s (%s) libraries: %r", path, triplet, libraries) + illegal_libraries = list(filter(RE_ILLEGAL_MINGW_LIBRARIES.match, libraries)) + logger.error("Detected 'illegal' libraries: %r", illegal_libraries) + if illegal_libraries: + raise Exception(f"{path} links to illegal libraries: {illegal_libraries}") + def create_mingw_archives(self) -> None: build_type = "Release" build_parent_dir = self.root / "build-mingw" @@ -790,6 +811,7 @@ def extract_filter(member: tarfile.TarInfo, path: str, /): self.executer.run(["make", f"-j{self.cpu_count}"], cwd=build_path, env=new_env) with self.section_printer.group(f"Install MinGW {triplet} (autotools)"): self.executer.run(["make", "install"], cwd=build_path, env=new_env) + self.verify_mingw_library(triplet=ARCH_TO_TRIPLET[arch], path=install_path / "bin" / f"{self.project}.dll") archive_file_tree.add_directory_tree(arc_dir=arc_join(arc_root, triplet), path=install_path, time=self.arc_time) print("Recording arch-dependent extra files for MinGW development archive ...") @@ -845,6 +867,7 @@ def extract_filter(member: tarfile.TarInfo, path: str, /): self.executer.run(["cmake", "--build", str(build_path), "--verbose", "--config", build_type], cwd=build_path, env=new_env) with self.section_printer.group(f"Install MinGW {triplet} (CMake)"): self.executer.run(["cmake", "--install", str(build_path)], cwd=build_path, env=new_env) + self.verify_mingw_library(triplet=ARCH_TO_TRIPLET[arch], path=install_path / "bin" / f"{self.project}.dll") archive_file_tree.add_directory_tree(arc_dir=arc_join(arc_root, triplet), path=install_path, time=self.arc_time) print("Recording arch-dependent extra files for MinGW development archive ...") @@ -872,22 +895,25 @@ def extract_filter(member: tarfile.TarInfo, path: str, /): self.artifacts["mingw-devel-tar-gz"] = tgz_path self.artifacts["mingw-devel-tar-xz"] = txz_path - def _detect_android_api(self, android_home: str) -> typing.Optional[int]: + def _detect_android_api(self, android_home: str) -> typing.Optional[AndroidApiVersion]: platform_dirs = list(Path(p) for p in glob.glob(f"{android_home}/platforms/android-*")) - re_platform = re.compile("android-([0-9]+)") - platform_versions = [] + re_platform = re.compile("^android-([0-9]+)(?:-ext([0-9]+))?$") + platform_versions: list[AndroidApiVersion] = [] for platform_dir in platform_dirs: logger.debug("Found Android Platform SDK: %s", platform_dir) + if not (platform_dir / "android.jar").is_file(): + logger.debug("Skipping SDK, missing android.jar") + continue if m:= re_platform.match(platform_dir.name): - platform_versions.append(int(m.group(1))) - platform_versions.sort() + platform_versions.append(AndroidApiVersion(name=platform_dir.name, ints=(int(m.group(1)), int(m.group(2) or 0)))) + platform_versions.sort(key=lambda v: v.ints) logger.info("Available platform versions: %s", platform_versions) - platform_versions = list(filter(lambda v: v >= self._android_api_minimum, platform_versions)) - logger.info("Valid platform versions (>=%d): %s", self._android_api_minimum, platform_versions) + platform_versions = list(filter(lambda v: v.ints >= self._android_api_minimum.ints, platform_versions)) + logger.info("Valid platform versions (>=%s): %s", self._android_api_minimum.ints, platform_versions) if not platform_versions: return None android_api = platform_versions[0] - logger.info("Selected API version %d", android_api) + logger.info("Selected API version %s", android_api) return android_api def _get_prefab_json_text(self) -> str: @@ -911,8 +937,19 @@ def _get_prefab_module_json_text(self, library_name: typing.Optional[str], expor return json.dumps(module_json_dict, indent=4) @property - def _android_api_minimum(self): - return self.release_info["android"]["api-minimum"] + def _android_api_minimum(self) -> AndroidApiVersion: + value = self.release_info["android"]["api-minimum"] + if isinstance(value, int): + ints = (value, ) + elif isinstance(value, str): + ints = tuple(split(".")) + else: + raise ValueError("Invalid android.api-minimum: must be X or X.Y") + match len(ints): + case 1: name = f"android-{ints[0]}" + case 2: name = f"android-{ints[0]}-ext-{ints[1]}" + case _: raise ValueError("Invalid android.api-minimum: must be X or X.Y") + return AndroidApiVersion(name=name, ints=ints) @property def _android_api_target(self): @@ -925,7 +962,7 @@ def _android_ndk_minimum(self): def _get_prefab_abi_json_text(self, abi: str, cpp: bool, shared: bool) -> str: abi_json_dict = { "abi": abi, - "api": self._android_api_minimum, + "api": self._android_api_minimum.ints[0], "ndk": self._android_ndk_minimum, "stl": "c++_shared" if cpp else "none", "static": not shared, @@ -938,7 +975,7 @@ def _get_android_manifest_text(self) -> str: xmlns:android="http://schemas.android.com/apk/res/android" package="org.libsdl.android.{self.project}" android:versionCode="1" android:versionName="1.0"> - """) @@ -948,7 +985,8 @@ def create_android_archives(self, android_api: int, android_home: Path, android_ if not cmake_toolchain_file.exists(): logger.error("CMake toolchain file does not exist (%s)", cmake_toolchain_file) raise SystemExit(1) - aar_path = self.dist_path / f"{self.project}-{self.version}.aar" + aar_path = self.root / "build-android" / f"{self.project}-{self.version}.aar" + android_dist_path = self.dist_path / f"{self.project}-devel-{self.version}-android.zip" android_abis = self.release_info["android"]["abis"] java_jars_added = False module_data_added = False @@ -956,16 +994,27 @@ def create_android_archives(self, android_api: int, android_home: Path, android_ shutil.rmtree(android_deps_path, ignore_errors=True) for dep, depinfo in self.release_info["android"].get("dependencies", {}).items(): - android_aar = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0] - with self.section_printer.group(f"Extracting Android dependency {dep} ({android_aar.name})"): - self.executer.run([sys.executable, str(android_aar), "-o", str(android_deps_path)]) + dep_devel_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0] + + dep_extract_path = self.deps_path / f"extract/android/{dep}" + shutil.rmtree(dep_extract_path, ignore_errors=True) + dep_extract_path.mkdir(parents=True, exist_ok=True) + + with self.section_printer.group(f"Extracting Android dependency {dep} ({dep_devel_zip})"): + with zipfile.ZipFile(dep_devel_zip, "r") as zf: + zf.extractall(dep_extract_path) + + dep_devel_aar = dep_extract_path / glob.glob("*.aar", root_dir=dep_extract_path)[0] + self.executer.run([sys.executable, str(dep_devel_aar), "-o", str(android_deps_path)]) for module_name, module_info in self.release_info["android"]["modules"].items(): assert "type" in module_info and module_info["type"] in ("interface", "library"), f"module {module_name} must have a valid type" - archive_file_tree = ArchiveFileTree() + aar_file_tree = ArchiveFileTree() + android_devel_file_tree = ArchiveFileTree() for android_abi in android_abis: + extra_link_options = ANDROID_ABI_EXTRA_LINK_OPTIONS.get(android_abi, "") with self.section_printer.group(f"Building for Android {android_api} {android_abi}"): build_dir = self.root / "build-android" / f"{android_abi}-build" install_dir = self.root / "install-android" / f"{android_abi}-install" @@ -976,8 +1025,11 @@ def create_android_archives(self, android_api: int, android_home: Path, android_ "cmake", "-S", str(self.root), "-B", str(build_dir), - f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', - f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + # NDK 21e does not support -ffile-prefix-map + # f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + # f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + f"-DCMAKE_EXE_LINKER_FLAGS={extra_link_options}", + f"-DCMAKE_SHARED_LINKER_FLAGS={extra_link_options}", f"-DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file}", f"-DCMAKE_PREFIX_PATH={str(android_deps_path)}", f"-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH", @@ -1014,20 +1066,20 @@ def create_android_archives(self, android_api: int, android_home: Path, android_ assert library.suffix in (".so", ".a") assert library.is_file(), f"CMake should have built library '{library}' for module {module_name}" arcdir_prefab_libs = f"{arcdir_prefab_module}/libs/android.{android_abi}" - archive_file_tree.add_file(NodeInArchive.from_fs(arcpath=f"{arcdir_prefab_libs}/{library.name}", path=library, time=self.arc_time)) - archive_file_tree.add_file(NodeInArchive.from_text(arcpath=f"{arcdir_prefab_libs}/abi.json", text=self._get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=library.suffix == ".so"), time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_fs(arcpath=f"{arcdir_prefab_libs}/{library.name}", path=library, time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_text(arcpath=f"{arcdir_prefab_libs}/abi.json", text=self._get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=library.suffix == ".so"), time=self.arc_time)) if not module_data_added: library_name = None if module_info["type"] == "library": library_name = Path(module_info["library"]).stem.removeprefix("lib") export_libraries = module_info.get("export-libraries", []) - archive_file_tree.add_file(NodeInArchive.from_text(arcpath=arc_join(arcdir_prefab_module, "module.json"), text=self._get_prefab_module_json_text(library_name=library_name, export_libraries=export_libraries), time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_text(arcpath=arc_join(arcdir_prefab_module, "module.json"), text=self._get_prefab_module_json_text(library_name=library_name, export_libraries=export_libraries), time=self.arc_time)) arcdir_prefab_include = f"prefab/modules/{module_name}/include" if "includes" in module_info: - archive_file_tree.add_file_mapping(arc_dir=arcdir_prefab_include, file_mapping=module_info["includes"], file_mapping_root=install_dir, context=self.get_context(), time=self.arc_time) + aar_file_tree.add_file_mapping(arc_dir=arcdir_prefab_include, file_mapping=module_info["includes"], file_mapping_root=install_dir, context=self.get_context(), time=self.arc_time) else: - archive_file_tree.add_file(NodeInArchive.from_text(arcpath=arc_join(arcdir_prefab_include, ".keep"), text="\n", time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_text(arcpath=arc_join(arcdir_prefab_include, ".keep"), text="\n", time=self.arc_time)) module_data_added = True if not java_jars_added: @@ -1040,21 +1092,28 @@ def create_android_archives(self, android_api: int, android_home: Path, android_ assert sources_jar_path.is_file(), f"CMake should have archived the java sources into a JAR ({sources_jar_path})" assert doc_jar_path.is_file(), f"CMake should have archived javadoc into a JAR ({doc_jar_path})" - archive_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes.jar", path=classes_jar_path, time=self.arc_time)) - archive_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes-sources.jar", path=sources_jar_path, time=self.arc_time)) - archive_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes-doc.jar", path=doc_jar_path, time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes.jar", path=classes_jar_path, time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes-sources.jar", path=sources_jar_path, time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes-doc.jar", path=doc_jar_path, time=self.arc_time)) assert ("jars" in self.release_info["android"] and java_jars_added) or "jars" not in self.release_info["android"], "Must have archived java JAR archives" - archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["android"].get("files", {}), file_mapping_root=self.root, context=self.get_context(), time=self.arc_time) + aar_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["android"]["aar-files"], file_mapping_root=self.root, context=self.get_context(), time=self.arc_time) - archive_file_tree.add_file(NodeInArchive.from_text(arcpath="prefab/prefab.json", text=self._get_prefab_json_text(), time=self.arc_time)) - archive_file_tree.add_file(NodeInArchive.from_text(arcpath="AndroidManifest.xml", text=self._get_android_manifest_text(), time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_text(arcpath="prefab/prefab.json", text=self._get_prefab_json_text(), time=self.arc_time)) + aar_file_tree.add_file(NodeInArchive.from_text(arcpath="AndroidManifest.xml", text=self._get_android_manifest_text(), time=self.arc_time)) with Archiver(zip_path=aar_path) as archiver: - archive_file_tree.add_to_archiver(archive_base="", archiver=archiver) + aar_file_tree.add_to_archiver(archive_base="", archiver=archiver) + archiver.add_git_hash(arcdir="", commit=self.commit, time=self.arc_time) + + android_devel_file_tree.add_file(NodeInArchive.from_fs(arcpath=aar_path.name, path=aar_path)) + android_devel_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["android"]["files"], file_mapping_root=self.root, context=self.get_context(), time=self.arc_time) + with Archiver(zip_path=android_dist_path) as archiver: + android_devel_file_tree.add_to_archiver(archive_base="", archiver=archiver) archiver.add_git_hash(arcdir="", commit=self.commit, time=self.arc_time) - self.artifacts[f"android-aar"] = aar_path + + self.artifacts[f"android-aar"] = android_dist_path def download_dependencies(self): shutil.rmtree(self.deps_path, ignore_errors=True) @@ -1091,7 +1150,7 @@ def verify_dependencies(self): assert len(msvc_matches) == 1, f"Exactly one archive matches msvc {dep} dependency: {msvc_matches}" if "android" in self.release_info: android_matches = glob.glob(self.release_info["android"]["dependencies"][dep]["artifact"], root_dir=self.deps_path) - assert len(android_matches) == 1, f"Exactly one archive matches msvc {dep} dependency: {msvc_matches}" + assert len(android_matches) == 1, f"Exactly one archive matches msvc {dep} dependency: {android_matches}" @staticmethod def _arch_to_vs_platform(arch: str, configuration: str="Release") -> VsArchPlatformConfig: @@ -1217,6 +1276,10 @@ def _build_msvc_cmake(self, arch_platform: VsArchPlatformConfig, dep_roots: list platform_context = self.get_context(extra_context=arch_platform.extra_context()) build_type = "Release" + extra_context = { + "ARCH": arch_platform.arch, + "PLATFORM": arch_platform.platform, + } built_paths = set(install_path / configure_text(f, context=platform_context) for file_mapping in (self.release_info["msvc"]["cmake"]["files-lib"], self.release_info["msvc"]["cmake"]["files-devel"]) for files_list in file_mapping.values() for f in files_list) logger.info("CMake builds these files, to be included in the package: %s", built_paths) @@ -1267,7 +1330,7 @@ def _build_msvc_cmake(self, arch_platform: VsArchPlatformConfig, dep_roots: list logger.info("Collecting files...") archive_file_tree = ArchiveFileTree() archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["msvc"]["cmake"]["files-lib"], file_mapping_root=install_path, context=platform_context, time=self.arc_time) - archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["msvc"]["files-lib"], file_mapping_root=self.root, context=self.get_context(), time=self.arc_time) + archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["msvc"]["files-lib"], file_mapping_root=self.root, context=self.get_context(extra_context=extra_context), time=self.arc_time) logger.info("Creating %s", zip_path) with Archiver(zip_path=zip_path) as archiver: @@ -1331,7 +1394,7 @@ def main(argv=None) -> int: parser.add_argument("--actions", choices=["download", "source", "android", "mingw", "msvc", "dmg"], required=True, nargs="+", dest="actions", help="What to do?") parser.set_defaults(loglevel=logging.INFO) parser.add_argument('--vs-year', dest="vs_year", help="Visual Studio year") - parser.add_argument('--android-api', type=int, dest="android_api", help="Android API version") + parser.add_argument('--android-api', dest="android_api", help="Android API version") parser.add_argument('--android-home', dest="android_home", default=os.environ.get("ANDROID_HOME"), help="Android Home folder") parser.add_argument('--android-ndk-home', dest="android_ndk_home", default=os.environ.get("ANDROID_NDK_HOME"), help="Android NDK Home folder") parser.add_argument('--cmake-generator', dest="cmake_generator", default="Ninja", help="CMake Generator") @@ -1458,14 +1521,27 @@ def main(argv=None) -> int: if args.android_api is None: with section_printer.group("Detect Android APIS"): args.android_api = releaser._detect_android_api(android_home=args.android_home) - if args.android_api is None or not (Path(args.android_home) / f"platforms/android-{args.android_api}").is_dir(): + else: + try: + android_api_ints = tuple(int(v) for v in args.android_api.split(".")) + match len(android_api_ints): + case 1: android_api_name = f"android-{android_api_ints[0]}" + case 2: android_api_name = f"android-{android_api_ints[0]}-ext-{android_api_ints[1]}" + case _: raise ValueError + except ValueError: + logger.error("Invalid --android-api, must be a 'X' or 'X.Y' version") + args.android_api = AndroidApiVersion(ints=android_api_ints, name=android_api_name) + if args.android_api is None: parser.error("Invalid --android-api, and/or could not be detected") + android_api_path = Path(args.android_home) / f"platforms/{args.android_api.name}" + if not android_api_path.is_dir(): + logger.warning(f"Android API directory does not exist ({android_api_path})") with section_printer.group("Android arguments"): print(f"android_home = {args.android_home}") print(f"android_ndk_home = {args.android_ndk_home}") print(f"android_api = {args.android_api}") releaser.create_android_archives( - android_api=args.android_api, + android_api=args.android_api.ints[0], android_home=args.android_home, android_ndk_home=args.android_ndk_home, ) diff --git a/build-scripts/config.guess b/build-scripts/config.guess index 48a68460..a9d01fde 100755 --- a/build-scripts/config.guess +++ b/build-scripts/config.guess @@ -1,10 +1,10 @@ #! /bin/sh # Attempt to guess a canonical system name. -# Copyright 1992-2024 Free Software Foundation, Inc. +# Copyright 1992-2025 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale -timestamp='2024-07-27' +timestamp='2025-07-10' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -60,7 +60,7 @@ version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. -Copyright 1992-2024 Free Software Foundation, Inc. +Copyright 1992-2025 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." @@ -1597,8 +1597,11 @@ EOF *:Unleashed:*:*) GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE ;; - *:Ironclad:*:*) - GUESS=$UNAME_MACHINE-unknown-ironclad + x86_64:[Ii]ronclad:*:*|i?86:[Ii]ronclad:*:*) + GUESS=$UNAME_MACHINE-pc-ironclad-mlibc + ;; + *:[Ii]ronclad:*:*) + GUESS=$UNAME_MACHINE-unknown-ironclad-mlibc ;; esac @@ -1808,8 +1811,8 @@ fi exit 1 # Local variables: -# eval: (add-hook 'before-save-hook 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "timestamp='" -# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-format: "%Y-%02m-%02d" # time-stamp-end: "'" # End: diff --git a/build-scripts/config.sub b/build-scripts/config.sub index 4aaae46f..3d35cde1 100755 --- a/build-scripts/config.sub +++ b/build-scripts/config.sub @@ -1,10 +1,10 @@ #! /bin/sh # Configuration validation subroutine script. -# Copyright 1992-2024 Free Software Foundation, Inc. +# Copyright 1992-2025 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268,SC2162 # see below for rationale -timestamp='2024-05-27' +timestamp='2025-07-10' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -76,7 +76,7 @@ Report bugs and patches to ." version="\ GNU config.sub ($timestamp) -Copyright 1992-2024 Free Software Foundation, Inc. +Copyright 1992-2025 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." @@ -145,6 +145,7 @@ case $1 in | kfreebsd*-gnu* \ | knetbsd*-gnu* \ | kopensolaris*-gnu* \ + | ironclad-* \ | linux-* \ | managarm-* \ | netbsd*-eabi* \ @@ -242,7 +243,6 @@ case $1 in | rombug \ | semi \ | sequent* \ - | siemens \ | sgi* \ | siemens \ | sim \ @@ -261,7 +261,7 @@ case $1 in basic_machine=$field1-$field2 basic_os= ;; - zephyr*) + tock* | zephyr*) basic_machine=$field1-unknown basic_os=$field2 ;; @@ -1194,7 +1194,7 @@ case $cpu-$vendor in xscale-* | xscalee[bl]-*) cpu=`echo "$cpu" | sed 's/^xscale/arm/'` ;; - arm64-* | aarch64le-*) + arm64-* | aarch64le-* | arm64_32-*) cpu=aarch64 ;; @@ -1321,6 +1321,7 @@ case $cpu-$vendor in | i960 \ | ia16 \ | ia64 \ + | intelgt \ | ip2k \ | iq2000 \ | javascript \ @@ -1522,6 +1523,10 @@ EOF kernel=nto os=`echo "$basic_os" | sed -e 's|nto|qnx|'` ;; + ironclad*) + kernel=ironclad + os=`echo "$basic_os" | sed -e 's|ironclad|mlibc|'` + ;; linux*) kernel=linux os=`echo "$basic_os" | sed -e 's|linux|gnu|'` @@ -1976,6 +1981,7 @@ case $os in | atheos* \ | auroraux* \ | aux* \ + | banan_os* \ | beos* \ | bitrig* \ | bme* \ @@ -2022,7 +2028,6 @@ case $os in | ios* \ | iris* \ | irix* \ - | ironclad* \ | isc* \ | its* \ | l4re* \ @@ -2118,6 +2123,7 @@ case $os in | sysv* \ | tenex* \ | tirtos* \ + | tock* \ | toppers* \ | tops10* \ | tops20* \ @@ -2214,6 +2220,8 @@ case $kernel-$os-$obj in ;; uclinux-uclibc*- | uclinux-gnu*- ) ;; + ironclad-mlibc*-) + ;; managarm-mlibc*- | managarm-kernel*- ) ;; windows*-msvc*-) @@ -2249,6 +2257,8 @@ case $kernel-$os-$obj in ;; *-eabi*- | *-gnueabi*-) ;; + ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) + ;; none--*) # None (no kernel, i.e. freestanding / bare metal), # can be paired with an machine code file format @@ -2347,8 +2357,8 @@ echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" exit # Local variables: -# eval: (add-hook 'before-save-hook 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "timestamp='" -# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-format: "%Y-%02m-%02d" # time-stamp-end: "'" # End: diff --git a/cmake/PrivateSdlFunctions.cmake b/cmake/PrivateSdlFunctions.cmake index 1f3786c0..a0b170cb 100644 --- a/cmake/PrivateSdlFunctions.cmake +++ b/cmake/PrivateSdlFunctions.cmake @@ -278,7 +278,6 @@ endmacro() function(sdl_target_link_options_no_undefined TARGET) if(NOT MSVC AND NOT CMAKE_SYSTEM_NAME MATCHES ".*OpenBSD.*") if(CMAKE_C_COMPILER_ID MATCHES "AppleClang") - target_link_options(${TARGET} PRIVATE "-Wl,-undefined,error") else() sdl_check_linker_flag("-Wl,--no-undefined" HAVE_WL_NO_UNDEFINED) if(HAVE_WL_NO_UNDEFINED AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") AND WIN32)) diff --git a/external/flac b/external/flac index dac59a8e..86b36086 160000 --- a/external/flac +++ b/external/flac @@ -1 +1 @@ -Subproject commit dac59a8eb44f2f9f7006b1246bede041dfb18ea0 +Subproject commit 86b360863221d0f6957e01d477710759e18ecdbf diff --git a/external/libgme b/external/libgme index f7216c15..8ebb4f65 160000 --- a/external/libgme +++ b/external/libgme @@ -1 +1 @@ -Subproject commit f7216c15bda1dccdaecab56d47810802b6f0e218 +Subproject commit 8ebb4f65096fdf980b39be2283699d523ddc09af diff --git a/external/libxmp b/external/libxmp index 9857dcdd..1e4b00d6 160000 --- a/external/libxmp +++ b/external/libxmp @@ -1 +1 @@ -Subproject commit 9857dcdde27a502913d95957c23f7b4e649f8a8a +Subproject commit 1e4b00d677c217ba303ffaa4f5d4a0e6a0cbf22c diff --git a/external/mpg123 b/external/mpg123 index d010f779..e1938431 160000 --- a/external/mpg123 +++ b/external/mpg123 @@ -1 +1 @@ -Subproject commit d010f77941aac74edcd72aa711e2729c8cd9a2c4 +Subproject commit e1938431b1a9fcc62aadd67590efb6a6dabcaca3 diff --git a/external/ogg b/external/ogg index 7231a568..f35ee015 160000 --- a/external/ogg +++ b/external/ogg @@ -1 +1 @@ -Subproject commit 7231a5687231bad3eb778399ac08b690589db8d4 +Subproject commit f35ee01578587a54609946ba57d473d069854921 diff --git a/external/opus b/external/opus index 123a9faf..e6fd7f5d 160000 --- a/external/opus +++ b/external/opus @@ -1 +1 @@ -Subproject commit 123a9faf5598ceef6fcb6d44bbadc4f9469945a6 +Subproject commit e6fd7f5d1fe41d36033980efe2809d8c2cc0f320 diff --git a/external/opusfile b/external/opusfile index d820a66a..7d26ff56 160000 --- a/external/opusfile +++ b/external/opusfile @@ -1 +1 @@ -Subproject commit d820a66af7ba7fdda346b5b439baca9a2f04dd7f +Subproject commit 7d26ff56278aa6cd7f2a5046498932e838f0ea3a diff --git a/external/vorbis b/external/vorbis index 0cec9a37..ff6b9b12 160000 --- a/external/vorbis +++ b/external/vorbis @@ -1 +1 @@ -Subproject commit 0cec9a37f057a54a1e6b572301aec659d84840c3 +Subproject commit ff6b9b125fdfc04466389d48c2ddd2b3bedd4a79 diff --git a/external/wavpack b/external/wavpack index 02efabe7..112a5641 160000 --- a/external/wavpack +++ b/external/wavpack @@ -1 +1 @@ -Subproject commit 02efabe73e1ac743ec35885f2b620cec3e996ca5 +Subproject commit 112a56419d45dd4bf0f80f6ca89989c574262580 diff --git a/include/SDL_mixer.h b/include/SDL_mixer.h index 52eaf77d..2c4dd098 100644 --- a/include/SDL_mixer.h +++ b/include/SDL_mixer.h @@ -665,9 +665,11 @@ extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS(const char *file); * fly. Also, crucially, there are as many channels for chunks as the app can * allocate, but SDL_mixer only offers a single "music" channel. * - * If `freesrc` is non-zero, the RWops will be closed before returning, - * whether this function succeeds or not. SDL_mixer reads everything it needs - * from the RWops during this call in any case. + * If `freesrc` is non-zero, the RWops will be closed when SDL_mixer is done + * with it, which will be before this function call returns if there is an + * error, or perhaps much later if the music is streaming for some time. The + * app should not attempt to use the RWops again, as it may become invalid + * without warning. * * As a convenience, there is a function to read files from disk without * having to deal with SDL_RWops: `Mix_LoadMUS("filename.mp3")` will manage @@ -725,9 +727,11 @@ extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS_RW(SDL_RWops *src, int freesrc); * - `MUS_OPUS` (Opus files) * - `MUS_WAVPACK` (WavPack files) * - * If `freesrc` is non-zero, the RWops will be closed before returning, - * whether this function succeeds or not. SDL_mixer reads everything it needs - * from the RWops during this call in any case. + * If `freesrc` is non-zero, the RWops will be closed when SDL_mixer is done + * with it, which will be before this function call returns if there is an + * error, or perhaps much later if the music is streaming for some time. The + * app should not attempt to use the RWops again, as it may become invalid + * without warning. * * As a convenience, there is a function to read files from disk without * having to deal with SDL_RWops: `Mix_LoadMUS("filename.mp3")` will manage @@ -1266,7 +1270,7 @@ typedef void (SDLCALL *Mix_ChannelFinishedCallback)(int channel); extern DECLSPEC void SDLCALL Mix_ChannelFinished(Mix_ChannelFinishedCallback channel_finished); -#define MIX_CHANNEL_POST (-2) +#define MIX_CHANNEL_POST (-2) /** * This is the format of a special effect callback: @@ -2633,7 +2637,8 @@ extern DECLSPEC int SDLCALL Mix_GetSynchroValue(void); * Set SoundFonts paths to use by supported MIDI backends. * * You may specify multiple paths in a single string by separating them with - * semicolons; they will be searched in the order listed. + * semicolons; they will be searched in the _reverse_ order listed (last one + * listed will be the first one searched). * * This function replaces any previously-specified paths. * diff --git a/playmus.c b/playmus.c index cc5dc1a9..78afbba1 100644 --- a/playmus.c +++ b/playmus.c @@ -28,7 +28,7 @@ #include #include -#ifdef unix +#if defined(__unix__) || defined(__APPLE__) #include #endif diff --git a/playwave.c b/playwave.c index 26b7e101..5d13355c 100644 --- a/playwave.c +++ b/playwave.c @@ -25,7 +25,7 @@ #include #include -#ifdef unix +#if defined(__unix__) || defined(__APPLE__) #include #endif diff --git a/src/codecs/dr_libs/dr_flac.h b/src/codecs/dr_libs/dr_flac.h index 3a47251f..c95a78c3 100644 --- a/src/codecs/dr_libs/dr_flac.h +++ b/src/codecs/dr_libs/dr_flac.h @@ -1,121 +1,12 @@ /* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_flac - v0.12.43 - 2024-12-17 +dr_flac - v0.13.2 - 2025-12-02 David Reid - mackron@gmail.com GitHub: https://github.com/mackron/dr_libs */ -/* -RELEASE NOTES - v0.12.0 -======================= -Version 0.12.0 has breaking API changes including changes to the existing API and the removal of deprecated APIs. - - -Improved Client-Defined Memory Allocation ------------------------------------------ -The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The -existing system of DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE are still in place and will be used by default when no custom -allocation callbacks are specified. - -To use the new system, you pass in a pointer to a drflac_allocation_callbacks object to drflac_open() and family, like this: - - void* my_malloc(size_t sz, void* pUserData) - { - return malloc(sz); - } - void* my_realloc(void* p, size_t sz, void* pUserData) - { - return realloc(p, sz); - } - void my_free(void* p, void* pUserData) - { - free(p); - } - - ... - - drflac_allocation_callbacks allocationCallbacks; - allocationCallbacks.pUserData = &myData; - allocationCallbacks.onMalloc = my_malloc; - allocationCallbacks.onRealloc = my_realloc; - allocationCallbacks.onFree = my_free; - drflac* pFlac = drflac_open_file("my_file.flac", &allocationCallbacks); - -The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. - -Passing in null for the allocation callbacks object will cause dr_flac to use defaults which is the same as DRFLAC_MALLOC, -DRFLAC_REALLOC and DRFLAC_FREE and the equivalent of how it worked in previous versions. - -Every API that opens a drflac object now takes this extra parameter. These include the following: - - drflac_open() - drflac_open_relaxed() - drflac_open_with_metadata() - drflac_open_with_metadata_relaxed() - drflac_open_file() - drflac_open_file_with_metadata() - drflac_open_memory() - drflac_open_memory_with_metadata() - drflac_open_and_read_pcm_frames_s32() - drflac_open_and_read_pcm_frames_s16() - drflac_open_and_read_pcm_frames_f32() - drflac_open_file_and_read_pcm_frames_s32() - drflac_open_file_and_read_pcm_frames_s16() - drflac_open_file_and_read_pcm_frames_f32() - drflac_open_memory_and_read_pcm_frames_s32() - drflac_open_memory_and_read_pcm_frames_s16() - drflac_open_memory_and_read_pcm_frames_f32() - - - -Optimizations -------------- -Seeking performance has been greatly improved. A new binary search based seeking algorithm has been introduced which significantly -improves performance over the brute force method which was used when no seek table was present. Seek table based seeking also takes -advantage of the new binary search seeking system to further improve performance there as well. Note that this depends on CRC which -means it will be disabled when DR_FLAC_NO_CRC is used. - -The SSE4.1 pipeline has been cleaned up and optimized. You should see some improvements with decoding speed of 24-bit files in -particular. 16-bit streams should also see some improvement. - -drflac_read_pcm_frames_s16() has been optimized. Previously this sat on top of drflac_read_pcm_frames_s32() and performed it's s32 -to s16 conversion in a second pass. This is now all done in a single pass. This includes SSE2 and ARM NEON optimized paths. - -A minor optimization has been implemented for drflac_read_pcm_frames_s32(). This will now use an SSE2 optimized pipeline for stereo -channel reconstruction which is the last part of the decoding process. - -The ARM build has seen a few improvements. The CLZ (count leading zeroes) and REV (byte swap) instructions are now used when -compiling with GCC and Clang which is achieved using inline assembly. The CLZ instruction requires ARM architecture version 5 at -compile time and the REV instruction requires ARM architecture version 6. - -An ARM NEON optimized pipeline has been implemented. To enable this you'll need to add -mfpu=neon to the command line when compiling. - - -Removed APIs ------------- -The following APIs were deprecated in version 0.11.0 and have been completely removed in version 0.12.0: - - drflac_read_s32() -> drflac_read_pcm_frames_s32() - drflac_read_s16() -> drflac_read_pcm_frames_s16() - drflac_read_f32() -> drflac_read_pcm_frames_f32() - drflac_seek_to_sample() -> drflac_seek_to_pcm_frame() - drflac_open_and_decode_s32() -> drflac_open_and_read_pcm_frames_s32() - drflac_open_and_decode_s16() -> drflac_open_and_read_pcm_frames_s16() - drflac_open_and_decode_f32() -> drflac_open_and_read_pcm_frames_f32() - drflac_open_and_decode_file_s32() -> drflac_open_file_and_read_pcm_frames_s32() - drflac_open_and_decode_file_s16() -> drflac_open_file_and_read_pcm_frames_s16() - drflac_open_and_decode_file_f32() -> drflac_open_file_and_read_pcm_frames_f32() - drflac_open_and_decode_memory_s32() -> drflac_open_memory_and_read_pcm_frames_s32() - drflac_open_and_decode_memory_s16() -> drflac_open_memory_and_read_pcm_frames_s16() - drflac_open_and_decode_memory_f32() -> drflac_open_memroy_and_read_pcm_frames_f32() - -Prior versions of dr_flac operated on a per-sample basis whereas now it operates on PCM frames. The removed APIs all relate -to the old per-sample APIs. You now need to use the "pcm_frame" versions. -*/ - - /* Introduction ============ @@ -234,8 +125,8 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 -#define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 43 +#define DRFLAC_VERSION_MINOR 13 +#define DRFLAC_VERSION_REVISION 2 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include /* For size_t. */ @@ -406,8 +297,9 @@ typedef enum typedef enum { - drflac_seek_origin_start, - drflac_seek_origin_current + DRFLAC_SEEK_SET, + DRFLAC_SEEK_CUR, + DRFLAC_SEEK_END } drflac_seek_origin; /* The order of members in this structure is important because we map this directly to the raw data within the SEEKTABLE metadata block. */ @@ -439,6 +331,12 @@ typedef struct */ drflac_uint32 type; + /* The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. */ + drflac_uint32 rawDataSize; + + /* The offset in the stream of the raw data. */ + drflac_uint64 rawDataOffset; + /* A pointer to the raw data. This points to a temporary buffer so don't hold on to it. It's best to not modify the contents of this buffer. Use the structures below for more meaningful and structured @@ -446,9 +344,6 @@ typedef struct */ const void* pRawData; - /* The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. */ - drflac_uint32 rawDataSize; - union { drflac_streaminfo streaminfo; @@ -500,6 +395,7 @@ typedef struct drflac_uint32 colorDepth; drflac_uint32 indexColorCount; drflac_uint32 pictureDataSize; + drflac_uint64 pictureDataOffset; /* Offset from the start of the stream. */ const drflac_uint8* pPictureData; } picture; } data; @@ -547,7 +443,7 @@ offset (in) The number of bytes to move, relative to the origin. Will never be negative. origin (in) - The origin of the seek - the current position or the start of the stream. + The origin of the seek - the current position, the start of the stream, or the end of the stream. Return Value @@ -557,14 +453,32 @@ Whether or not the seek was successful. Remarks ------- -The offset will never be negative. Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be -either drflac_seek_origin_start or drflac_seek_origin_current. +Seeking relative to the start and the current position must always be supported. If seeking from the end of the stream is not supported, return DRFLAC_FALSE. When seeking to a PCM frame using drflac_seek_to_pcm_frame(), dr_flac may call this with an offset beyond the end of the FLAC stream. This needs to be detected and handled by returning DRFLAC_FALSE. */ typedef drflac_bool32 (* drflac_seek_proc)(void* pUserData, int offset, drflac_seek_origin origin); +/* +Callback for when the current position in the stream needs to be retrieved. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pCursor (out) + A pointer to a variable to receive the current position in the stream. + + +Return Value +------------ +Whether or not the operation was successful. +*/ +typedef drflac_bool32 (* drflac_tell_proc)(void* pUserData, drflac_int64* pCursor); + /* Callback for when a metadata block is read. @@ -603,6 +517,9 @@ typedef struct /* The function to call when the current read position needs to be moved. */ drflac_seek_proc onSeek; + /* The function to call when the current read position needs to be retrieved. */ + drflac_tell_proc onTell; + /* The user data to pass around to onRead and onSeek. */ void* pUserData; @@ -828,7 +745,7 @@ drflac_open_memory() drflac_open_with_metadata() drflac_close() */ -DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC stream with relaxed validation of the header block. @@ -869,7 +786,7 @@ force your `onRead` callback to return 0, which dr_flac will use as an indicator Use `drflac_open_with_metadata_relaxed()` if you need access to metadata. */ -DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder and notifies the caller of the metadata chunks (album art, etc.). @@ -926,7 +843,7 @@ drflac_open_memory_with_metadata() drflac_open() drflac_close() */ -DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* The same as drflac_open_with_metadata(), except attempts to open the stream even when a header block is not present. @@ -936,7 +853,7 @@ See Also drflac_open_with_metadata() drflac_open_relaxed() */ -DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Closes the given FLAC decoder. @@ -1234,13 +1151,13 @@ read samples into a dynamically sized buffer on the heap until no samples are le Do not call this function on a broadcast type of stream (like internet radio streams and whatnot). */ -DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ -DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ -DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); #ifndef DR_FLAC_NO_STDIO /* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a file. */ @@ -2815,7 +2732,7 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) return r; } - #elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */ + #elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !(defined(__thumb__) && !defined(__thumb2__)) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */ { unsigned int r; __asm__ __volatile__ ( @@ -2960,25 +2877,25 @@ static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFro */ if (offsetFromStart > 0x7FFFFFFF) { drflac_uint64 bytesRemaining = offsetFromStart; - if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } bytesRemaining -= 0x7FFFFFFF; while (bytesRemaining > 0x7FFFFFFF) { - if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } bytesRemaining -= 0x7FFFFFFF; } if (bytesRemaining > 0) { - if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, drflac_seek_origin_current)) { + if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } } else { - if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, drflac_seek_origin_start)) { + if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } } @@ -6330,6 +6247,7 @@ typedef struct { drflac_read_proc onRead; drflac_seek_proc onSeek; + drflac_tell_proc onTell; drflac_meta_proc onMeta; drflac_container container; void* pUserData; @@ -6497,7 +6415,7 @@ static void drflac__free_from_callbacks(void* p, const drflac_allocation_callbac } -static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks) +static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks) { /* We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that @@ -6507,6 +6425,8 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d drflac_uint64 seektablePos = 0; drflac_uint32 seektableSize = 0; + (void)onTell; + for (;;) { drflac_metadata metadata; drflac_uint8 isLastBlock = 0; @@ -6518,8 +6438,9 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d runningFilePos += 4; metadata.type = blockType; - metadata.pRawData = NULL; metadata.rawDataSize = 0; + metadata.rawDataOffset = runningFilePos; + metadata.pRawData = NULL; switch (blockType) { @@ -6796,59 +6717,149 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d } if (onMeta) { - void* pRawData; - const char* pRunningData; - const char* pRunningDataEnd; + drflac_bool32 result = DRFLAC_TRUE; + drflac_uint32 blockSizeRemaining = blockSize; + char* pMime = NULL; + char* pDescription = NULL; + void* pPictureData = NULL; + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.type, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.type = drflac__be2host_32(metadata.data.picture.type); - pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); - if (pRawData == NULL) { - return DRFLAC_FALSE; + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.mimeLength, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; } + blockSizeRemaining -= 4; + metadata.data.picture.mimeLength = drflac__be2host_32(metadata.data.picture.mimeLength); - if (onRead(pUserData, pRawData, blockSize) != blockSize) { - drflac__free_from_callbacks(pRawData, pAllocationCallbacks); - return DRFLAC_FALSE; + pMime = (char*)drflac__malloc_from_callbacks(metadata.data.picture.mimeLength + 1, pAllocationCallbacks); /* +1 for null terminator. */ + if (pMime == NULL) { + result = DRFLAC_FALSE; + goto done_flac; } - metadata.pRawData = pRawData; - metadata.rawDataSize = blockSize; + if (blockSizeRemaining < metadata.data.picture.mimeLength || onRead(pUserData, pMime, metadata.data.picture.mimeLength) != metadata.data.picture.mimeLength) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= metadata.data.picture.mimeLength; + pMime[metadata.data.picture.mimeLength] = '\0'; /* Null terminate for safety. */ + metadata.data.picture.mime = (const char*)pMime; - pRunningData = (const char*)pRawData; - pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.picture.type = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.descriptionLength, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.descriptionLength = drflac__be2host_32(metadata.data.picture.descriptionLength); - /* Need space for the rest of the block */ - if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ - drflac__free_from_callbacks(pRawData, pAllocationCallbacks); - return DRFLAC_FALSE; + pDescription = (char*)drflac__malloc_from_callbacks(metadata.data.picture.descriptionLength + 1, pAllocationCallbacks); /* +1 for null terminator. */ + if (pDescription == NULL) { + result = DRFLAC_FALSE; + goto done_flac; } - metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; - metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - /* Need space for the rest of the block */ - if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ - drflac__free_from_callbacks(pRawData, pAllocationCallbacks); - return DRFLAC_FALSE; + if (blockSizeRemaining < metadata.data.picture.descriptionLength || onRead(pUserData, pDescription, metadata.data.picture.descriptionLength) != metadata.data.picture.descriptionLength) { + result = DRFLAC_FALSE; + goto done_flac; } - metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; - metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; - - /* Need space for the picture after the block */ - if (pRunningDataEnd - pRunningData < (drflac_int64)metadata.data.picture.pictureDataSize) { /* <-- Note the order of operations to avoid overflow to a valid value */ - drflac__free_from_callbacks(pRawData, pAllocationCallbacks); - return DRFLAC_FALSE; + blockSizeRemaining -= metadata.data.picture.descriptionLength; + pDescription[metadata.data.picture.descriptionLength] = '\0'; /* Null terminate for safety. */ + metadata.data.picture.description = (const char*)pDescription; + + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.width, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; } + blockSizeRemaining -= 4; + metadata.data.picture.width = drflac__be2host_32(metadata.data.picture.width); - onMeta(pUserDataMD, &metadata); + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.height, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.height = drflac__be2host_32(metadata.data.picture.height); - drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.colorDepth, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.colorDepth = drflac__be2host_32(metadata.data.picture.colorDepth); + + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.indexColorCount, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.indexColorCount = drflac__be2host_32(metadata.data.picture.indexColorCount); + + + /* Picture data. */ + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.pictureDataSize, 4) != 4) { + result = DRFLAC_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32(metadata.data.picture.pictureDataSize); + + if (blockSizeRemaining < metadata.data.picture.pictureDataSize) { + result = DRFLAC_FALSE; + goto done_flac; + } + + /* For the actual image data we want to store the offset to the start of the stream. */ + metadata.data.picture.pictureDataOffset = runningFilePos + (blockSize - blockSizeRemaining); + + /* + For the allocation of image data, we can allow memory allocation to fail, in which case we just leave + the pointer as null. If it fails, we need to fall back to seeking past the image data. + */ + #ifndef DR_FLAC_NO_PICTURE_METADATA_MALLOC + pPictureData = drflac__malloc_from_callbacks(metadata.data.picture.pictureDataSize, pAllocationCallbacks); + if (pPictureData != NULL) { + if (onRead(pUserData, pPictureData, metadata.data.picture.pictureDataSize) != metadata.data.picture.pictureDataSize) { + result = DRFLAC_FALSE; + goto done_flac; + } + } else + #endif + { + /* Allocation failed. We need to seek past the picture data. */ + if (!onSeek(pUserData, metadata.data.picture.pictureDataSize, DRFLAC_SEEK_CUR)) { + result = DRFLAC_FALSE; + goto done_flac; + } + } + + blockSizeRemaining -= metadata.data.picture.pictureDataSize; + metadata.data.picture.pPictureData = (const drflac_uint8*)pPictureData; + + + /* Only fire the callback if we actually have a way to read the image data. We must have either a valid offset, or a valid data pointer. */ + if (metadata.data.picture.pictureDataOffset != 0 || metadata.data.picture.pPictureData != NULL) { + onMeta(pUserDataMD, &metadata); + } else { + /* Don't have a valid offset or data pointer, so just pretend we don't have a picture metadata. */ + } + + done_flac: + drflac__free_from_callbacks(pMime, pAllocationCallbacks); + drflac__free_from_callbacks(pDescription, pAllocationCallbacks); + drflac__free_from_callbacks(pPictureData, pAllocationCallbacks); + + if (result != DRFLAC_TRUE) { + return DRFLAC_FALSE; + } } } break; @@ -6858,7 +6869,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d metadata.data.padding.unused = 0; /* Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. */ - if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } else { onMeta(pUserDataMD, &metadata); @@ -6870,7 +6881,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d { /* Invalid chunk. Just skip over this one. */ if (onMeta) { - if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } } @@ -6884,13 +6895,16 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d */ if (onMeta) { void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); - if (pRawData == NULL) { - return DRFLAC_FALSE; - } - - if (onRead(pUserData, pRawData, blockSize) != blockSize) { - drflac__free_from_callbacks(pRawData, pAllocationCallbacks); - return DRFLAC_FALSE; + if (pRawData != NULL) { + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + } else { + /* Allocation failed. We need to seek past the block. */ + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { + return DRFLAC_FALSE; + } } metadata.pRawData = pRawData; @@ -6904,7 +6918,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d /* If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. */ if (onMeta == NULL && blockSize > 0) { - if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, blockSize, DRFLAC_SEEK_CUR)) { isLastBlock = DRFLAC_TRUE; } } @@ -7238,6 +7252,7 @@ typedef struct { drflac_read_proc onRead; /* The original onRead callback from drflac_open() and family. */ drflac_seek_proc onSeek; /* The original onSeek callback from drflac_open() and family. */ + drflac_tell_proc onTell; /* The original onTell callback from drflac_open() and family. */ void* pUserData; /* The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. */ drflac_uint64 currentBytePos; /* The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. */ drflac_uint64 firstBytePos; /* The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. */ @@ -7259,32 +7274,32 @@ static size_t drflac_oggbs__read_physical(drflac_oggbs* oggbs, void* bufferOut, static drflac_bool32 drflac_oggbs__seek_physical(drflac_oggbs* oggbs, drflac_uint64 offset, drflac_seek_origin origin) { - if (origin == drflac_seek_origin_start) { + if (origin == DRFLAC_SEEK_SET) { if (offset <= 0x7FFFFFFF) { - if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_start)) { + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } oggbs->currentBytePos = offset; return DRFLAC_TRUE; } else { - if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } oggbs->currentBytePos = offset; - return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, drflac_seek_origin_current); + return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, DRFLAC_SEEK_CUR); } } else { while (offset > 0x7FFFFFFF) { - if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } oggbs->currentBytePos += 0x7FFFFFFF; offset -= 0x7FFFFFFF; } - if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_current)) { /* <-- Safe cast thanks to the loop above. */ + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, DRFLAC_SEEK_CUR)) { /* <-- Safe cast thanks to the loop above. */ return DRFLAC_FALSE; } oggbs->currentBytePos += offset; @@ -7316,7 +7331,7 @@ static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_og if (header.serialNumber != oggbs->serialNumber) { /* It's not a FLAC page. Skip it. */ - if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, drflac_seek_origin_current)) { + if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } continue; @@ -7402,7 +7417,7 @@ static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) At this point we will have found either the packet or the end of the page. If were at the end of the page we'll want to load the next page and keep searching for the end of the packet. */ - drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, drflac_seek_origin_current); + drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, DRFLAC_SEEK_CUR); oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; if (atEndOfPage) { @@ -7480,8 +7495,8 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ /* Seeking is always forward which makes things a lot simpler. */ - if (origin == drflac_seek_origin_start) { - if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, drflac_seek_origin_start)) { + if (origin == DRFLAC_SEEK_SET) { + if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } @@ -7489,38 +7504,50 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see return DRFLAC_FALSE; } - return drflac__on_seek_ogg(pUserData, offset, drflac_seek_origin_current); - } + return drflac__on_seek_ogg(pUserData, offset, DRFLAC_SEEK_CUR); + } else if (origin == DRFLAC_SEEK_CUR) { + while (bytesSeeked < offset) { + int bytesRemainingToSeek = offset - bytesSeeked; + DRFLAC_ASSERT(bytesRemainingToSeek >= 0); - DRFLAC_ASSERT(origin == drflac_seek_origin_current); - - while (bytesSeeked < offset) { - int bytesRemainingToSeek = offset - bytesSeeked; - DRFLAC_ASSERT(bytesRemainingToSeek >= 0); - - if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { - bytesSeeked += bytesRemainingToSeek; - (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ - oggbs->bytesRemainingInPage -= bytesRemainingToSeek; - break; - } + if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { + bytesSeeked += bytesRemainingToSeek; + (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ + oggbs->bytesRemainingInPage -= bytesRemainingToSeek; + break; + } - /* If we get here it means some of the requested data is contained in the next pages. */ - if (oggbs->bytesRemainingInPage > 0) { - bytesSeeked += (int)oggbs->bytesRemainingInPage; - oggbs->bytesRemainingInPage = 0; - } + /* If we get here it means some of the requested data is contained in the next pages. */ + if (oggbs->bytesRemainingInPage > 0) { + bytesSeeked += (int)oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } - DRFLAC_ASSERT(bytesRemainingToSeek > 0); - if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { - /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ - return DRFLAC_FALSE; + DRFLAC_ASSERT(bytesRemainingToSeek > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ + return DRFLAC_FALSE; + } } + } else if (origin == DRFLAC_SEEK_END) { + /* Seeking to the end is not supported. */ + return DRFLAC_FALSE; } return DRFLAC_TRUE; } +static drflac_bool32 drflac__on_tell_ogg(void* pUserData, drflac_int64* pCursor) +{ + /* + Not implemented for Ogg containers because we don't currently track the byte position of the logical bitstream. To support this, we'll need + to track the position in drflac__on_read_ogg and drflac__on_seek_ogg. + */ + (void)pUserData; + (void)pCursor; + return DRFLAC_FALSE; +} + static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) { @@ -7543,7 +7570,7 @@ static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 runningGranulePosition = 0; for (;;) { if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { - drflac_oggbs__seek_physical(oggbs, originalBytePos, drflac_seek_origin_start); + drflac_oggbs__seek_physical(oggbs, originalBytePos, DRFLAC_SEEK_SET); return DRFLAC_FALSE; /* Never did find that sample... */ } @@ -7577,7 +7604,7 @@ static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until we find the one containing the target sample. */ - if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, drflac_seek_origin_start)) { + if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, DRFLAC_SEEK_SET)) { return DRFLAC_FALSE; } if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { @@ -7744,7 +7771,7 @@ static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_r The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to be handling it in a generic way based on the serial number and packet types. */ - if (!onSeek(pUserData, 2, drflac_seek_origin_current)) { + if (!onSeek(pUserData, 2, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } @@ -7801,18 +7828,18 @@ static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_r } } else { /* Not a FLAC header. Skip it. */ - if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + if (!onSeek(pUserData, bytesRemainingInPage, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } } else { /* Not a FLAC header. Seek past the entire page and move on to the next. */ - if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + if (!onSeek(pUserData, bytesRemainingInPage, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } } else { - if (!onSeek(pUserData, pageBodySize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, pageBodySize, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; } } @@ -7837,18 +7864,19 @@ static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_r } #endif -static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) +static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) { drflac_bool32 relaxed; drflac_uint8 id[4]; - if (pInit == NULL || onRead == NULL || onSeek == NULL) { + if (pInit == NULL || onRead == NULL || onSeek == NULL) { /* <-- onTell is optional. */ return DRFLAC_FALSE; } DRFLAC_ZERO_MEMORY(pInit, sizeof(*pInit)); pInit->onRead = onRead; pInit->onSeek = onSeek; + pInit->onTell = onTell; pInit->onMeta = onMeta; pInit->container = container; pInit->pUserData = pUserData; @@ -7856,6 +7884,7 @@ static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_p pInit->bs.onRead = onRead; pInit->bs.onSeek = onSeek; + pInit->bs.onTell = onTell; pInit->bs.pUserData = pUserData; drflac__reset_cache(&pInit->bs); @@ -7888,7 +7917,7 @@ static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_p headerSize += 10; } - if (!onSeek(pUserData, headerSize, drflac_seek_origin_current)) { + if (!onSeek(pUserData, headerSize, DRFLAC_SEEK_CUR)) { return DRFLAC_FALSE; /* Failed to seek past the tag. */ } pInit->runningFilePos += headerSize; @@ -7940,7 +7969,7 @@ static void drflac__init_from_info(drflac* pFlac, const drflac_init_info* pInit) } -static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) +static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac_init_info init; drflac_uint32 allocationSize; @@ -7958,7 +7987,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac /* CPU support first. */ drflac__init_cpu_caps(); - if (!drflac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { + if (!drflac__init_private(&init, onRead, onSeek, onTell, onMeta, container, pUserData, pUserDataMD)) { return NULL; } @@ -8014,6 +8043,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac DRFLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs)); pOggbs->onRead = onRead; pOggbs->onSeek = onSeek; + pOggbs->onTell = onTell; pOggbs->pUserData = pUserData; pOggbs->currentBytePos = init.oggFirstBytePos; pOggbs->firstBytePos = init.oggFirstBytePos; @@ -8034,17 +8064,19 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac if (init.hasMetadataBlocks) { drflac_read_proc onReadOverride = onRead; drflac_seek_proc onSeekOverride = onSeek; + drflac_tell_proc onTellOverride = onTell; void* pUserDataOverride = pUserData; #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { onReadOverride = drflac__on_read_ogg; onSeekOverride = drflac__on_seek_ogg; + onTellOverride = drflac__on_tell_ogg; pUserDataOverride = (void*)pOggbs; } #endif - if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { + if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onTellOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { #ifndef DR_FLAC_NO_OGG drflac__free_from_callbacks(pOggbs, &allocationCallbacks); #endif @@ -8079,6 +8111,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac /* The Ogg bistream needs to be layered on top of the original bitstream. */ pFlac->bs.onRead = drflac__on_read_ogg; pFlac->bs.onSeek = drflac__on_seek_ogg; + pFlac->bs.onTell = drflac__on_tell_ogg; pFlac->bs.pUserData = (void*)pInternalOggbs; pFlac->_oggbs = (void*)pInternalOggbs; } @@ -8105,7 +8138,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac DRFLAC_ASSERT(pFlac->bs.onRead != NULL); /* Seek to the seektable, then just read directly into our seektable buffer. */ - if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) { + if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, DRFLAC_SEEK_SET)) { drflac_uint32 iSeekpoint; for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) { @@ -8123,7 +8156,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac } /* We need to seek back to where we were. If this fails it's a critical error. */ - if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, drflac_seek_origin_start)) { + if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, DRFLAC_SEEK_SET)) { drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } @@ -8294,7 +8327,7 @@ static drflac_result drflac_result_from_errno(int e) #ifdef ENOSYS case ENOSYS: return DRFLAC_NOT_IMPLEMENTED; #endif - #ifdef ENOTEMPTY + #if defined(ENOTEMPTY) && ENOTEMPTY != EEXIST /* In AIX, ENOTEMPTY and EEXIST use the same value. */ case ENOTEMPTY: return DRFLAC_DIRECTORY_NOT_EMPTY; #endif #ifdef ELOOP @@ -8745,12 +8778,42 @@ static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t byt static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) { - DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ + int whence = SEEK_SET; + if (origin == DRFLAC_SEEK_CUR) { + whence = SEEK_CUR; + } else if (origin == DRFLAC_SEEK_END) { + whence = SEEK_END; + } + + return fseek((FILE*)pUserData, offset, whence) == 0; +} + +static drflac_bool32 drflac__on_tell_stdio(void* pUserData, drflac_int64* pCursor) +{ + FILE* pFileStdio = (FILE*)pUserData; + drflac_int64 result; + + /* These were all validated at a higher level. */ + DRFLAC_ASSERT(pFileStdio != NULL); + DRFLAC_ASSERT(pCursor != NULL); + +#if defined(_WIN32) && !defined(NXDK) + #if defined(_MSC_VER) && _MSC_VER > 1200 + result = _ftelli64(pFileStdio); + #else + result = ftell(pFileStdio); + #endif +#else + result = ftell(pFileStdio); +#endif + + *pCursor = result; - return fseek((FILE*)pUserData, offset, (origin == drflac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + return DRFLAC_TRUE; } + DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8760,7 +8823,7 @@ DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocati return NULL; } - pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; @@ -8779,7 +8842,7 @@ DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_all return NULL; } - pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; @@ -8798,7 +8861,7 @@ DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_ return NULL; } - pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; @@ -8817,7 +8880,7 @@ DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, dr return NULL; } - pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, drflac__on_tell_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; @@ -8852,28 +8915,43 @@ static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t by static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_seek_origin origin) { drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + drflac_int64 newCursor; DRFLAC_ASSERT(memoryStream != NULL); - DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ - if (offset > (drflac_int64)memoryStream->dataSize) { + if (origin == DRFLAC_SEEK_SET) { + newCursor = 0; + } else if (origin == DRFLAC_SEEK_CUR) { + newCursor = (drflac_int64)memoryStream->currentReadPos; + } else if (origin == DRFLAC_SEEK_END) { + newCursor = (drflac_int64)memoryStream->dataSize; + } else { + DRFLAC_ASSERT(!"Invalid seek origin"); return DRFLAC_FALSE; } - if (origin == drflac_seek_origin_current) { - if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { - memoryStream->currentReadPos += offset; - } else { - return DRFLAC_FALSE; /* Trying to seek too far forward. */ - } - } else { - if ((drflac_uint32)offset <= memoryStream->dataSize) { - memoryStream->currentReadPos = offset; - } else { - return DRFLAC_FALSE; /* Trying to seek too far forward. */ - } + newCursor += offset; + + if (newCursor < 0) { + return DRFLAC_FALSE; /* Trying to seek prior to the start of the buffer. */ } + if ((size_t)newCursor > memoryStream->dataSize) { + return DRFLAC_FALSE; /* Trying to seek beyond the end of the buffer. */ + } + + memoryStream->currentReadPos = (size_t)newCursor; + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__on_tell_memory(void* pUserData, drflac_int64* pCursor) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(pCursor != NULL); + *pCursor = (drflac_int64)memoryStream->currentReadPos; return DRFLAC_TRUE; } @@ -8885,7 +8963,7 @@ DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream, pAllocationCallbacks); + pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, drflac__on_tell_memory, &memoryStream, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8916,7 +8994,7 @@ DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t da memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); + pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, drflac__on_tell_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8941,22 +9019,22 @@ DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t da -DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, NULL, container, pUserData, pUserData, pAllocationCallbacks); } -DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData, pAllocationCallbacks); + return drflac_open_with_metadata_private(onRead, onSeek, onTell, onMeta, container, pUserData, pUserData, pAllocationCallbacks); } DRFLAC_API void drflac_close(drflac* pFlac) @@ -11720,58 +11798,43 @@ static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned { \ type* pSampleData = NULL; \ drflac_uint64 totalPCMFrameCount; \ + type buffer[4096]; \ + drflac_uint64 pcmFramesRead; \ + size_t sampleDataBufferSize = sizeof(buffer); \ \ DRFLAC_ASSERT(pFlac != NULL); \ \ - totalPCMFrameCount = pFlac->totalPCMFrameCount; \ - \ - if (totalPCMFrameCount == 0) { \ - type buffer[4096]; \ - drflac_uint64 pcmFramesRead; \ - size_t sampleDataBufferSize = sizeof(buffer); \ - \ - pSampleData = (type*)drflac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ - if (pSampleData == NULL) { \ - goto on_error; \ - } \ + totalPCMFrameCount = 0; \ \ - while ((pcmFramesRead = (drflac_uint64)drflac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ - if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ - type* pNewSampleData; \ - size_t newSampleDataBufferSize; \ + pSampleData = (type*)drflac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pSampleData == NULL) { \ + goto on_error; \ + } \ \ - newSampleDataBufferSize = sampleDataBufferSize * 2; \ - pNewSampleData = (type*)drflac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ - if (pNewSampleData == NULL) { \ - drflac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ - goto on_error; \ - } \ + while ((pcmFramesRead = (drflac_uint64)drflac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ + if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ + type* pNewSampleData; \ + size_t newSampleDataBufferSize; \ \ - sampleDataBufferSize = newSampleDataBufferSize; \ - pSampleData = pNewSampleData; \ + newSampleDataBufferSize = sampleDataBufferSize * 2; \ + pNewSampleData = (type*)drflac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pNewSampleData == NULL) { \ + drflac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ + goto on_error; \ } \ \ - DRFLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ - totalPCMFrameCount += pcmFramesRead; \ - } \ - \ - /* At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to \ - protect those ears from random noise! */ \ - DRFLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ - } else { \ - drflac_uint64 dataSize = totalPCMFrameCount*pFlac->channels*sizeof(type); \ - if (dataSize > (drflac_uint64)DRFLAC_SIZE_MAX) { \ - goto on_error; /* The decoded data is too big. */ \ - } \ - \ - pSampleData = (type*)drflac__malloc_from_callbacks((size_t)dataSize, &pFlac->allocationCallbacks); /* <-- Safe cast as per the check above. */ \ - if (pSampleData == NULL) { \ - goto on_error; \ + sampleDataBufferSize = newSampleDataBufferSize; \ + pSampleData = pNewSampleData; \ } \ \ - totalPCMFrameCount = drflac_read_pcm_frames_##extension(pFlac, pFlac->totalPCMFrameCount, pSampleData); \ + DRFLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ + totalPCMFrameCount += pcmFramesRead; \ } \ \ + /* At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to \ + protect those ears from random noise! */ \ + DRFLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ + \ if (sampleRateOut) *sampleRateOut = pFlac->sampleRate; \ if (channelsOut) *channelsOut = pFlac->channels; \ if (totalPCMFrameCountOut) *totalPCMFrameCountOut = totalPCMFrameCount; \ @@ -11788,7 +11851,7 @@ DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s32, drflac_int32) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s16, drflac_int16) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) -DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -11802,7 +11865,7 @@ DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc on *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -11810,7 +11873,7 @@ DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc on return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -11824,7 +11887,7 @@ DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc on *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -11832,7 +11895,7 @@ DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc on return drflac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_tell_proc onTell, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -11846,7 +11909,7 @@ DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, d *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + pFlac = drflac_open(onRead, onSeek, onTell, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -12095,6 +12158,30 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat /* REVISION HISTORY ================ +v0.13.2 - 2025-12-02 + - Improve robustness of the parsing of picture metadata to improve support for memory constrained embedded devices. + - Fix a warning about an assigned by unused variable. + - Improvements to drflac_open_and_read_pcm_frames_*() and family to avoid excessively large memory allocations from malformed files. + +v0.13.1 - 2025-09-10 + - Fix an error with the NXDK build. + +v0.13.0 - 2025-07-23 + - API CHANGE: Seek origin enums have been renamed to match the naming convention used by other dr_libs libraries: + - drflac_seek_origin_start -> DRFLAC_SEEK_SET + - drflac_seek_origin_current -> DRFLAC_SEEK_CUR + - DRFLAC_SEEK_END (new) + - API CHANGE: A new seek origin has been added to allow seeking from the end of the file. If you implement your own `onSeek` callback, you should now detect and handle `DRFLAC_SEEK_END`. If seeking to the end is not supported, return `DRFLAC_FALSE`. If you only use `*_open_file()` or `*_open_memory()`, you need not change anything. + - API CHANGE: An `onTell` callback has been added to the following functions: + - drflac_open() + - drflac_open_relaxed() + - drflac_open_with_metadata() + - drflac_open_with_metadata_relaxed() + - drflac_open_and_read_pcm_frames_s32() + - drflac_open_and_read_pcm_frames_s16() + - drflac_open_and_read_pcm_frames_f32() + - Fix compilation for AIX OS. + v0.12.43 - 2024-12-17 - Fix a possible buffer overflow during decoding. - Improve detection of ARM64EC diff --git a/src/codecs/minimp3/minimp3.h b/src/codecs/minimp3/minimp3.h index 3220ae1a..5442f591 100644 --- a/src/codecs/minimp3/minimp3.h +++ b/src/codecs/minimp3/minimp3.h @@ -651,6 +651,10 @@ static float L3_ldexp_q2(float y, int exp_q2) return y; } +#if (defined(__GNUC__) && (__GNUC__ >= 14)) && !defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif static void L3_decode_scalefactors(const uint8_t *hdr, uint8_t *ist_pos, bs_t *bs, const L3_gr_info_t *gr, float *scf, int ch) { static const uint8_t g_scf_partitions[3][28] = { @@ -712,6 +716,9 @@ static void L3_decode_scalefactors(const uint8_t *hdr, uint8_t *ist_pos, bs_t *b scf[i] = L3_ldexp_q2(gain, iscf[i] << scf_shift); } } +#if (defined(__GNUC__) && (__GNUC__ >= 14)) && !defined(__clang__) + #pragma GCC diagnostic pop +#endif static const float g_pow43[129 + 16] = { 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, diff --git a/src/codecs/music_drflac.c b/src/codecs/music_drflac.c index 2999188b..5e793e37 100644 --- a/src/codecs/music_drflac.c +++ b/src/codecs/music_drflac.c @@ -77,13 +77,33 @@ static size_t DRFLAC_ReadCB(void *context, void *buf, size_t size) static drflac_bool32 DRFLAC_SeekCB(void *context, int offset, drflac_seek_origin origin) { DRFLAC_Music *music = (DRFLAC_Music *)context; - int whence = (origin == drflac_seek_origin_start) ? RW_SEEK_SET : RW_SEEK_CUR; + int whence; + switch (origin) { + case DRFLAC_SEEK_SET: + whence = RW_SEEK_SET; + break; + case DRFLAC_SEEK_CUR: + whence = RW_SEEK_CUR; + break; + case DRFLAC_SEEK_END: + whence = RW_SEEK_END; + break; + default: + return DRFLAC_FALSE; + } if (MP3_RWseek(&music->file, offset, whence) < 0) { return DRFLAC_FALSE; } return DRFLAC_TRUE; } +static drflac_bool32 DRFLAC_TellCB(void *context, drflac_int64 *pos) +{ + DRFLAC_Music *music = (DRFLAC_Music *)context; + *pos = MP3_RWtell(&music->file); + return (*pos < 0) ? DRFLAC_FALSE : DRFLAC_TRUE; +} + static void DRFLAC_MetaCB(void *context, drflac_metadata *metadata) { DRFLAC_Music *music = (DRFLAC_Music *)context; @@ -176,7 +196,7 @@ static void *DRFLAC_CreateFromRW(SDL_RWops *src, int freesrc) meta_tags_init(&music->tags); - music->dec = drflac_open_with_metadata(DRFLAC_ReadCB, DRFLAC_SeekCB, DRFLAC_MetaCB, music, NULL); + music->dec = drflac_open_with_metadata(DRFLAC_ReadCB, DRFLAC_SeekCB, DRFLAC_TellCB, DRFLAC_MetaCB, music, NULL); if (!music->dec) { SDL_free(music); Mix_SetError("music_drflac: corrupt flac file (bad stream)."); diff --git a/src/codecs/music_mpg123.c b/src/codecs/music_mpg123.c index 4457250e..b6ee2994 100644 --- a/src/codecs/music_mpg123.c +++ b/src/codecs/music_mpg123.c @@ -29,8 +29,11 @@ #include "music_mpg123.h" #include "mp3utils.h" +/* Not defining MPG123_PORTABLE_API here, because mpg123 v1.32.x, + * i.e. MPG123_API_VERSION 48, doesn't have mpg123_open_handle64() + */ +/* #define MPG123_PORTABLE_API */ #include /* For SEEK_SET */ -#define MPG123_NO_LARGENAME /* disable the _FILE_OFFSET_BITS suffixes. */ #ifdef MPG123_HEADER #include MPG123_HEADER #else @@ -55,7 +58,7 @@ typedef struct { int (*mpg123_getformat)( mpg123_handle *mh, long *rate, int *channels, int *encoding ); int (*mpg123_init)(void); mpg123_handle *(*mpg123_new)(const char* decoder, int *error); - int (*mpg123_open_handle)(mpg123_handle *mh, void *iohandle); + const char* (*mpg123_strerror)(mpg123_handle *mh); const char* (*mpg123_plain_strerror)(int errcode); void (*mpg123_rates)(const long **list, size_t *number); #if (MPG123_API_VERSION >= 45) /* api (but not abi) change as of mpg123-1.26.0 */ @@ -63,11 +66,19 @@ typedef struct { #else int (*mpg123_read)(mpg123_handle *mh, unsigned char *outmemory, size_t outmemsize, size_t *done ); #endif +#if (MPG123_API_VERSION >= 49) + int (*mpg123_open_handle64)(mpg123_handle *mh, void *iohandle); + int (*mpg123_reader64)(mpg123_handle *mh, int (*r_read)(void*, void*, size_t, size_t*), int64_t (*r_lseek)(void*, int64_t, int), void (*cleanup)(void*)); + int64_t (*mpg123_seek64)(mpg123_handle *mh, int64_t sampleoff, int whence); + int64_t (*mpg123_tell64)(mpg123_handle *mh); + int64_t (*mpg123_length64)(mpg123_handle *mh); +#else + int (*mpg123_open_handle)(mpg123_handle *mh, void *iohandle); int (*mpg123_replace_reader_handle)( mpg123_handle *mh, MIX_SSIZE_T (*r_read) (void *, void *, size_t), off_t (*r_lseek)(void *, off_t, int), void (*cleanup)(void*) ); off_t (*mpg123_seek)( mpg123_handle *mh, off_t sampleoff, int whence ); off_t (*mpg123_tell)( mpg123_handle *mh); off_t (*mpg123_length)(mpg123_handle *mh); - const char* (*mpg123_strerror)(mpg123_handle *mh); +#endif } mpg123_loader; static mpg123_loader mpg123; @@ -103,7 +114,7 @@ static int MPG123_Load(void) FUNCTION_LOADER(mpg123_getformat, int (*)( mpg123_handle *mh, long *rate, int *channels, int *encoding )) FUNCTION_LOADER(mpg123_init, int (*)(void)) FUNCTION_LOADER(mpg123_new, mpg123_handle *(*)(const char* decoder, int *error)) - FUNCTION_LOADER(mpg123_open_handle, int (*)(mpg123_handle *mh, void *iohandle)) + FUNCTION_LOADER(mpg123_strerror, const char* (*)(mpg123_handle *mh)) FUNCTION_LOADER(mpg123_plain_strerror, const char* (*)(int errcode)) FUNCTION_LOADER(mpg123_rates, void (*)(const long **list, size_t *number)) #if (MPG123_API_VERSION >= 45) /* api (but not abi) change as of mpg123-1.26.0 */ @@ -111,11 +122,19 @@ static int MPG123_Load(void) #else FUNCTION_LOADER(mpg123_read, int (*)(mpg123_handle *mh, unsigned char *outmemory, size_t outmemsize, size_t *done )) #endif +#if (MPG123_API_VERSION >= 49) + FUNCTION_LOADER(mpg123_open_handle64, int (*)(mpg123_handle *mh, void *iohandle)) + FUNCTION_LOADER(mpg123_reader64, int (*)(mpg123_handle *mh, int (*r_read)(void*, void*, size_t, size_t*), int64_t (*r_lseek)(void*, int64_t, int), void (*cleanup)(void*))) + FUNCTION_LOADER(mpg123_seek64, int64_t (*)(mpg123_handle *mh, int64_t sampleoff, int whence)) + FUNCTION_LOADER(mpg123_tell64, int64_t (*)(mpg123_handle *mh)) + FUNCTION_LOADER(mpg123_length64, int64_t (*)(mpg123_handle *mh)) +#else + FUNCTION_LOADER(mpg123_open_handle, int (*)(mpg123_handle *mh, void *iohandle)) FUNCTION_LOADER(mpg123_replace_reader_handle, int (*)( mpg123_handle *mh, MIX_SSIZE_T (*r_read) (void *, void *, size_t), off_t (*r_lseek)(void *, off_t, int), void (*cleanup)(void*) )) FUNCTION_LOADER(mpg123_seek, off_t (*)( mpg123_handle *mh, off_t sampleoff, int whence )) FUNCTION_LOADER(mpg123_tell, off_t (*)( mpg123_handle *mh)) FUNCTION_LOADER(mpg123_length, off_t (*)(mpg123_handle *mh)) - FUNCTION_LOADER(mpg123_strerror, const char* (*)(mpg123_handle *mh)) +#endif } ++mpg123.loaded; @@ -148,7 +167,7 @@ typedef struct unsigned char *buffer; size_t buffer_size; long sample_rate; - off_t total_length; + Sint64 total_length; Mix_MusicMetaTags tags; } MPG123_Music; @@ -201,6 +220,18 @@ static char const* mpg_err(mpg123_handle* mpg, int result) } /* we're gonna override mpg123's I/O with these wrappers for RWops */ +#if (MPG123_API_VERSION >= 49) +static int rwops_read(void* p, void* dst, size_t n, size_t *b) +{ + *b = MP3_RWread((struct mp3file_t *)p, dst, 1, n); + return 0; +} + +static int64_t rwops_seek(void* p, int64_t offset, int whence) +{ + return MP3_RWseek((struct mp3file_t *)p, offset, whence); +} +#else static MIX_SSIZE_T rwops_read(void* p, void* dst, size_t n) { return (MIX_SSIZE_T)MP3_RWread((struct mp3file_t *)p, dst, 1, n); @@ -210,6 +241,7 @@ static off_t rwops_seek(void* p, off_t offset, int whence) { return (off_t)MP3_RWseek((struct mp3file_t *)p, (Sint64)offset, whence); } +#endif static void rwops_cleanup(void* p) { @@ -269,10 +301,17 @@ static void *MPG123_CreateFromRW(SDL_RWops *src, int freesrc) return NULL; } +#if (MPG123_API_VERSION >= 49) + result = mpg123.mpg123_reader64( + music->handle, + rwops_read, rwops_seek, rwops_cleanup + ); +#else result = mpg123.mpg123_replace_reader_handle( music->handle, rwops_read, rwops_seek, rwops_cleanup ); +#endif if (result != MPG123_OK) { Mix_SetError("mpg123_replace_reader_handle: %s", mpg_err(music->handle, result)); MPG123_Delete(music); @@ -299,7 +338,11 @@ static void *MPG123_CreateFromRW(SDL_RWops *src, int freesrc) mpg123.mpg123_format(music->handle, rates[i], channels, formats); } +#if (MPG123_API_VERSION >= 49) + result = mpg123.mpg123_open_handle64(music->handle, &music->mp3file); +#else result = mpg123.mpg123_open_handle(music->handle, &music->mp3file); +#endif if (result != MPG123_OK) { Mix_SetError("mpg123_open_handle: %s", mpg_err(music->handle, result)); MPG123_Delete(music); @@ -328,7 +371,11 @@ static void *MPG123_CreateFromRW(SDL_RWops *src, int freesrc) return NULL; } +#if (MPG123_API_VERSION >= 49) + music->total_length = mpg123.mpg123_length64(music->handle); +#else music->total_length = mpg123.mpg123_length(music->handle); +#endif music->freesrc = freesrc; return music; @@ -448,9 +495,15 @@ static int MPG123_GetAudio(void *context, void *data, int bytes) static int MPG123_Seek(void *context, double secs) { MPG123_Music *music = (MPG123_Music *)context; +#if (MPG123_API_VERSION >= 49) + int64_t offset = (int64_t)(music->sample_rate * secs); + offset = mpg123.mpg123_seek64(music->handle, offset, SEEK_SET); +#else off_t offset = (off_t)(music->sample_rate * secs); + offset = mpg123.mpg123_seek(music->handle, offset, SEEK_SET); +#endif - if ((offset = mpg123.mpg123_seek(music->handle, offset, SEEK_SET)) < 0) { + if (offset < 0) { return Mix_SetError("mpg123_seek: %s", mpg_err(music->handle, (int)-offset)); } return 0; @@ -459,11 +512,16 @@ static int MPG123_Seek(void *context, double secs) static double MPG123_Tell(void *context) { MPG123_Music *music = (MPG123_Music *)context; - off_t offset = 0; + Sint64 offset; if (!music->sample_rate) { return 0.0; } - if ((offset = mpg123.mpg123_tell(music->handle)) < 0) { +#if (MPG123_API_VERSION >= 49) + offset = mpg123.mpg123_tell64(music->handle); +#else + offset = mpg123.mpg123_tell(music->handle); +#endif + if (offset < 0) { return Mix_SetError("mpg123_tell: %s", mpg_err(music->handle, (int)-offset)); } return (double)offset / music->sample_rate; diff --git a/src/codecs/music_ogg.c b/src/codecs/music_ogg.c index a5ebd956..a1580162 100644 --- a/src/codecs/music_ogg.c +++ b/src/codecs/music_ogg.c @@ -26,6 +26,7 @@ #include "SDL_loadso.h" #include "music_ogg.h" +#include "remap_channels.h" #include "utils.h" #define OV_EXCLUDE_STATIC_CALLBACKS @@ -195,6 +196,7 @@ static void OGG_Delete(void *context); static int OGG_UpdateSection(OGG_music *music) { vorbis_info *vi; + int new_buffer_size; vi = vorbis.ov_info(&music->vf, -1); if (!vi) { @@ -206,11 +208,6 @@ static int OGG_UpdateSection(OGG_music *music) } SDL_memcpy(&music->vi, vi, sizeof(*vi)); - if (music->buffer) { - SDL_free(music->buffer); - music->buffer = NULL; - } - if (music->stream) { SDL_FreeAudioStream(music->stream); music->stream = NULL; @@ -219,13 +216,25 @@ static int OGG_UpdateSection(OGG_music *music) music->stream = SDL_NewAudioStream(AUDIO_S16SYS, (Uint8)vi->channels, (int)vi->rate, music_spec.format, music_spec.channels, music_spec.freq); if (!music->stream) { + SDL_free(music->buffer); + music->buffer = NULL; + music->buffer_size = 0; return -1; } - music->buffer_size = music_spec.samples * (int)sizeof(Sint16) * vi->channels; - music->buffer = (char *)SDL_malloc((size_t)music->buffer_size); - if (!music->buffer) { - return -1; + new_buffer_size = music_spec.samples * (int)sizeof(Sint16) * vi->channels; + + /* Note: never shrink the buffer, we just decoded data in there. */ + if (new_buffer_size > music->buffer_size) { + char *new_buffer = (char *)SDL_realloc(music->buffer, new_buffer_size); + if (!new_buffer) { + SDL_free(music->buffer); + music->buffer = NULL; + music->buffer_size = 0; + return -1; + } + music->buffer = new_buffer; + music->buffer_size = new_buffer_size; } return 0; } @@ -387,7 +396,7 @@ static int OGG_GetSome(void *context, void *data, int bytes, SDL_bool *done) #ifdef OGG_USE_TREMOR amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, §ion); #else - amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, SDL_BYTEORDER == SDL_BIG_ENDIAN, 2, 1, §ion); + amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, SDL_BYTEORDER == SDL_BIG_ENDIAN, (int)sizeof(Sint16), 1, §ion); #endif if (amount < 0) { return set_ov_error("ov_read", amount); @@ -400,6 +409,10 @@ static int OGG_GetSome(void *context, void *data, int bytes, SDL_bool *done) } } + remap_channels_vorbis_s16((Sint16 *)music->buffer, + amount / (int)sizeof(Sint16), + music->vi.channels); + pcmPos = vorbis.ov_pcm_tell(&music->vf); if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) { amount -= (int)((pcmPos - music->loop_end) * music->vi.channels) * (int)sizeof(Sint16); diff --git a/src/codecs/music_ogg_stb.c b/src/codecs/music_ogg_stb.c index f9b36d46..c3e7386e 100644 --- a/src/codecs/music_ogg_stb.c +++ b/src/codecs/music_ogg_stb.c @@ -24,6 +24,7 @@ /* This file supports Ogg Vorbis music streams using a modified stb_vorbis module */ #include "music_ogg.h" +#include "remap_channels.h" #include "utils.h" #include "SDL_assert.h" @@ -74,7 +75,6 @@ typedef struct { int volume; stb_vorbis *vf; stb_vorbis_info vi; - int section; SDL_AudioStream *stream; char *buffer; int buffer_size; @@ -123,18 +123,15 @@ static void OGG_Delete(void *context); static int OGG_UpdateSection(OGG_music *music) { stb_vorbis_info vi; + int new_buffer_size; vi = stb_vorbis_get_info(music->vf); if (vi.channels == music->vi.channels && vi.sample_rate == music->vi.sample_rate) { return 0; } - SDL_memcpy(&music->vi, &vi, sizeof(vi)); - if (music->buffer) { - SDL_free(music->buffer); - music->buffer = NULL; - } + music->vi = vi; if (music->stream) { SDL_FreeAudioStream(music->stream); @@ -144,17 +141,30 @@ static int OGG_UpdateSection(OGG_music *music) music->stream = SDL_NewAudioStream(AUDIO_F32SYS, (Uint8)vi.channels, (int)vi.sample_rate, music_spec.format, music_spec.channels, music_spec.freq); if (!music->stream) { + SDL_free(music->buffer); + music->buffer = NULL; + music->buffer_size = 0; return -1; } - music->buffer_size = music_spec.samples * (int)sizeof(float) * vi.channels; - if (music->buffer_size <= 0) { + new_buffer_size = music_spec.samples * (int)sizeof(float) * vi.channels; + if (new_buffer_size <= 0) { + music->buffer = NULL; + music->buffer_size = 0; return -1; } - music->buffer = (char *)SDL_malloc((size_t)music->buffer_size); - if (!music->buffer) { - return -1; + /* Note: never shrink the buffer, we just decoded data in there. */ + if (new_buffer_size > music->buffer_size) { + char *new_buffer = (char *)SDL_realloc(music->buffer, new_buffer_size); + if (!new_buffer) { + SDL_free(music->buffer); + music->buffer = NULL; + music->buffer_size = 0; + return -1; + } + music->buffer = new_buffer; + music->buffer_size = new_buffer_size; } return 0; } @@ -175,7 +185,6 @@ static void *OGG_CreateFromRW(SDL_RWops *src, int freesrc) } music->src = src; music->volume = MIX_MAX_VOLUME; - music->section = -1; music->vf = stb_vorbis_open_rwops(src, 0, &error, NULL); @@ -308,8 +317,7 @@ static int OGG_GetSome(void *context, void *data, int bytes, SDL_bool *done) { OGG_music *music = (OGG_music *)context; SDL_bool looped = SDL_FALSE; - int filled, amount, result; - int section; + int filled, amount, samples, result; Sint64 pcmPos; filled = SDL_AudioStreamGet(music->stream, data, bytes); @@ -323,21 +331,21 @@ static int OGG_GetSome(void *context, void *data, int bytes, SDL_bool *done) return 0; } - section = music->section; - amount = stb_vorbis_get_samples_float_interleaved(music->vf, - music->vi.channels, - (float *)music->buffer, - music_spec.samples * music->vi.channels); + samples = stb_vorbis_get_samples_float_interleaved(music->vf, + music->vi.channels, + (float *)music->buffer, + music->buffer_size / (int)sizeof(float)); - amount *= music->vi.channels * sizeof(float); - - if (section != music->section) { - music->section = section; - if (OGG_UpdateSection(music) < 0) { - return -1; - } + if (OGG_UpdateSection(music) < 0) { + return -1; } + amount = samples * music->vi.channels * sizeof(float); + + remap_channels_vorbis_flt((float *)music->buffer, + samples * music->vi.channels, + music->vi.channels); + pcmPos = stb_vorbis_get_playback_sample_offset(music->vf); if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) { amount -= (int)((pcmPos - music->loop_end) * music->vi.channels) * (int)sizeof(float); diff --git a/src/codecs/music_opus.c b/src/codecs/music_opus.c index a367fbaa..4ddae9f1 100644 --- a/src/codecs/music_opus.c +++ b/src/codecs/music_opus.c @@ -26,6 +26,7 @@ #include "SDL_loadso.h" #include "music_opus.h" +#include "remap_channels.h" #include "utils.h" #ifdef OPUSFILE_HEADER @@ -169,6 +170,7 @@ static void OPUS_Delete(void*); static int OPUS_UpdateSection(OPUS_music *music) { const OpusHead *op_info; + int new_buffer_size; op_info = opus.op_head(music->of, -1); if (!op_info) { @@ -180,11 +182,6 @@ static int OPUS_UpdateSection(OPUS_music *music) } 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; @@ -193,13 +190,25 @@ static int OPUS_UpdateSection(OPUS_music *music) music->stream = SDL_NewAudioStream(AUDIO_S16SYS, (Uint8)op_info->channel_count, 48000, music_spec.format, music_spec.channels, music_spec.freq); if (!music->stream) { + SDL_free(music->buffer); + music->buffer = NULL; + music->buffer_size = 0; return -1; } - music->buffer_size = (int)music_spec.samples * (int)sizeof(opus_int16) * op_info->channel_count; - music->buffer = (char *)SDL_malloc((size_t)music->buffer_size); - if (!music->buffer) { - return -1; + new_buffer_size = (int)music_spec.samples * (int)sizeof(opus_int16) * op_info->channel_count; + + /* Note: never shrink the buffer, we just decoded data in there. */ + if (new_buffer_size > music->buffer_size) { + char *new_buffer = (char *)SDL_realloc(music->buffer, (size_t)new_buffer_size); + if (!new_buffer) { + SDL_free(music->buffer); + music->buffer = NULL; + music->buffer_size = 0; + return -1; + } + music->buffer = new_buffer; + music->buffer_size = new_buffer_size; } return 0; } @@ -378,6 +387,12 @@ static int OPUS_GetSome(void *context, void *data, int bytes, SDL_bool *done) } } + if (music->op_info->mapping_family == 1) { + remap_channels_vorbis_s16((Sint16 *)music->buffer, + samples * music->op_info->channel_count, + music->op_info->channel_count); + } + pcmPos = opus.op_pcm_tell(music->of); if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) { samples -= (int)((pcmPos - music->loop_end) * music->op_info->channel_count) * (int)sizeof(Sint16); diff --git a/src/codecs/remap_channels.c b/src/codecs/remap_channels.c new file mode 100644 index 00000000..9c057e6d --- /dev/null +++ b/src/codecs/remap_channels.c @@ -0,0 +1,312 @@ +/* + SDL_mixer: An audio mixer library based on the SDL library + Copyright (C) 2026 Daniel K. O. + + 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. +*/ + +/* + * Comparison of channel orders: + * + * | Num. | chan. | SDL | FLAC | MS/USB | Vorbis | + * |------:|------:|-----|------|--------|--------| + * | 2 | 1 | FL | FL | FL | FL | + * | | 2 | FR | FR | FR | FR | + * |-------|-------|-----|------|--------|--------| + * | 3 | 1 | FL | FL | FL | FL | + * | | 2 | FR | FR | FR | FC | + * | | 3 | LFE | FC | FC/LFE | FR | + * |-------|-------|-----|------|--------|--------| + * | 4 | 1 | FL | FL | FL | FL | + * | | 2 | FR | FR | FR | FR | + * | | 3 | RL | RL | RL | RL | + * | | 4 | RR | RR | RR | RR | + * |-------|-------|-----|------|--------|--------| + * | 5 | 1 | FL | FL | FL | FL | + * | (5.0) | 2 | FR | FR | FR | FC | + * | | 3 | LFE | FC | FC/LFE | FR | + * | | 4 | RL | RL | RL | RL | + * | | 5 | RR | RR | RR | RR | + * |-------|-------|-----|------|--------|--------| + * | 6 | 1 | FL | FL | FL | FL | + * | (5.1) | 2 | FR | FR | FR | FC | + * | | 3 | FC | FC | FC | FR | + * | | 4 | LFE | LFE | LFE | RL | + * | | 5 | RL | RL | RR | RR | + * | | 6 | RR | RR | RR | LFE | + * |-------|-------|-----|------|--------|--------| + * | 7 | 1 | FL | FL | FL | FL | + * | | 2 | FR | FR | FR | FC | + * | | 3 | FC | FC | FC | FR | + * | | 4 | LFE | LFE | LFE | SL | + * | | 5 | RC | RC | RC | SR | + * | | 6 | SL | SL | SL | RC | + * | | 7 | SR | SR | SR | LFE | + * |-------|-------|-----|------|--------|--------| + * | 8 | 1 | FL | FL | FL | FL | + * | (7.1) | 2 | FR | FR | FR | FC | + * | | 3 | FC | FC | FC | FR | + * | | 4 | LFE | LFE | LFE | SL | + * | | 5 | RL | RL | RL | SR | + * | | 6 | RR | RR | RR | RL | + * | | 7 | SL | SL | SL | RR | + * | | 8 | SR | SR | SR | LFE | + * + * + * **Note:** USB/MS use a bitmask to indicate which channels are present. The only + * requirement is that they must appear in a fixed order, if present: + * + * - FL (Front Center) + * - FR (Front Right) + * - FC (Front Center) + * - LFE (Low Frequency Enhancement) + * - BL (Back Left) aka RL (Rear LEft) + * - BR (Back Right) aka RR (Rear Right) + * - FLC (Front Left of Center) + * - FRC (Front Right of Center) + * - BC (Back Center) aka RC (Rear Center) + * - SL (Side Left) + * - SR (Side Right) + * - TC (Top Center) + * - TFL (Top Front Left) + * - TFC (Top Front Center) + * - TFR (Top Front Right) + * - TBL (Top Back Left) + * - TBC (Top Back Center) + * - TBR (Top Back Right) + * + * + * **Note:** WavPack documentation claims that ALL MS/USB channels (up to the max) must be + * present. So to contain all the 7.1 channels, a .wv file must have 11 channels, with + * silent FLC, FRC, BC/RC channels. + * + * + * Sources: + * - Vorbis: https://www.rfc-editor.org/rfc/rfc7845.html#section-5.1.1.2 + * - SDL: https://github.com/libsdl-org/SDL/blob/main/include/SDL3/SDL_audio.h + * - FLAC: https://www.rfc-editor.org/rfc/rfc9639.html#name-channels-bits + * - WavPack (WV): https://www.wavpack.com/wavpack_doc.html + * - USB: https://www.usb.org/sites/default/files/audio10.pdf (see "3.7.2.3 Audio Channel Cluster Format") + * - MS: https://learn.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible + */ + +#include "remap_channels.h" + + +static void remap_channels_vorbis_3_s16(Sint16 *samples, int num_samples) +{ + /* Note: this isn't perfect, because we map FC to LFE */ + int i; + for (i = 0; i < num_samples; i += 3) { + Sint16 FC = samples[i + 1]; + Sint16 FR = samples[i + 2]; + samples[i + 1] = FR; + samples[i + 2] = FC; + } +} + +static void remap_channels_vorbis_3_flt(float *samples, int num_samples) +{ + /* Note: this isn't perfect, because we map FC to LFE */ + int i; + for (i = 0; i < num_samples; i += 3) { + float FC = samples[i + 1]; + float FR = samples[i + 2]; + samples[i + 1] = FR; + samples[i + 2] = FC; + } +} + +static void remap_channels_vorbis_5_s16(Sint16 *samples, int num_samples) +{ + /* Note: this isn't perfect, because we map FC to LFE. */ + int i; + for (i = 0; i < num_samples; i += 5) { + Sint16 FC = samples[i + 1]; + Sint16 FR = samples[i + 2]; + samples[i + 1] = FR; + samples[i + 2] = FC; + } +} + +static void remap_channels_vorbis_5_flt(float *samples, int num_samples) +{ + /* Note: this isn't perfect, because we map FC to LFE. */ + int i; + for (i = 0; i < num_samples; i += 5) { + float FC = samples[i + 1]; + float FR = samples[i + 2]; + samples[i + 1] = FR; + samples[i + 2] = FC; + } +} + +static void remap_channels_vorbis_5_1_s16(Sint16 *samples, int num_samples) +{ + int i; + for (i = 0; i < num_samples; i += 6) { + Sint16 FC = samples[i + 1]; + Sint16 FR = samples[i + 2]; + Sint16 RL = samples[i + 3]; + Sint16 RR = samples[i + 4]; + Sint16 LFE = samples[i + 5]; + samples[i + 1] = FR; + samples[i + 2] = FC; + samples[i + 3] = LFE; + samples[i + 4] = RL; + samples[i + 5] = RR; + } +} + +static void remap_channels_vorbis_5_1_flt(float *samples, int num_samples) +{ + int i; + for (i = 0; i < num_samples; i += 6) { + float FC = samples[i + 1]; + float FR = samples[i + 2]; + float RL = samples[i + 3]; + float RR = samples[i + 4]; + float LFE = samples[i + 5]; + samples[i + 1] = FR; + samples[i + 2] = FC; + samples[i + 3] = LFE; + samples[i + 4] = RL; + samples[i + 5] = RR; + } +} + +static void remap_channels_vorbis_7_s16(Sint16 *samples, int num_samples) +{ + int i = 0; + for (i = 0; i < num_samples; i += 7) { + Sint16 FC = samples[i + 1]; + Sint16 FR = samples[i + 2]; + Sint16 SL = samples[i + 3]; + Sint16 SR = samples[i + 4]; + Sint16 RC = samples[i + 5]; + Sint16 LFE = samples[i + 6]; + samples[i + 1] = FR; + samples[i + 2] = FC; + samples[i + 3] = LFE; + samples[i + 4] = RC; + samples[i + 5] = SL; + samples[i + 6] = SR; + } +} + +static void remap_channels_vorbis_7_flt(float *samples, int num_samples) +{ + int i = 0; + for (i = 0; i < num_samples; i += 7) { + float FC = samples[i + 1]; + float FR = samples[i + 2]; + float SL = samples[i + 3]; + float SR = samples[i + 4]; + float RC = samples[i + 5]; + float LFE = samples[i + 6]; + samples[i + 1] = FR; + samples[i + 2] = FC; + samples[i + 3] = LFE; + samples[i + 4] = RC; + samples[i + 5] = SL; + samples[i + 6] = SR; + } +} + +static void remap_channels_vorbis_7_1_s16(Sint16 *samples, int num_samples) +{ + int i = 0; + for (i = 0; i < num_samples; i += 8) { + Sint16 FC = samples[i + 1]; + Sint16 FR = samples[i + 2]; + Sint16 SL = samples[i + 3]; + Sint16 SR = samples[i + 4]; + Sint16 RL = samples[i + 5]; + Sint16 RR = samples[i + 6]; + Sint16 LFE = samples[i + 7]; + samples[i + 1] = FR; + samples[i + 2] = FC; + samples[i + 3] = LFE; + samples[i + 4] = RL; + samples[i + 5] = RR; + samples[i + 6] = SL; + samples[i + 7] = SR; + } +} + +static void remap_channels_vorbis_7_1_flt(float *samples, int num_samples) +{ + int i = 0; + for (i = 0; i < num_samples; i += 8) { + float FC = samples[i + 1]; + float FR = samples[i + 2]; + float SL = samples[i + 3]; + float SR = samples[i + 4]; + float RL = samples[i + 5]; + float RR = samples[i + 6]; + float LFE = samples[i + 7]; + samples[i + 1] = FR; + samples[i + 2] = FC; + samples[i + 3] = LFE; + samples[i + 4] = RL; + samples[i + 5] = RR; + samples[i + 6] = SL; + samples[i + 7] = SR; + } +} + +void remap_channels_vorbis_s16(Sint16 *samples, int num_samples, int num_channels) +{ + switch (num_channels) { + case 3: + remap_channels_vorbis_3_s16(samples, num_samples); + break; + case 5: + remap_channels_vorbis_5_s16(samples, num_samples); + break; + case 6: + remap_channels_vorbis_5_1_s16(samples, num_samples); + break; + case 7: + remap_channels_vorbis_7_s16(samples, num_samples); + break; + case 8: + remap_channels_vorbis_7_1_s16(samples, num_samples); + break; + } +} + +void remap_channels_vorbis_flt(float *samples, int num_samples, int num_channels) +{ + switch (num_channels) { + case 3: + remap_channels_vorbis_3_flt(samples, num_samples); + break; + case 5: + remap_channels_vorbis_5_flt(samples, num_samples); + break; + case 6: + remap_channels_vorbis_5_1_flt(samples, num_samples); + break; + case 7: + remap_channels_vorbis_7_flt(samples, num_samples); + break; + case 8: + remap_channels_vorbis_7_1_flt(samples, num_samples); + break; + } +} diff --git a/src/codecs/remap_channels.h b/src/codecs/remap_channels.h new file mode 100644 index 00000000..3479680d --- /dev/null +++ b/src/codecs/remap_channels.h @@ -0,0 +1,31 @@ +/* + SDL_mixer: An audio mixer library based on the SDL library + Copyright (C) 2026 Daniel K. O. + + 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. +*/ + +#ifndef REMAP_CHANNELS_H_ +#define REMAP_CHANNELS_H_ + +#include "SDL_types.h" + +extern void remap_channels_vorbis_s16(Sint16 *samples, int num_samples, int num_channels); + +extern void remap_channels_vorbis_flt(float *samples, int num_samples, int num_channels); + +#endif /* REMAP_CHANNELS_H_ */