├── .editorconfig ├── .github └── workflows │ └── CI.yaml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── FFMPEG_CORE_VERSION.cmake ├── LICENSE ├── README.md ├── cmake ├── FindAVCODEC.cmake ├── FindAVDEVICE.cmake ├── FindAVFILTER.cmake ├── FindAVFORMAT.cmake ├── FindAVUTIL.cmake ├── FindSDL2.cmake ├── FindSDL3.cmake └── FindSWRESAMPLE.cmake ├── ffmpeg_core.h ├── ffmpeg_core.rc.in ├── ffmpeg_core_config.h.in ├── ffmpeg_core_version.h.in ├── patch └── zlib-msvc │ └── zconf.h.patch ├── scripts ├── build_SDL2_mingw-x64.sh ├── build_SDL2_msvc-x64.bat ├── build_bzlib_msvc-x64.bat ├── build_ffmpeg_core_mingw-x64-debug.sh ├── build_ffmpeg_core_mingw-x64-release.sh ├── build_ffmpeg_core_mingw-x64-wasapi-debug.sh ├── build_ffmpeg_core_mingw-x64-wasapi-release.sh ├── build_ffmpeg_core_msvc-x64-debug.bat ├── build_ffmpeg_core_msvc-x64-release.bat ├── build_ffmpeg_core_msvc-x64-wasapi-debug.bat ├── build_ffmpeg_core_msvc-x64-wasapi-release.bat ├── build_ffmpeg_mingw-x64.sh ├── build_ffmpeg_msvc-x64.sh ├── build_pkgconf_msvc-x64.bat ├── build_zlib_msvc-x64.bat ├── download_cdio-paranoia_msvc-x64.bat ├── download_cdio_msvc-x64.bat ├── download_gnutls_msvc-x64.bat ├── download_lld-rust.bat ├── download_resource.bat ├── download_resource.py ├── extract_zip.bat ├── extract_zip.py ├── get_cache_key.py └── pack_prog.py └── src ├── cda.c ├── cda.h ├── ch_layout.c ├── ch_layout.h ├── core.cpp ├── core.h ├── decode.c ├── decode.h ├── equalizer.c ├── equalizer.h ├── equalizer_settings.cpp ├── equalizer_settings.h ├── fft_data.c ├── fft_data.h ├── file.cpp ├── file.h ├── filter.c ├── filter.h ├── linked_list ├── float_linked_list.cpp ├── float_linked_list.h ├── position_data_linked_list.cpp └── position_data_linked_list.h ├── loop.c ├── loop.h ├── mixing.c ├── mixing.h ├── open.c ├── open.h ├── output.c ├── output.h ├── position_data.c ├── position_data.h ├── reverb.c ├── reverb.h ├── speed.c ├── speed.h ├── volume.c ├── volume.h ├── wasapi.cpp └── wasapi.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | charset = utf-8 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "utils"] 2 | path = utils 3 | url = https://github.com/lifegpc/c-utils 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(ffmpeg_core) 4 | 5 | if (MSVC) 6 | add_compile_options(/utf-8) # 让编译器使用UTF-8编码 7 | endif() 8 | 9 | option(ENABLE_WASAPI "Enable WASAPI support" OFF) 10 | option(ENABLE_SDL3 "Force enable SDL3" OFF) 11 | option(ENABLE_SDL2 "Force enable SDL2" OFF) 12 | 13 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 14 | include(GNUInstallDirs) 15 | include(CheckIncludeFiles) 16 | find_package(AVFORMAT 58 REQUIRED) 17 | find_package(AVCODEC 58 REQUIRED) 18 | find_package(AVDEVICE 58 REQUIRED) 19 | find_package(AVUTIL 56 REQUIRED) 20 | find_package(AVFILTER 7 REQUIRED) 21 | find_package(SWRESAMPLE 4 REQUIRED) 22 | if (ENABLE_SDL2 AND ENABLE_SDL3) 23 | message(FATAL_ERROR "SDL2 and SDL3 cannot be enabled at the same time") 24 | elseif(ENABLE_SDL3) 25 | find_package(SDL3 REQUIRED) 26 | elseif(ENABLE_SDL2) 27 | find_package(SDL2 REQUIRED) 28 | else() 29 | find_package(SDL3) 30 | if (NOT SDL3_FOUND) 31 | find_package(SDL2 REQUIRED) 32 | endif() 33 | endif() 34 | if (NOT WIN32) 35 | find_package(Threads) 36 | if (NOT CMAKE_USE_PTHREADS_INIT) 37 | message(FATAL_ERROR "Pthreads is required") 38 | endif() 39 | endif() 40 | 41 | set(ENABLE_ICONV OFF CACHE BOOL "Libiconv is not needed.") 42 | add_subdirectory(utils) 43 | include_directories("${CMAKE_CURRENT_SOURCE_DIR}/utils") 44 | 45 | set(CORE_FILES 46 | ffmpeg_core.h 47 | src/core.h 48 | src/core.cpp 49 | src/decode.h 50 | src/decode.c 51 | src/open.h 52 | src/open.c 53 | "src/output.h" 54 | "src/output.c" 55 | src/loop.h 56 | src/loop.c 57 | "src/filter.h" 58 | "src/filter.c" 59 | src/volume.h 60 | src/volume.c 61 | src/speed.h 62 | src/speed.c 63 | src/fft_data.h 64 | src/fft_data.c 65 | src/cda.h 66 | src/cda.c 67 | src/file.h 68 | src/file.cpp 69 | src/equalizer_settings.h 70 | src/equalizer_settings.cpp 71 | src/equalizer.h 72 | src/equalizer.c 73 | src/ch_layout.h 74 | src/ch_layout.c 75 | src/reverb.h 76 | src/reverb.c 77 | src/linked_list/float_linked_list.h 78 | src/linked_list/float_linked_list.cpp 79 | src/mixing.h 80 | src/mixing.c 81 | "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core.rc" 82 | "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core_version.h" 83 | "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core_config.h" 84 | .editorconfig 85 | ) 86 | 87 | if (ENABLE_WASAPI) 88 | CHECK_INCLUDE_FILES("audioclient.h;mmdeviceapi.h;Functiondiscoverykeys_devpkey.h" HAVE_WASAPI) 89 | endif() 90 | 91 | if (HAVE_WASAPI) 92 | list(APPEND CORE_FILES 93 | src/wasapi.h 94 | src/wasapi.cpp 95 | src/linked_list/position_data_linked_list.h 96 | src/linked_list/position_data_linked_list.cpp 97 | src/position_data.h 98 | src/position_data.c 99 | ) 100 | endif() 101 | 102 | if (SDL3_FOUND) 103 | set(HAVE_SDL3 1) 104 | endif() 105 | 106 | if (WIN32) 107 | check_symbol_exists(printf_s "stdio.h" HAVE_PRINTF_S) 108 | else() 109 | set(TMP "${CMAKE_REQUIRED_DEFINITIONS}") 110 | set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) 111 | check_symbol_exists(pthread_tryjoin_np "pthread.h" HAVE_PTHREAD_TRYJOIN_NP) 112 | if (HAVE_PTHREAD_TRYJOIN_NP) 113 | add_compile_definitions(_GNU_SOURCE) 114 | set(HAVE_GNU_SOURCE ON) 115 | endif() 116 | set(CMAKE_REQUIRED_DEFINITIONS "${TMP}") 117 | endif() 118 | 119 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg_core_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core_config.h") 120 | 121 | set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core.rc" PROPERTIES GENERATED TRUE) 122 | set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core_version.h" PROPERTIES GENERATED TRUE) 123 | 124 | add_custom_target(ffmpeg_core_version 125 | ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/FFMPEG_CORE_VERSION.cmake" 126 | ) 127 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 128 | 129 | set(PUBLIC_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg_core.h" "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core_version.h") 130 | 131 | add_library(ffmpeg_core SHARED "${CORE_FILES}") 132 | add_dependencies(ffmpeg_core ffmpeg_core_version) 133 | set_target_properties(ffmpeg_core PROPERTIES PREFIX "") 134 | set_target_properties(ffmpeg_core PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") 135 | target_compile_definitions(ffmpeg_core PRIVATE BUILD_FFMPEG_CORE) 136 | target_link_libraries(ffmpeg_core AVFORMAT::AVFORMAT) 137 | target_link_libraries(ffmpeg_core AVCODEC::AVCODEC) 138 | target_link_libraries(ffmpeg_core AVDEVICE::AVDEVICE) 139 | target_link_libraries(ffmpeg_core AVUTIL::AVUTIL) 140 | target_link_libraries(ffmpeg_core AVFILTER::AVFILTER) 141 | target_link_libraries(ffmpeg_core SWRESAMPLE::SWRESAMPLE) 142 | if (SDL2_FOUND) 143 | target_link_libraries(ffmpeg_core SDL2::Core) 144 | if (SDL2MAIN_FOUND) 145 | target_link_libraries(ffmpeg_core SDL2::Main) 146 | endif() 147 | endif() 148 | if (SDL3_FOUND) 149 | target_link_libraries(ffmpeg_core SDL3::Core) 150 | if (SDL3MAIN_FOUND) 151 | target_link_libraries(ffmpeg_core SDL3::Main) 152 | endif() 153 | endif() 154 | target_link_libraries(ffmpeg_core utils) 155 | 156 | if (NOT MSVC) 157 | target_link_libraries(ffmpeg_core m) 158 | if (HAVE_WASAPI) 159 | target_link_libraries(ffmpeg_core ksuser) 160 | endif() 161 | endif() 162 | 163 | if (NOT WIN32) 164 | target_link_libraries(ffmpeg_core Threads::Threads) 165 | endif() 166 | 167 | install(TARGETS ffmpeg_core) 168 | if (MSVC) 169 | install(FILES $ DESTINATION bin OPTIONAL) 170 | endif() 171 | -------------------------------------------------------------------------------- /FFMPEG_CORE_VERSION.cmake: -------------------------------------------------------------------------------- 1 | set(FFMPEG_CORE_VERSION_MAJOR 1) 2 | set(FFMPEG_CORE_VERSION_MINOR 1) 3 | set(FFMPEG_CORE_VERSION_MICRO 1) 4 | set(FFMPEG_CORE_VERSION_REV 0) 5 | set(FFMPEG_CORE_VERSION ${FFMPEG_CORE_VERSION_MAJOR}.${FFMPEG_CORE_VERSION_MINOR}.${FFMPEG_CORE_VERSION_MICRO}.${FFMPEG_CORE_VERSION_REV}) 6 | message(STATUS "Generate \"${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core_version.h\"") 7 | configure_file("${CMAKE_CURRENT_LIST_DIR}/ffmpeg_core_version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core_version.h") 8 | message(STATUS "Generate \"${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core.rc\"") 9 | configure_file("${CMAKE_CURRENT_LIST_DIR}/ffmpeg_core.rc.in" "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg_core.rc") 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FFMPEG 核心 2 | ## 可选功能 3 | ### WASAPI 4 | 目前支持WASAPI,但没有经过足够的测试,可能会有严重的BUG。尤其是独占模式。 5 | ## CMake 选项 6 | |选项|描述|默认值| 7 | |:-:|:-:|:-:| 8 | |`ENABLE_WASAPI`|启用 WASAPI 支持|`OFF`| 9 | ## 编译需求 10 | ### 第三方库 11 | * `FFMPEG` 库,包含 12 | * `libavutil` 13 | * `libavcodec` 14 | * `libavformat` 15 | * `libavdevice` 16 | * `libavfilter` 17 | * `libswresample` 18 | * `SDL2` / `SDL3` 库 19 | 20 | FFMPEG库采用pkg-config来寻找,请确保正确的设置了环境变量`PKG_CONFIG_PATH`和CMAKE选项`CMAKE_PREFIX_PATH` 21 | ### FFMPEG 库要求 22 | * 需要链接任意一TLS库以支持HTTPS(例如 `gnutls` / `openssl`) 23 | * 需要链接 `libcdio` 以支持播放CD(ffmpeg官网的预编译版本可能无法正常工作即使其链接了 `libcdio`) 24 | #### libavfilter 25 | 以下 `filters` 在核心中被使用到: 26 | * `volume`:用于调节声音大小 27 | * `atempo`:用于调节速度 28 | * `equalizer`:用于均衡器 29 | * `aresample`:用于格式自动转换 30 | * `aecho`:用于支持混响 31 | 32 | 其他 `filters` 可以删除以减小体积 33 | -------------------------------------------------------------------------------- /cmake/FindAVCODEC.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | find_package(PkgConfig) 3 | if (PkgConfig_FOUND) 4 | pkg_check_modules(PC_AVCODEC QUIET IMPORTED_TARGET GLOBAL libavcodec) 5 | endif() 6 | 7 | if (PC_AVCODEC_FOUND) 8 | set(AVCODEC_FOUND TRUE) 9 | set(AVCODEC_VERSION ${PC_AVCODEC_VERSION}) 10 | set(AVCODEC_VERSION_STRING ${PC_AVCODEC_STRING}) 11 | set(AVCODEC_LIBRARYS ${PC_AVCODEC_LIBRARIES}) 12 | if (USE_STATIC_LIBS) 13 | set(AVCODEC_INCLUDE_DIRS ${PC_AVCODEC_STATIC_INCLUDE_DIRS}) 14 | else() 15 | set(AVCODEC_INCLUDE_DIRS ${PC_AVCODEC_INCLUDE_DIRS}) 16 | endif() 17 | if (NOT AVCODEC_INCLUDE_DIRS) 18 | find_path(AVCODEC_INCLUDE_DIRS NAMES libavcodec/avcodec.h) 19 | if (AVCODEC_INCLUDE_DIRS) 20 | target_include_directories(PkgConfig::PC_AVCODEC INTERFACE ${AVCODEC_INCLUDE_DIRS}) 21 | endif() 22 | endif() 23 | if (NOT TARGET AVCODEC::AVCODEC) 24 | add_library(AVCODEC::AVCODEC ALIAS PkgConfig::PC_AVCODEC) 25 | endif() 26 | else() 27 | message(FATAL_ERROR "failed.") 28 | endif() 29 | 30 | include(FindPackageHandleStandardArgs) 31 | find_package_handle_standard_args(AVCODEC 32 | FOUND_VAR AVCODEC_FOUND 33 | REQUIRED_VARS 34 | AVCODEC_LIBRARYS 35 | AVCODEC_INCLUDE_DIRS 36 | VERSION_VAR AVCODEC_VERSION 37 | ) 38 | -------------------------------------------------------------------------------- /cmake/FindAVDEVICE.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | find_package(PkgConfig) 3 | if (PkgConfig_FOUND) 4 | pkg_check_modules(PC_AVDEVICE QUIET IMPORTED_TARGET GLOBAL libavdevice) 5 | endif() 6 | 7 | if (PC_AVDEVICE_FOUND) 8 | set(AVDEVICE_FOUND TRUE) 9 | set(AVDEVICE_VERSION ${PC_AVDEVICE_VERSION}) 10 | set(AVDEVICE_VERSION_STRING ${PC_AVDEVICE_STRING}) 11 | set(AVDEVICE_LIBRARYS ${PC_AVDEVICE_LIBRARIES}) 12 | if (USE_STATIC_LIBS) 13 | set(AVDEVICE_INCLUDE_DIRS ${PC_AVDEVICE_STATIC_INCLUDE_DIRS}) 14 | else() 15 | set(AVDEVICE_INCLUDE_DIRS ${PC_AVDEVICE_INCLUDE_DIRS}) 16 | endif() 17 | if (NOT AVDEVICE_INCLUDE_DIRS) 18 | find_path(AVDEVICE_INCLUDE_DIRS NAMES libavdevice/avdevice.h) 19 | if (AVDEVICE_INCLUDE_DIRS) 20 | target_include_directories(PkgConfig::PC_AVDEVICE INTERFACE ${AVDEVICE_INCLUDE_DIRS}) 21 | endif() 22 | endif() 23 | if (NOT TARGET AVDEVICE::AVDEVICE) 24 | add_library(AVDEVICE::AVDEVICE ALIAS PkgConfig::PC_AVDEVICE) 25 | endif() 26 | else() 27 | message(FATAL_ERROR "failed.") 28 | endif() 29 | 30 | include(FindPackageHandleStandardArgs) 31 | find_package_handle_standard_args(AVDEVICE 32 | FOUND_VAR AVDEVICE_FOUND 33 | REQUIRED_VARS 34 | AVDEVICE_LIBRARYS 35 | AVDEVICE_INCLUDE_DIRS 36 | VERSION_VAR AVDEVICE_VERSION 37 | ) 38 | -------------------------------------------------------------------------------- /cmake/FindAVFILTER.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | find_package(PkgConfig) 3 | if (PkgConfig_FOUND) 4 | pkg_check_modules(PC_AVFILTER QUIET IMPORTED_TARGET GLOBAL libavfilter) 5 | endif() 6 | 7 | if (PC_AVFILTER_FOUND) 8 | set(AVFILTER_FOUND TRUE) 9 | set(AVFILTER_VERSION ${PC_AVFILTER_VERSION}) 10 | set(AVFILTER_VERSION_STRING ${PC_AVFILTER_STRING}) 11 | set(AVFILTER_LIBRARYS ${PC_AVFILTER_LIBRARIES}) 12 | if (USE_STATIC_LIBS) 13 | set(AVFILTER_INCLUDE_DIRS ${PC_AVFILTER_STATIC_INCLUDE_DIRS}) 14 | else() 15 | set(AVFILTER_INCLUDE_DIRS ${PC_AVFILTER_INCLUDE_DIRS}) 16 | endif() 17 | if (NOT AVFILTER_INCLUDE_DIRS) 18 | find_path(AVFILTER_INCLUDE_DIRS NAMES libavfilter/avfilter.h) 19 | if (AVFILTER_INCLUDE_DIRS) 20 | target_include_directories(PkgConfig::PC_AVFILTER INTERFACE ${AVFILTER_INCLUDE_DIRS}) 21 | endif() 22 | endif() 23 | if (NOT TARGET AVFILTER::AVFILTER) 24 | add_library(AVFILTER::AVFILTER ALIAS PkgConfig::PC_AVFILTER) 25 | endif() 26 | else() 27 | message(FATAL_ERROR "failed.") 28 | endif() 29 | 30 | include(FindPackageHandleStandardArgs) 31 | find_package_handle_standard_args(AVFILTER 32 | FOUND_VAR AVFILTER_FOUND 33 | REQUIRED_VARS 34 | AVFILTER_LIBRARYS 35 | AVFILTER_INCLUDE_DIRS 36 | VERSION_VAR AVFILTER_VERSION 37 | ) 38 | -------------------------------------------------------------------------------- /cmake/FindAVFORMAT.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | find_package(PkgConfig) 3 | if (PkgConfig_FOUND) 4 | pkg_check_modules(PC_AVFORMAT QUIET IMPORTED_TARGET GLOBAL libavformat) 5 | endif() 6 | 7 | if (PC_AVFORMAT_FOUND) 8 | set(AVFORMAT_FOUND TRUE) 9 | set(AVFORMAT_VERSION ${PC_AVFORMAT_VERSION}) 10 | set(AVFORMAT_VERSION_STRING ${PC_AVFORMAT_STRING}) 11 | set(AVFORMAT_LIBRARYS ${PC_AVFORMAT_LIBRARIES}) 12 | if (USE_STATIC_LIBS) 13 | set(AVFORMAT_INCLUDE_DIRS ${PC_AVFORMAT_STATIC_INCLUDE_DIRS}) 14 | else() 15 | set(AVFORMAT_INCLUDE_DIRS ${PC_AVFORMAT_INCLUDE_DIRS}) 16 | endif() 17 | if (NOT AVFORMAT_INCLUDE_DIRS) 18 | find_path(AVFORMAT_INCLUDE_DIRS NAMES libavformat/avformat.h) 19 | if (AVFORMAT_INCLUDE_DIRS) 20 | target_link_directories(PkgConfig::PC_AVFORMAT INTERFACE ${AVFORMAT_INCLUDE_DIRS}) 21 | endif() 22 | endif() 23 | if (NOT TARGET AVFORMAT::AVFORMAT) 24 | add_library(AVFORMAT::AVFORMAT ALIAS PkgConfig::PC_AVFORMAT) 25 | endif() 26 | else() 27 | message(FATAL_ERROR "failed.") 28 | endif() 29 | 30 | include(FindPackageHandleStandardArgs) 31 | find_package_handle_standard_args(AVFORMAT 32 | FOUND_VAR AVFORMAT_FOUND 33 | REQUIRED_VARS 34 | AVFORMAT_LIBRARYS 35 | AVFORMAT_INCLUDE_DIRS 36 | VERSION_VAR AVFORMAT_VERSION 37 | ) 38 | -------------------------------------------------------------------------------- /cmake/FindAVUTIL.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | find_package(PkgConfig) 3 | if (PkgConfig_FOUND) 4 | pkg_check_modules(PC_AVUTIL QUIET IMPORTED_TARGET GLOBAL libavutil) 5 | endif() 6 | 7 | if (PC_AVUTIL_FOUND) 8 | set(AVUTIL_FOUND TRUE) 9 | set(AVUTIL_VERSION ${PC_AVUTIL_VERSION}) 10 | set(AVUTIL_VERSION_STRING ${PC_AVUTIL_STRING}) 11 | set(AVUTIL_LIBRARYS ${PC_AVUTIL_LIBRARIES}) 12 | if (USE_STATIC_LIBS) 13 | set(AVUTIL_INCLUDE_DIRS ${PC_AVUTIL_STATIC_INCLUDE_DIRS}) 14 | else() 15 | set(AVUTIL_INCLUDE_DIRS ${PC_AVUTIL_INCLUDE_DIRS}) 16 | endif() 17 | if (NOT AVUTIL_INCLUDE_DIRS) 18 | find_path(AVUTIL_INCLUDE_DIRS NAMES libavutil/avutil.h) 19 | if (AVUTIL_INCLUDE_DIRS) 20 | target_include_directories(PkgConfig::PC_AVUTIL INTERFACE ${AVUTIL_INCLUDE_DIRS}) 21 | endif() 22 | endif() 23 | if (NOT TARGET AVUTIL::AVUTIL) 24 | add_library(AVUTIL::AVUTIL ALIAS PkgConfig::PC_AVUTIL) 25 | endif() 26 | else() 27 | message(FATAL_ERROR "failed.") 28 | endif() 29 | 30 | include(FindPackageHandleStandardArgs) 31 | find_package_handle_standard_args(AVUTIL 32 | FOUND_VAR AVUTIL_FOUND 33 | REQUIRED_VARS 34 | AVUTIL_LIBRARYS 35 | AVUTIL_INCLUDE_DIRS 36 | VERSION_VAR AVUTIL_VERSION 37 | ) 38 | -------------------------------------------------------------------------------- /cmake/FindSDL2.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | # Copyright 2019 Amine Ben Hassouna 5 | # Copyright 2000-2019 Kitware, Inc. and Contributors 6 | # All rights reserved. 7 | 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions 10 | # are met: 11 | 12 | # * Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | 15 | # * Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | 19 | # * Neither the name of Kitware, Inc. nor the names of Contributors 20 | # may be used to endorse or promote products derived from this 21 | # software without specific prior written permission. 22 | 23 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | #[=======================================================================[.rst: 36 | FindSDL2 37 | -------- 38 | 39 | Locate SDL2 library 40 | 41 | This module defines the following 'IMPORTED' targets: 42 | 43 | :: 44 | 45 | SDL2::Core 46 | The SDL2 library, if found. 47 | Libraries should link to SDL2::Core 48 | 49 | SDL2::Main 50 | The SDL2main library, if found. 51 | Applications should link to SDL2::Main instead of SDL2::Core 52 | 53 | 54 | 55 | This module will set the following variables in your project: 56 | 57 | :: 58 | 59 | SDL2_LIBRARIES, the name of the library to link against 60 | SDL2_INCLUDE_DIRS, where to find SDL.h 61 | SDL2_FOUND, if false, do not try to link to SDL2 62 | SDL2MAIN_FOUND, if false, do not try to link to SDL2main 63 | SDL2_VERSION_STRING, human-readable string containing the version of SDL2 64 | 65 | 66 | 67 | This module responds to the following cache variables: 68 | 69 | :: 70 | 71 | SDL2_PATH 72 | Set a custom SDL2 Library path (default: empty) 73 | 74 | SDL2_NO_DEFAULT_PATH 75 | Disable search SDL2 Library in default path. 76 | If SDL2_PATH (default: ON) 77 | Else (default: OFF) 78 | 79 | SDL2_INCLUDE_DIR 80 | SDL2 headers path. 81 | 82 | SDL2_LIBRARY 83 | SDL2 Library (.dll, .so, .a, etc) path. 84 | 85 | SDL2MAIN_LIBRAY 86 | SDL2main Library (.a) path. 87 | 88 | SDL2_BUILDING_LIBRARY 89 | This flag is useful only when linking to SDL2_LIBRARIES insead of 90 | SDL2::Main. It is required only when building a library that links to 91 | SDL2_LIBRARIES, because only applications need main() (No need to also 92 | link to SDL2main). 93 | If this flag is defined, then no SDL2main will be added to SDL2_LIBRARIES 94 | and no SDL2::Main target will be created. 95 | 96 | 97 | Don't forget to include SDLmain.h and SDLmain.m in your project for the 98 | OS X framework based version. (Other versions link to -lSDL2main which 99 | this module will try to find on your behalf.) Also for OS X, this 100 | module will automatically add the -framework Cocoa on your behalf. 101 | 102 | 103 | Additional Note: If you see an empty SDL2_LIBRARY in your project 104 | configuration, it means CMake did not find your SDL2 library 105 | (SDL2.dll, libsdl2.so, SDL2.framework, etc). Set SDL2_LIBRARY to point 106 | to your SDL2 library, and configure again. Similarly, if you see an 107 | empty SDL2MAIN_LIBRARY, you should set this value as appropriate. These 108 | values are used to generate the final SDL2_LIBRARIES variable and the 109 | SDL2::Core and SDL2::Main targets, but when these values are unset, 110 | SDL2_LIBRARIES, SDL2::Core and SDL2::Main does not get created. 111 | 112 | 113 | $SDL2DIR is an environment variable that would correspond to the 114 | ./configure --prefix=$SDL2DIR used in building SDL2. l.e.galup 9-20-02 115 | 116 | 117 | 118 | Created by Amine Ben Hassouna: 119 | Adapt FindSDL.cmake to SDL2 (FindSDL2.cmake). 120 | Add cache variables for more flexibility: 121 | SDL2_PATH, SDL2_NO_DEFAULT_PATH (for details, see doc above). 122 | Mark 'Threads' as a required dependency for non-OSX systems. 123 | Modernize the FindSDL2.cmake module by creating specific targets: 124 | SDL2::Core and SDL2::Main (for details, see doc above). 125 | 126 | 127 | Original FindSDL.cmake module: 128 | Modified by Eric Wing. Added code to assist with automated building 129 | by using environmental variables and providing a more 130 | controlled/consistent search behavior. Added new modifications to 131 | recognize OS X frameworks and additional Unix paths (FreeBSD, etc). 132 | Also corrected the header search path to follow "proper" SDL 133 | guidelines. Added a search for SDLmain which is needed by some 134 | platforms. Added a search for threads which is needed by some 135 | platforms. Added needed compile switches for MinGW. 136 | 137 | On OSX, this will prefer the Framework version (if found) over others. 138 | People will have to manually change the cache value of SDL2_LIBRARY to 139 | override this selection or set the SDL2_PATH variable or the CMake 140 | environment CMAKE_INCLUDE_PATH to modify the search paths. 141 | 142 | Note that the header path has changed from SDL/SDL.h to just SDL.h 143 | This needed to change because "proper" SDL convention is #include 144 | "SDL.h", not . This is done for portability reasons 145 | because not all systems place things in SDL/ (see FreeBSD). 146 | #]=======================================================================] 147 | 148 | # Define options for searching SDL2 Library in a custom path 149 | 150 | set(SDL2_PATH "" CACHE STRING "Custom SDL2 Library path") 151 | 152 | set(_SDL2_NO_DEFAULT_PATH OFF) 153 | if(SDL2_PATH) 154 | set(_SDL2_NO_DEFAULT_PATH ON) 155 | endif() 156 | 157 | set(SDL2_NO_DEFAULT_PATH ${_SDL2_NO_DEFAULT_PATH} 158 | CACHE BOOL "Disable search SDL2 Library in default path") 159 | unset(_SDL2_NO_DEFAULT_PATH) 160 | 161 | set(SDL2_NO_DEFAULT_PATH_CMD) 162 | if(SDL2_NO_DEFAULT_PATH) 163 | set(SDL2_NO_DEFAULT_PATH_CMD NO_DEFAULT_PATH) 164 | endif() 165 | 166 | # Search for the SDL2 include directory 167 | find_path(SDL2_INCLUDE_DIR SDL.h 168 | HINTS 169 | ENV SDL2DIR 170 | ${SDL2_NO_DEFAULT_PATH_CMD} 171 | PATH_SUFFIXES SDL2 172 | # path suffixes to search inside ENV{SDL2DIR} 173 | include/SDL2 include 174 | PATHS ${SDL2_PATH} 175 | DOC "Where the SDL2 headers can be found" 176 | ) 177 | 178 | set(SDL2_INCLUDE_DIRS "${SDL2_INCLUDE_DIR}") 179 | 180 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 181 | set(VC_LIB_PATH_SUFFIX lib/x64) 182 | else() 183 | set(VC_LIB_PATH_SUFFIX lib/x86) 184 | endif() 185 | 186 | # SDL-2.0 is the name used by FreeBSD ports... 187 | # don't confuse it for the version number. 188 | find_library(SDL2_LIBRARY 189 | NAMES SDL2 SDL-2.0 190 | HINTS 191 | ENV SDL2DIR 192 | ${SDL2_NO_DEFAULT_PATH_CMD} 193 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 194 | PATHS ${SDL2_PATH} 195 | DOC "Where the SDL2 Library can be found" 196 | ) 197 | 198 | set(SDL2_LIBRARIES "${SDL2_LIBRARY}") 199 | 200 | if(NOT SDL2_BUILDING_LIBRARY) 201 | if(NOT SDL2_INCLUDE_DIR MATCHES ".framework") 202 | # Non-OS X framework versions expect you to also dynamically link to 203 | # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms 204 | # seem to provide SDL2main for compatibility even though they don't 205 | # necessarily need it. 206 | 207 | if(SDL2_PATH) 208 | set(SDL2MAIN_LIBRARY_PATHS "${SDL2_PATH}") 209 | endif() 210 | 211 | if(NOT SDL2_NO_DEFAULT_PATH) 212 | set(SDL2MAIN_LIBRARY_PATHS 213 | /sw 214 | /opt/local 215 | /opt/csw 216 | /opt 217 | "${SDL2MAIN_LIBRARY_PATHS}" 218 | ) 219 | endif() 220 | 221 | find_library(SDL2MAIN_LIBRARY 222 | NAMES SDL2main 223 | HINTS 224 | ENV SDL2DIR 225 | ${SDL2_NO_DEFAULT_PATH_CMD} 226 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 227 | PATHS ${SDL2MAIN_LIBRARY_PATHS} 228 | DOC "Where the SDL2main library can be found" 229 | ) 230 | unset(SDL2MAIN_LIBRARY_PATHS) 231 | endif() 232 | endif() 233 | 234 | # SDL2 may require threads on your system. 235 | # The Apple build may not need an explicit flag because one of the 236 | # frameworks may already provide it. 237 | # But for non-OSX systems, I will use the CMake Threads package. 238 | if(NOT APPLE) 239 | find_package(Threads QUIET) 240 | if(NOT Threads_FOUND) 241 | set(SDL2_THREADS_NOT_FOUND "Could NOT find Threads (Threads is required by SDL2).") 242 | if(SDL2_FIND_REQUIRED) 243 | message(FATAL_ERROR ${SDL2_THREADS_NOT_FOUND}) 244 | else() 245 | if(NOT SDL2_FIND_QUIETLY) 246 | message(STATUS ${SDL2_THREADS_NOT_FOUND}) 247 | endif() 248 | return() 249 | endif() 250 | unset(SDL2_THREADS_NOT_FOUND) 251 | endif() 252 | endif() 253 | 254 | # MinGW needs an additional link flag, -mwindows 255 | # It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -mwindows 256 | if(MINGW) 257 | set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW") 258 | endif() 259 | 260 | if(SDL2_LIBRARY) 261 | # For SDL2main 262 | if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY) 263 | list(FIND SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX) 264 | if(_SDL2_MAIN_INDEX EQUAL -1) 265 | set(SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARIES}) 266 | endif() 267 | unset(_SDL2_MAIN_INDEX) 268 | endif() 269 | 270 | # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. 271 | # CMake doesn't display the -framework Cocoa string in the UI even 272 | # though it actually is there if I modify a pre-used variable. 273 | # I think it has something to do with the CACHE STRING. 274 | # So I use a temporary variable until the end so I can set the 275 | # "real" variable in one-shot. 276 | if(APPLE) 277 | set(SDL2_LIBRARIES ${SDL2_LIBRARIES} -framework Cocoa) 278 | endif() 279 | 280 | # For threads, as mentioned Apple doesn't need this. 281 | # In fact, there seems to be a problem if I used the Threads package 282 | # and try using this line, so I'm just skipping it entirely for OS X. 283 | if(NOT APPLE) 284 | set(SDL2_LIBRARIES ${SDL2_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) 285 | endif() 286 | 287 | # For MinGW library 288 | if(MINGW) 289 | set(SDL2_LIBRARIES ${MINGW32_LIBRARY} ${SDL2_LIBRARIES}) 290 | endif() 291 | 292 | endif() 293 | 294 | # Read SDL2 version 295 | if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL_version.h") 296 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+[0-9]+$") 297 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MINOR_VERSION[ \t]+[0-9]+$") 298 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_PATCHLEVEL[ \t]+[0-9]+$") 299 | string(REGEX REPLACE "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}") 300 | string(REGEX REPLACE "^#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}") 301 | string(REGEX REPLACE "^#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}") 302 | set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}) 303 | unset(SDL2_VERSION_MAJOR_LINE) 304 | unset(SDL2_VERSION_MINOR_LINE) 305 | unset(SDL2_VERSION_PATCH_LINE) 306 | unset(SDL2_VERSION_MAJOR) 307 | unset(SDL2_VERSION_MINOR) 308 | unset(SDL2_VERSION_PATCH) 309 | endif() 310 | 311 | include(FindPackageHandleStandardArgs) 312 | 313 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 314 | REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR 315 | VERSION_VAR SDL2_VERSION_STRING) 316 | 317 | if(SDL2MAIN_LIBRARY) 318 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2main 319 | REQUIRED_VARS SDL2MAIN_LIBRARY SDL2_INCLUDE_DIR 320 | VERSION_VAR SDL2_VERSION_STRING) 321 | endif() 322 | 323 | 324 | mark_as_advanced(SDL2_PATH 325 | SDL2_NO_DEFAULT_PATH 326 | SDL2_LIBRARY 327 | SDL2MAIN_LIBRARY 328 | SDL2_INCLUDE_DIR 329 | SDL2_BUILDING_LIBRARY) 330 | 331 | 332 | # SDL2:: targets (SDL2::Core and SDL2::Main) 333 | if(SDL2_FOUND) 334 | 335 | # SDL2::Core target 336 | if(SDL2_LIBRARY AND NOT TARGET SDL2::Core) 337 | add_library(SDL2::Core UNKNOWN IMPORTED) 338 | set_target_properties(SDL2::Core PROPERTIES 339 | IMPORTED_LOCATION "${SDL2_LIBRARY}" 340 | INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}") 341 | 342 | if(APPLE) 343 | # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. 344 | # For more details, please see above. 345 | set_property(TARGET SDL2::Core APPEND PROPERTY 346 | INTERFACE_LINK_OPTIONS -framework Cocoa) 347 | else() 348 | # For threads, as mentioned Apple doesn't need this. 349 | # For more details, please see above. 350 | set_property(TARGET SDL2::Core APPEND PROPERTY 351 | INTERFACE_LINK_LIBRARIES Threads::Threads) 352 | endif() 353 | endif() 354 | 355 | # SDL2::Main target 356 | # Applications should link to SDL2::Main instead of SDL2::Core 357 | # For more details, please see above. 358 | if(NOT SDL2_BUILDING_LIBRARY AND NOT TARGET SDL2::Main) 359 | 360 | if(SDL2_INCLUDE_DIR MATCHES ".framework" OR NOT SDL2MAIN_LIBRARY) 361 | add_library(SDL2::Main INTERFACE IMPORTED) 362 | set_property(TARGET SDL2::Main PROPERTY 363 | INTERFACE_LINK_LIBRARIES SDL2::Core) 364 | elseif(SDL2MAIN_LIBRARY) 365 | # MinGW requires that the mingw32 library is specified before the 366 | # libSDL2main.a static library when linking. 367 | # The SDL2::MainInternal target is used internally to make sure that 368 | # CMake respects this condition. 369 | add_library(SDL2::MainInternal UNKNOWN IMPORTED) 370 | set_property(TARGET SDL2::MainInternal PROPERTY 371 | IMPORTED_LOCATION "${SDL2MAIN_LIBRARY}") 372 | set_property(TARGET SDL2::MainInternal PROPERTY 373 | INTERFACE_LINK_LIBRARIES SDL2::Core) 374 | 375 | add_library(SDL2::Main INTERFACE IMPORTED) 376 | 377 | if(MINGW) 378 | # MinGW needs an additional link flag '-mwindows' and link to mingw32 379 | set_property(TARGET SDL2::Main PROPERTY 380 | INTERFACE_LINK_LIBRARIES "mingw32" "-mwindows") 381 | endif() 382 | 383 | set_property(TARGET SDL2::Main APPEND PROPERTY 384 | INTERFACE_LINK_LIBRARIES SDL2::MainInternal) 385 | endif() 386 | 387 | endif() 388 | endif() 389 | -------------------------------------------------------------------------------- /cmake/FindSDL3.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | # Copyright 2019 Amine Ben Hassouna 5 | # Copyright 2000-2019 Kitware, Inc. and Contributors 6 | # All rights reserved. 7 | 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions 10 | # are met: 11 | 12 | # * Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | 15 | # * Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | 19 | # * Neither the name of Kitware, Inc. nor the names of Contributors 20 | # may be used to endorse or promote products derived from this 21 | # software without specific prior written permission. 22 | 23 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | #[=======================================================================[.rst: 36 | FindSDL3 37 | -------- 38 | 39 | Locate SDL3 library 40 | 41 | This module defines the following 'IMPORTED' targets: 42 | 43 | :: 44 | 45 | SDL3::Core 46 | The SDL3 library, if found. 47 | Libraries should link to SDL3::Core 48 | 49 | SDL3::Main 50 | The SDL3main library, if found. 51 | Applications should link to SDL3::Main instead of SDL3::Core 52 | 53 | 54 | 55 | This module will set the following variables in your project: 56 | 57 | :: 58 | 59 | SDL3_LIBRARIES, the name of the library to link against 60 | SDL3_INCLUDE_DIRS, where to find SDL.h 61 | SDL3_FOUND, if false, do not try to link to SDL3 62 | SDL3MAIN_FOUND, if false, do not try to link to SDL3main 63 | SDL3_VERSION_STRING, human-readable string containing the version of SDL3 64 | 65 | 66 | 67 | This module responds to the following cache variables: 68 | 69 | :: 70 | 71 | SDL3_PATH 72 | Set a custom SDL3 Library path (default: empty) 73 | 74 | SDL3_NO_DEFAULT_PATH 75 | Disable search SDL3 Library in default path. 76 | If SDL3_PATH (default: ON) 77 | Else (default: OFF) 78 | 79 | SDL3_INCLUDE_DIR 80 | SDL3 headers path. 81 | 82 | SDL3_LIBRARY 83 | SDL3 Library (.dll, .so, .a, etc) path. 84 | 85 | SDL3MAIN_LIBRAY 86 | SDL3main Library (.a) path. 87 | 88 | SDL3_BUILDING_LIBRARY 89 | This flag is useful only when linking to SDL3_LIBRARIES insead of 90 | SDL3::Main. It is required only when building a library that links to 91 | SDL3_LIBRARIES, because only applications need main() (No need to also 92 | link to SDL3main). 93 | If this flag is defined, then no SDL3main will be added to SDL3_LIBRARIES 94 | and no SDL3::Main target will be created. 95 | 96 | 97 | Don't forget to include SDLmain.h and SDLmain.m in your project for the 98 | OS X framework based version. (Other versions link to -lSDL3main which 99 | this module will try to find on your behalf.) Also for OS X, this 100 | module will automatically add the -framework Cocoa on your behalf. 101 | 102 | 103 | Additional Note: If you see an empty SDL3_LIBRARY in your project 104 | configuration, it means CMake did not find your SDL3 library 105 | (SDL3.dll, libsdl3.so, SDL3.framework, etc). Set SDL3_LIBRARY to point 106 | to your SDL3 library, and configure again. Similarly, if you see an 107 | empty SDL3MAIN_LIBRARY, you should set this value as appropriate. These 108 | values are used to generate the final SDL3_LIBRARIES variable and the 109 | SDL3::Core and SDL3::Main targets, but when these values are unset, 110 | SDL3_LIBRARIES, SDL3::Core and SDL3::Main does not get created. 111 | 112 | 113 | $SDL3DIR is an environment variable that would correspond to the 114 | ./configure --prefix=$SDL3DIR used in building SDL3. l.e.galup 9-20-02 115 | 116 | 117 | 118 | Created by Amine Ben Hassouna: 119 | Adapt FindSDL.cmake to SDL3 (FindSDL3.cmake). 120 | Add cache variables for more flexibility: 121 | SDL3_PATH, SDL3_NO_DEFAULT_PATH (for details, see doc above). 122 | Mark 'Threads' as a required dependency for non-OSX systems. 123 | Modernize the FindSDL3.cmake module by creating specific targets: 124 | SDL3::Core and SDL3::Main (for details, see doc above). 125 | 126 | 127 | Original FindSDL.cmake module: 128 | Modified by Eric Wing. Added code to assist with automated building 129 | by using environmental variables and providing a more 130 | controlled/consistent search behavior. Added new modifications to 131 | recognize OS X frameworks and additional Unix paths (FreeBSD, etc). 132 | Also corrected the header search path to follow "proper" SDL 133 | guidelines. Added a search for SDLmain which is needed by some 134 | platforms. Added a search for threads which is needed by some 135 | platforms. Added needed compile switches for MinGW. 136 | 137 | On OSX, this will prefer the Framework version (if found) over others. 138 | People will have to manually change the cache value of SDL3_LIBRARY to 139 | override this selection or set the SDL3_PATH variable or the CMake 140 | environment CMAKE_INCLUDE_PATH to modify the search paths. 141 | 142 | Note that the header path has changed from SDL/SDL.h to just SDL.h 143 | This needed to change because "proper" SDL convention is #include 144 | "SDL.h", not . This is done for portability reasons 145 | because not all systems place things in SDL/ (see FreeBSD). 146 | #]=======================================================================] 147 | 148 | # Define options for searching SDL3 Library in a custom path 149 | 150 | set(SDL3_PATH "" CACHE STRING "Custom SDL3 Library path") 151 | 152 | set(_SDL3_NO_DEFAULT_PATH OFF) 153 | if(SDL3_PATH) 154 | set(_SDL3_NO_DEFAULT_PATH ON) 155 | endif() 156 | 157 | set(SDL3_NO_DEFAULT_PATH ${_SDL3_NO_DEFAULT_PATH} 158 | CACHE BOOL "Disable search SDL3 Library in default path") 159 | unset(_SDL3_NO_DEFAULT_PATH) 160 | 161 | set(SDL3_NO_DEFAULT_PATH_CMD) 162 | if(SDL3_NO_DEFAULT_PATH) 163 | set(SDL3_NO_DEFAULT_PATH_CMD NO_DEFAULT_PATH) 164 | endif() 165 | 166 | # Search for the SDL3 include directory 167 | find_path(SDL3_INCLUDE_DIR SDL.h 168 | HINTS 169 | ENV SDL3DIR 170 | ${SDL3_NO_DEFAULT_PATH_CMD} 171 | PATH_SUFFIXES SDL3 172 | # path suffixes to search inside ENV{SDL3DIR} 173 | include/SDL3 include 174 | PATHS ${SDL3_PATH} 175 | DOC "Where the SDL3 headers can be found" 176 | ) 177 | 178 | set(SDL3_INCLUDE_DIRS "${SDL3_INCLUDE_DIR}") 179 | 180 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 181 | set(VC_LIB_PATH_SUFFIX lib/x64) 182 | else() 183 | set(VC_LIB_PATH_SUFFIX lib/x86) 184 | endif() 185 | 186 | # SDL-2.0 is the name used by FreeBSD ports... 187 | # don't confuse it for the version number. 188 | find_library(SDL3_LIBRARY 189 | NAMES SDL3 SDL-2.0 190 | HINTS 191 | ENV SDL3DIR 192 | ${SDL3_NO_DEFAULT_PATH_CMD} 193 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 194 | PATHS ${SDL3_PATH} 195 | DOC "Where the SDL3 Library can be found" 196 | ) 197 | 198 | set(SDL3_LIBRARIES "${SDL3_LIBRARY}") 199 | 200 | if(NOT SDL3_BUILDING_LIBRARY) 201 | if(NOT SDL3_INCLUDE_DIR MATCHES ".framework") 202 | # Non-OS X framework versions expect you to also dynamically link to 203 | # SDL3main. This is mainly for Windows and OS X. Other (Unix) platforms 204 | # seem to provide SDL3main for compatibility even though they don't 205 | # necessarily need it. 206 | 207 | if(SDL3_PATH) 208 | set(SDL3MAIN_LIBRARY_PATHS "${SDL3_PATH}") 209 | endif() 210 | 211 | if(NOT SDL3_NO_DEFAULT_PATH) 212 | set(SDL3MAIN_LIBRARY_PATHS 213 | /sw 214 | /opt/local 215 | /opt/csw 216 | /opt 217 | "${SDL3MAIN_LIBRARY_PATHS}" 218 | ) 219 | endif() 220 | 221 | find_library(SDL3MAIN_LIBRARY 222 | NAMES SDL3main 223 | HINTS 224 | ENV SDL3DIR 225 | ${SDL3_NO_DEFAULT_PATH_CMD} 226 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 227 | PATHS ${SDL3MAIN_LIBRARY_PATHS} 228 | DOC "Where the SDL3main library can be found" 229 | ) 230 | unset(SDL3MAIN_LIBRARY_PATHS) 231 | endif() 232 | endif() 233 | 234 | # SDL3 may require threads on your system. 235 | # The Apple build may not need an explicit flag because one of the 236 | # frameworks may already provide it. 237 | # But for non-OSX systems, I will use the CMake Threads package. 238 | if(NOT APPLE) 239 | find_package(Threads QUIET) 240 | if(NOT Threads_FOUND) 241 | set(SDL3_THREADS_NOT_FOUND "Could NOT find Threads (Threads is required by SDL3).") 242 | if(SDL3_FIND_REQUIRED) 243 | message(FATAL_ERROR ${SDL3_THREADS_NOT_FOUND}) 244 | else() 245 | if(NOT SDL3_FIND_QUIETLY) 246 | message(STATUS ${SDL3_THREADS_NOT_FOUND}) 247 | endif() 248 | return() 249 | endif() 250 | unset(SDL3_THREADS_NOT_FOUND) 251 | endif() 252 | endif() 253 | 254 | # MinGW needs an additional link flag, -mwindows 255 | # It's total link flags should look like -lmingw32 -lSDL3main -lSDL3 -mwindows 256 | if(MINGW) 257 | set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW") 258 | endif() 259 | 260 | if(SDL3_LIBRARY) 261 | # For SDL3main 262 | if(SDL3MAIN_LIBRARY AND NOT SDL3_BUILDING_LIBRARY) 263 | list(FIND SDL3_LIBRARIES "${SDL3MAIN_LIBRARY}" _SDL3_MAIN_INDEX) 264 | if(_SDL3_MAIN_INDEX EQUAL -1) 265 | set(SDL3_LIBRARIES "${SDL3MAIN_LIBRARY}" ${SDL3_LIBRARIES}) 266 | endif() 267 | unset(_SDL3_MAIN_INDEX) 268 | endif() 269 | 270 | # For OS X, SDL3 uses Cocoa as a backend so it must link to Cocoa. 271 | # CMake doesn't display the -framework Cocoa string in the UI even 272 | # though it actually is there if I modify a pre-used variable. 273 | # I think it has something to do with the CACHE STRING. 274 | # So I use a temporary variable until the end so I can set the 275 | # "real" variable in one-shot. 276 | if(APPLE) 277 | set(SDL3_LIBRARIES ${SDL3_LIBRARIES} -framework Cocoa) 278 | endif() 279 | 280 | # For threads, as mentioned Apple doesn't need this. 281 | # In fact, there seems to be a problem if I used the Threads package 282 | # and try using this line, so I'm just skipping it entirely for OS X. 283 | if(NOT APPLE) 284 | set(SDL3_LIBRARIES ${SDL3_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) 285 | endif() 286 | 287 | # For MinGW library 288 | if(MINGW) 289 | set(SDL3_LIBRARIES ${MINGW32_LIBRARY} ${SDL3_LIBRARIES}) 290 | endif() 291 | 292 | endif() 293 | 294 | # Read SDL3 version 295 | if(SDL3_INCLUDE_DIR AND EXISTS "${SDL3_INCLUDE_DIR}/SDL_version.h") 296 | file(STRINGS "${SDL3_INCLUDE_DIR}/SDL_version.h" SDL3_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+[0-9]+$") 297 | file(STRINGS "${SDL3_INCLUDE_DIR}/SDL_version.h" SDL3_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MINOR_VERSION[ \t]+[0-9]+$") 298 | file(STRINGS "${SDL3_INCLUDE_DIR}/SDL_version.h" SDL3_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_PATCHLEVEL[ \t]+[0-9]+$") 299 | string(REGEX REPLACE "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL3_VERSION_MAJOR "${SDL3_VERSION_MAJOR_LINE}") 300 | string(REGEX REPLACE "^#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL3_VERSION_MINOR "${SDL3_VERSION_MINOR_LINE}") 301 | string(REGEX REPLACE "^#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL3_VERSION_PATCH "${SDL3_VERSION_PATCH_LINE}") 302 | set(SDL3_VERSION_STRING ${SDL3_VERSION_MAJOR}.${SDL3_VERSION_MINOR}.${SDL3_VERSION_PATCH}) 303 | unset(SDL3_VERSION_MAJOR_LINE) 304 | unset(SDL3_VERSION_MINOR_LINE) 305 | unset(SDL3_VERSION_PATCH_LINE) 306 | unset(SDL3_VERSION_MAJOR) 307 | unset(SDL3_VERSION_MINOR) 308 | unset(SDL3_VERSION_PATCH) 309 | endif() 310 | 311 | include(FindPackageHandleStandardArgs) 312 | 313 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL3 314 | REQUIRED_VARS SDL3_LIBRARY SDL3_INCLUDE_DIR 315 | VERSION_VAR SDL3_VERSION_STRING) 316 | 317 | if(SDL3MAIN_LIBRARY) 318 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL3main 319 | REQUIRED_VARS SDL3MAIN_LIBRARY SDL3_INCLUDE_DIR 320 | VERSION_VAR SDL3_VERSION_STRING) 321 | endif() 322 | 323 | 324 | mark_as_advanced(SDL3_PATH 325 | SDL3_NO_DEFAULT_PATH 326 | SDL3_LIBRARY 327 | SDL3MAIN_LIBRARY 328 | SDL3_INCLUDE_DIR 329 | SDL3_BUILDING_LIBRARY) 330 | 331 | 332 | # SDL3:: targets (SDL3::Core and SDL3::Main) 333 | if(SDL3_FOUND) 334 | 335 | # SDL3::Core target 336 | if(SDL3_LIBRARY AND NOT TARGET SDL3::Core) 337 | add_library(SDL3::Core UNKNOWN IMPORTED) 338 | set_target_properties(SDL3::Core PROPERTIES 339 | IMPORTED_LOCATION "${SDL3_LIBRARY}" 340 | INTERFACE_INCLUDE_DIRECTORIES "${SDL3_INCLUDE_DIR}") 341 | 342 | if(APPLE) 343 | # For OS X, SDL3 uses Cocoa as a backend so it must link to Cocoa. 344 | # For more details, please see above. 345 | set_property(TARGET SDL3::Core APPEND PROPERTY 346 | INTERFACE_LINK_OPTIONS -framework Cocoa) 347 | else() 348 | # For threads, as mentioned Apple doesn't need this. 349 | # For more details, please see above. 350 | set_property(TARGET SDL3::Core APPEND PROPERTY 351 | INTERFACE_LINK_LIBRARIES Threads::Threads) 352 | endif() 353 | endif() 354 | 355 | # SDL3::Main target 356 | # Applications should link to SDL3::Main instead of SDL3::Core 357 | # For more details, please see above. 358 | if(NOT SDL3_BUILDING_LIBRARY AND NOT TARGET SDL3::Main) 359 | 360 | if(SDL3_INCLUDE_DIR MATCHES ".framework" OR NOT SDL3MAIN_LIBRARY) 361 | add_library(SDL3::Main INTERFACE IMPORTED) 362 | set_property(TARGET SDL3::Main PROPERTY 363 | INTERFACE_LINK_LIBRARIES SDL3::Core) 364 | elseif(SDL3MAIN_LIBRARY) 365 | # MinGW requires that the mingw32 library is specified before the 366 | # libSDL3main.a static library when linking. 367 | # The SDL3::MainInternal target is used internally to make sure that 368 | # CMake respects this condition. 369 | add_library(SDL3::MainInternal UNKNOWN IMPORTED) 370 | set_property(TARGET SDL3::MainInternal PROPERTY 371 | IMPORTED_LOCATION "${SDL3MAIN_LIBRARY}") 372 | set_property(TARGET SDL3::MainInternal PROPERTY 373 | INTERFACE_LINK_LIBRARIES SDL3::Core) 374 | 375 | add_library(SDL3::Main INTERFACE IMPORTED) 376 | 377 | if(MINGW) 378 | # MinGW needs an additional link flag '-mwindows' and link to mingw32 379 | set_property(TARGET SDL3::Main PROPERTY 380 | INTERFACE_LINK_LIBRARIES "mingw32" "-mwindows") 381 | endif() 382 | 383 | set_property(TARGET SDL3::Main APPEND PROPERTY 384 | INTERFACE_LINK_LIBRARIES SDL3::MainInternal) 385 | endif() 386 | 387 | endif() 388 | endif() 389 | -------------------------------------------------------------------------------- /cmake/FindSWRESAMPLE.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | find_package(PkgConfig) 3 | if (PkgConfig_FOUND) 4 | pkg_check_modules(PC_SWRESAMPLE QUIET IMPORTED_TARGET GLOBAL libswresample) 5 | endif() 6 | 7 | if (PC_SWRESAMPLE_FOUND) 8 | set(SWRESAMPLE_FOUND TRUE) 9 | set(SWRESAMPLE_VERSION ${PC_SWRESAMPLE_VERSION}) 10 | set(SWRESAMPLE_VERSION_STRING ${PC_SWRESAMPLE_STRING}) 11 | set(SWRESAMPLE_LIBRARYS ${PC_SWRESAMPLE_LIBRARIES}) 12 | if (USE_STATIC_LIBS) 13 | set(SWRESAMPLE_INCLUDE_DIRS ${PC_SWRESAMPLE_STATIC_INCLUDE_DIRS}) 14 | else() 15 | set(SWRESAMPLE_INCLUDE_DIRS ${PC_SWRESAMPLE_INCLUDE_DIRS}) 16 | endif() 17 | if (NOT SWRESAMPLE_INCLUDE_DIRS) 18 | find_path(SWRESAMPLE_INCLUDE_DIRS NAMES libswresample/swresample.h) 19 | if (SWRESAMPLE_INCLUDE_DIRS) 20 | target_include_directories(PkgConfig::PC_SWRESAMPLE INTERFACE ${SWRESAMPLE_INCLUDE_DIRS}) 21 | endif() 22 | endif() 23 | if (NOT TARGET SWRESAMPLE::SWRESAMPLE) 24 | add_library(SWRESAMPLE::SWRESAMPLE ALIAS PkgConfig::PC_SWRESAMPLE) 25 | endif() 26 | else() 27 | message(FATAL_ERROR "failed.") 28 | endif() 29 | 30 | include(FindPackageHandleStandardArgs) 31 | find_package_handle_standard_args(SWRESAMPLE 32 | FOUND_VAR SWRESAMPLE_FOUND 33 | REQUIRED_VARS 34 | SWRESAMPLE_LIBRARYS 35 | SWRESAMPLE_INCLUDE_DIRS 36 | VERSION_VAR SWRESAMPLE_VERSION 37 | ) 38 | -------------------------------------------------------------------------------- /ffmpeg_core.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_FFMPEG_CORE_H 2 | #define _MUSICPLAYER2_FFMPEG_CORE_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if _WIN32 12 | #define CORE_CHAR wchar_t 13 | #define CCORE_CHAR(x) L##x 14 | #else 15 | #define CORE_CHAR char 16 | #define CCORE_CHAR(x) x 17 | #endif 18 | 19 | #if _WIN32 20 | #if BUILD_FFMPEG_CORE 21 | #define FFMPEG_CORE_API __declspec(dllexport) 22 | #else 23 | #define FFMPEG_CORE_API __declspec(dllimport) 24 | #endif 25 | #elif defined(__GNUC__) 26 | #define FFMPEG_CORE_API __attribute__((visibility("default"))) 27 | #else 28 | #define FFMPEG_CORE_API 29 | #endif 30 | typedef struct MusicHandle MusicHandle; 31 | typedef struct MusicInfoHandle MusicInfoHandle; 32 | typedef struct FfmpegCoreSettings FfmpegCoreSettings; 33 | typedef struct DeviceNameList { 34 | char* device; 35 | struct DeviceNameList* prev; 36 | struct DeviceNameList* next; 37 | } DeviceNameList; 38 | // 负数即为来自ffmpeg的错误 39 | 40 | #define FFMPEG_CORE_ERR_OK 0 41 | #define FFMPEG_CORE_ERR_NULLPTR 1 42 | #define FFMPEG_CORE_ERR_INVAILD_NAME 2 43 | #define FFMPEG_CORE_ERR_OOM 3 44 | #define FFMPEG_CORE_ERR_NO_AUDIO_OR_DECODER 4 45 | #define FFMPEG_CORE_ERR_UNKNOWN_SAMPLE_FMT 5 46 | #define FFMPEG_CORE_ERR_SDL 6 47 | #define FFMPEG_CORE_ERR_FAILED_CREATE_THREAD 7 48 | #define FFMPEG_CORE_ERR_FAILED_CREATE_MUTEX 8 49 | #define FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED 9 50 | #define FFMPEG_CORE_ERR_NO_AUDIO 10 51 | #define FFMPEG_CORE_ERR_FAILED_SET_VOLUME 11 52 | #define FFMPEG_CORE_ERR_FAILED_SET_SPEED 12 53 | #define FFMPEG_CORE_ERR_TOO_BIG_FFT_DATA_LEN 13 54 | #define FFMPEG_CORE_ERR_FAILED_OPEN_FILE 14 55 | #define FFMPEG_CORE_ERR_FAILED_READ_FILE 15 56 | #define FFMPEG_CORE_ERR_INVALID_CDA_FILE 16 57 | #define FFMPEG_CORE_ERR_NO_LIBCDIO 17 58 | #define FFMEPG_CORE_ERR_FAILED_PARSE_URL 18 59 | #define FFMPEG_CORE_ERR_FAILED_SET_EQUALIZER_CHANNEL 19 60 | #define FFMPEG_CORE_ERR_FAILED_INIT_WASAPI 20 61 | #define FFMEPG_CORE_ERR_FAILED_GET_WASAPI_DEVICE_PROP 21 62 | #define FFMPEG_CORE_ERR_WASAPI_NOT_INITED 22 63 | #define FFMPEG_CORE_ERR_WASAPI_DEVICE_NOT_FOUND 23 64 | #define FFMPEG_CORE_ERR_WASAPI_FAILED_OPEN_DEVICE 24 65 | #define FFMPEG_CORE_ERR_INVALID_DEVICE_NAME 25 66 | #define FFMPEG_CORE_ERR_WASAPI_NO_SUITABLE_FORMAT 26 67 | #define FFMPEG_CORE_ERR_FAILED_CREATE_EVENT 27 68 | #define FFMPEG_CORE_ERR_TIMEOUT 28 69 | #define FFMPEG_CORE_ERR_WAIT_TIMEOUT 29 70 | #define REVERB_TYPE_OFF 0 71 | #define REVERB_TYPE_ROOM 1 72 | #define REVERB_TYPE_MAX 1 73 | #define MATRIX_ENCODING_NONE 0 74 | #define MATRIX_ENCODING_DOLBY 1 75 | #define MATRIX_ENCODING_DPLII 2 76 | #define MATRIX_ENCODING_DPLIIX 3 77 | #define MATRIX_ENCODING_DPLIIZ 4 78 | #define MATRIX_ENCODING_DOLBYEX 5 79 | #define MATRIX_ENCODING_DOLBYHEADPHONE 6 80 | #define MATRIX_ENCODING_NB 7 81 | FFMPEG_CORE_API void free_music_handle(MusicHandle* handle); 82 | FFMPEG_CORE_API void free_music_info_handle(MusicInfoHandle* handle); 83 | FFMPEG_CORE_API void free_ffmpeg_core_settings(FfmpegCoreSettings* s); 84 | FFMPEG_CORE_API void free_device_name_list(DeviceNameList** list); 85 | FFMPEG_CORE_API void ffmpeg_core_free(void* data); 86 | FFMPEG_CORE_API void* ffmpeg_core_malloc(size_t size); 87 | FFMPEG_CORE_API void* ffmpeg_core_realloc(void* data, size_t size); 88 | /// 即 av_log_format_line2 89 | FFMPEG_CORE_API int ffmpeg_core_log_format_line(void* ptr, int level, const char* fmt, va_list vl, char* line, int line_size, int* print_prefix); 90 | /// 即 av_log_set_callback 91 | FFMPEG_CORE_API void ffmpeg_core_log_set_callback(void(*callback)(void*, int, const char*, va_list)); 92 | /// 即 av_log_set_flags 93 | FFMPEG_CORE_API void ffmpeg_core_log_set_flags(int arg); 94 | FFMPEG_CORE_API const char* ffmpeg_core_version_str(); 95 | FFMPEG_CORE_API int32_t ffmpeg_core_version(); 96 | FFMPEG_CORE_API int ffmpeg_core_is_wasapi_supported(); 97 | FFMPEG_CORE_API void ffmpeg_core_dump_library_version(int use_av_log, int av_log_level); 98 | FFMPEG_CORE_API void ffmpeg_core_dump_ffmpeg_configuration(int use_av_log, int av_log_level); 99 | FFMPEG_CORE_API int ffmpeg_core_open(const CORE_CHAR* url, MusicHandle** handle); 100 | FFMPEG_CORE_API int ffmpeg_core_open2(const CORE_CHAR* url, MusicHandle** handle, FfmpegCoreSettings* s); 101 | FFMPEG_CORE_API int ffmpeg_core_open3(const CORE_CHAR* url, MusicHandle** handle, FfmpegCoreSettings* s, const CORE_CHAR* device); 102 | FFMPEG_CORE_API int ffmpeg_core_info_open(const CORE_CHAR* url, MusicInfoHandle** handle); 103 | FFMPEG_CORE_API int ffmpeg_core_play(MusicHandle* handle); 104 | FFMPEG_CORE_API int ffmpeg_core_pause(MusicHandle* handle); 105 | FFMPEG_CORE_API int ffmpeg_core_seek(MusicHandle* handle, int64_t time); 106 | FFMPEG_CORE_API int ffmpeg_core_set_volume(MusicHandle* handle, int volume); 107 | FFMPEG_CORE_API int ffmpeg_core_set_speed(MusicHandle* handle, float speed); 108 | FFMPEG_CORE_API int ffmpeg_core_set_equalizer_channel(MusicHandle* handle, int channel, int gain); 109 | FFMPEG_CORE_API int ffmpeg_core_set_reverb(MusicHandle* handle, int type, float mix, float time); 110 | FFMPEG_CORE_API int ffmpeg_core_get_error(MusicHandle* handle); 111 | /** 112 | * @brief 返回错误代码对应的错误消息 113 | * @param err 错误代码 114 | * @return 错误消息,需要调用free释放内存 115 | */ 116 | FFMPEG_CORE_API CORE_CHAR* ffmpeg_core_get_err_msg(int err); 117 | /** 118 | * @brief 返回错误代码对应的错误消息 119 | * @param err 错误代码(仅处理>=0的错误) 120 | * @return 错误消息 121 | */ 122 | FFMPEG_CORE_API const CORE_CHAR* ffmpeg_core_get_err_msg2(int err); 123 | /** 124 | * @brief 获取当前播放位置 125 | * @param handle Handle 126 | * @return 如果Handle为NULL,返回-1,反之返回以AV_TIME_BASE为基准的时间(1相当于1/1000000s) 127 | */ 128 | FFMPEG_CORE_API int64_t ffmpeg_core_get_cur_position(MusicHandle* handle); 129 | /** 130 | * @brief 是否已经播放完 131 | * @param handle Handle 132 | * @return 如果Handle为NULL或未播放完,返回0,反之返回1 133 | */ 134 | FFMPEG_CORE_API int ffmpeg_core_song_is_over(MusicHandle* handle); 135 | /** 136 | * @brief 获取长度(由demuxer回报,可能不准或者不存在) 137 | * @param handle Handle 138 | * @return 如果Handle为NULL,返回-1,反之返回以AV_TIME_BASE为基准的时间(1相当于1/1000000s) 139 | */ 140 | FFMPEG_CORE_API int64_t ffmpeg_core_get_song_length(MusicHandle* handle); 141 | FFMPEG_CORE_API int64_t ffmpeg_core_info_get_song_length(MusicInfoHandle* handle); 142 | /** 143 | * @brief 返回音频文件声道数(SDL可能会改如果音频设备不支持) 144 | * @param handle Handle 145 | * @return 如果Handle为NULL,返回-1,反之返回声道数 146 | */ 147 | FFMPEG_CORE_API int ffmpeg_core_get_channels(MusicHandle* handle); 148 | FFMPEG_CORE_API int ffmpeg_core_info_get_channels(MusicInfoHandle* handle); 149 | /** 150 | * @brief 返回音频文件采样频率(SDL可能会修改) 151 | * @param handle Handle 152 | * @return 如果Handle为NULL,返回-1,反之返回采样频率 153 | */ 154 | FFMPEG_CORE_API int ffmpeg_core_get_freq(MusicHandle* handle); 155 | FFMPEG_CORE_API int ffmpeg_core_info_get_freq(MusicInfoHandle* handle); 156 | /** 157 | * @brief 返回当前状态 158 | * @param handle Handle 159 | * @return 如果Handle为NULL,返回-1,正在播放返回1,未播放返回0 160 | */ 161 | FFMPEG_CORE_API int ffmpeg_core_is_playing(MusicHandle* handle); 162 | /** 163 | * @brief 返回位数 164 | * @param handle Handle 165 | * @return 如果Handle为NULL,返回-1 166 | */ 167 | FFMPEG_CORE_API int ffmpeg_core_get_bits(MusicHandle* handle); 168 | FFMPEG_CORE_API int ffmpeg_core_info_get_bits(MusicInfoHandle* handle); 169 | /** 170 | * @brief 返回比特率 171 | * @param handle Handle 172 | * @return 如果Handle为NULL,返回-1 173 | */ 174 | FFMPEG_CORE_API int64_t ffmpeg_core_get_bitrate(MusicHandle* handle); 175 | FFMPEG_CORE_API int64_t ffmpeg_core_info_get_bitrate(MusicInfoHandle* handle); 176 | /** 177 | * @brief 获取元数据 178 | * @param handle Handle 179 | * @param key 元数据Key 180 | * @return 结果,需要手动调用free释放内存 181 | */ 182 | FFMPEG_CORE_API CORE_CHAR* ffmpeg_core_get_metadata(MusicHandle* handle, const char* key); 183 | FFMPEG_CORE_API CORE_CHAR* ffmpeg_core_info_get_metadata(MusicInfoHandle* handle, const char* key); 184 | FFMPEG_CORE_API int ffmpeg_core_get_fft_data(MusicHandle* handle, float* fft_data, int len); 185 | FFMPEG_CORE_API FfmpegCoreSettings* ffmpeg_core_init_settings(); 186 | FFMPEG_CORE_API int ffmpeg_core_settings_set_volume(FfmpegCoreSettings* s, int volume); 187 | FFMPEG_CORE_API int ffmpeg_core_settings_set_speed(FfmpegCoreSettings* s, float speed); 188 | FFMPEG_CORE_API int ffmpeg_core_settings_set_cache_length(FfmpegCoreSettings* s, int length); 189 | FFMPEG_CORE_API int ffmpeg_core_settings_set_max_retry_count(FfmpegCoreSettings* s, int max_retry_count); 190 | FFMPEG_CORE_API int ffmpeg_core_settings_set_url_retry_interval(FfmpegCoreSettings* s, int url_retry_interval); 191 | FFMPEG_CORE_API int ffmpeg_core_settings_set_equalizer_channel(FfmpegCoreSettings* s, int channel, int gain); 192 | FFMPEG_CORE_API int ffmpeg_core_settings_set_use_WASAPI(FfmpegCoreSettings* s, int enable); 193 | FFMPEG_CORE_API int ffmpeg_core_settings_set_enable_exclusive(FfmpegCoreSettings* s, int enable); 194 | FFMPEG_CORE_API int ffmpeg_core_settings_set_max_wait_time(FfmpegCoreSettings* s, int timeout); 195 | FFMPEG_CORE_API int ffmpeg_core_settings_set_wasapi_min_buffer_time(FfmpegCoreSettings* s, int time); 196 | FFMPEG_CORE_API int ffmpeg_core_settings_set_reverb(FfmpegCoreSettings* s, int type, float mix, float time); 197 | FFMPEG_CORE_API int ffmpeg_core_settings_set_max_wait_buffer_time(FfmpegCoreSettings* s, int time); 198 | FFMPEG_CORE_API DeviceNameList* ffmpeg_core_get_audio_devices(); 199 | #ifdef __cplusplus 200 | } 201 | #endif 202 | #endif 203 | -------------------------------------------------------------------------------- /ffmpeg_core.rc.in: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef DEBUG 3 | #define VER_DEBUG 0 4 | #else 5 | #define VER_DEBUG VS_FF_DEBUG 6 | #endif 7 | 8 | VS_VERSION_INFO VERSIONINFO 9 | FILEVERSION @FFMPEG_CORE_VERSION_MAJOR@,@FFMPEG_CORE_VERSION_MINOR@,@FFMPEG_CORE_VERSION_MICRO@,@FFMPEG_CORE_VERSION_REV@ 10 | PRODUCTVERSION @FFMPEG_CORE_VERSION_MAJOR@,@FFMPEG_CORE_VERSION_MINOR@,@FFMPEG_CORE_VERSION_MICRO@,@FFMPEG_CORE_VERSION_REV@ 11 | FILEFLAGSMASK VS_FF_DEBUG 12 | FILEFLAGS VER_DEBUG 13 | FILEOS VOS__WINDOWS32 14 | FILETYPE VFT_DLL 15 | FILESUBTYPE VFT2_UNKNOWN 16 | BEGIN 17 | BLOCK "StringFileInfo" 18 | BEGIN 19 | BLOCK "04090000" 20 | BEGIN 21 | VALUE "CompanyName", "lifegpc\0" 22 | VALUE "FileDescription", "A music player core which use FFMPEG and SDL2.\0" 23 | VALUE "FileVersion", "@FFMPEG_CORE_VERSION@\0" 24 | VALUE "InternalName", "ffmpeg_core\0" 25 | VALUE "LegalCopyright", "Copyright (C) 2022 lifegpc\0" 26 | VALUE "OriginalFilename", "ffmpeg_core.dll\0" 27 | VALUE "ProductName", "ffmpeg_core\0" 28 | VALUE "ProductVersion", "@FFMPEG_CORE_VERSION@\0" 29 | END 30 | END 31 | BLOCK "VarFileInfo" 32 | BEGIN 33 | VALUE "Translation", 0x409, 0 34 | END 35 | END 36 | -------------------------------------------------------------------------------- /ffmpeg_core_config.h.in: -------------------------------------------------------------------------------- 1 | #cmakedefine HAVE_WASAPI @HAVE_WASAPI@ 2 | #cmakedefine HAVE_SDL3 @HAVE_SDL3@ 3 | #cmakedefine HAVE_PRINTF_S @HAVE_PRINTF_S@ 4 | #cmakedefine HAVE_PTHREAD_TRYJOIN_NP @HAVE_PTHREAD_TRYJOIN_NP@ 5 | -------------------------------------------------------------------------------- /ffmpeg_core_version.h.in: -------------------------------------------------------------------------------- 1 | #ifndef _FFMPEG_CORE_VERSION 2 | #define _FFMPEG_CORE_VERSION 3 | #define FFMPEG_CORE_VERSION_MAJOR @FFMPEG_CORE_VERSION_MAJOR@ 4 | #define FFMPEG_CORE_VERSION_MINOR @FFMPEG_CORE_VERSION_MINOR@ 5 | #define FFMPEG_CORE_VERSION_MICRO @FFMPEG_CORE_VERSION_MICRO@ 6 | #define FFMPEG_CORE_VERSION_REV @FFMPEG_CORE_VERSION_REV@ 7 | #define FFMPEG_CORE_VERSION "@FFMPEG_CORE_VERSION@" 8 | #define FFMPEG_CORE_VERSION_INT (((int32_t)FFMPEG_CORE_VERSION_MAJOR << 24) | ((int32_t)FFMPEG_CORE_VERSION_MINOR << 16) | ((int32_t)FFMPEG_CORE_VERSION_MICRO << 8) | (int32_t)FFMPEG_CORE_VERSION_REV) 9 | #endif 10 | -------------------------------------------------------------------------------- /patch/zlib-msvc/zconf.h.patch: -------------------------------------------------------------------------------- 1 | --- zconf.h.orig 2022-10-20 11:06:04 +0000 2 | +++ zconf.h 2022-10-20 11:06:56 +0000 3 | @@ -487,7 +487,7 @@ 4 | #endif 5 | #ifndef Z_SOLO 6 | # if defined(Z_HAVE_UNISTD_H) 7 | -# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ 8 | +//# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ 9 | # ifdef VMS 10 | # include /* for off_t */ 11 | # endif 12 | -------------------------------------------------------------------------------- /scripts/build_SDL2_mingw-x64.sh: -------------------------------------------------------------------------------- 1 | export PREFIX=`pwd`/clib 2 | mkdir -p cbuild && cd cbuild || exit 1 3 | curl -L "https://github.com/libsdl-org/SDL/releases/download/release-2.28.5/SDL2-2.28.5.tar.gz" -o SDL2.tar.gz || exit 1 4 | tar -xzvf SDL2.tar.gz || exit 1 5 | cd SDL2-* && mkdir -p build && cd build || exit 1 6 | cmake \ 7 | -G "MSYS Makefiles" \ 8 | -DCMAKE_BUILD_TYPE=Release \ 9 | -DSDL_SHARED=ON \ 10 | -DSDL_STATIC=OFF \ 11 | -DCMAKE_PREFIX_PATH=$PREFIX \ 12 | -DCMAKE_INSTALL_PREFIX=$PREFIX \ 13 | ../ || exit 1 14 | make -j8 || exit 1 15 | make -j8 install || exit 1 16 | -------------------------------------------------------------------------------- /scripts/build_SDL2_msvc-x64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET PREFIX=%CD%\clib 4 | SET PATH=%PREFIX%\bin;%PATH% 5 | IF NOT EXIST cbuild ( 6 | MD cbuild || EXIT /B 1 7 | ) 8 | CD cbuild || EXIT /B 1 9 | curl -L "https://github.com/libsdl-org/SDL/releases/download/release-2.28.5/SDL2-2.28.5.tar.gz" -o SDL2.tar.gz || EXIT /B %ERRORLEVEL% 10 | tar -xzvf SDL2.tar.gz || EXIT /B %ERRORLEVEL% 11 | CD SDL2-* || EXIT /B 1 12 | IF NOT EXIST build ( 13 | MD build || EXIT /B 1 14 | ) 15 | CD build || EXIT /B 1 16 | cmake ^ 17 | -DCMAKE_PREFIX_PATH=%PREFIX% ^ 18 | -DCMAKE_BUILD_TYPE=Release ^ 19 | -DCMAKE_INSTALL_PREFIX=%PREFIX% ^ 20 | -DSDL_SHARED=ON ^ 21 | -DSDL_STATIC=OFF ^ 22 | ../ || EXIT /B %ERRORLEVEL% 23 | cmake --build . --config Release && cmake --build . --config Release --target INSTALL ^ 24 | || cmake --build . --config Release && cmake --build . --config Release --target INSTALL ^ 25 | || EXIT /B %ERRORLEVEL% 26 | ENDLOCAL 27 | -------------------------------------------------------------------------------- /scripts/build_bzlib_msvc-x64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET PREFIX=%CD%\clib 5 | IF NOT EXIST cbuild ( 6 | MD cbuild || EXIT /B 1 7 | ) 8 | CD cbuild || EXIT /B 1 9 | git clone --depth 1 https://github.com/lifegpc/bzip2-msvc || EXIT /B %ERRORLEVEL% 10 | CD bzip2-msvc || EXIT /B 1 11 | IF NOT EXIST build ( 12 | MD build || EXIT /B 1 13 | ) 14 | CD build || EXIT /B 1 15 | cmake ^ 16 | -DCMAKE_PREFIX_PATH=%PREFIX% ^ 17 | -DCMAKE_BUILD_TYPE=Release ^ 18 | -DCMAKE_INSTALL_PREFIX=%PREFIX% ^ 19 | ../ || EXIT /B %ERRORLEVEL% 20 | cmake --build . --config Release && cmake --build . --config Release --target INSTALL ^ 21 | || cmake --build . --config Release && cmake --build . --config Release --target INSTALL ^ 22 | || EXIT /B %ERRORLEVEL% 23 | CD %PREFIX%\lib || EXIT /B 1 24 | COPY /Y libbz2.lib bz2.lib || EXIT /B 1 25 | ENDLOCAL 26 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_mingw-x64-debug.sh: -------------------------------------------------------------------------------- 1 | export PREFIX=`pwd`/clib 2 | export INSTALL_PREFIX=`pwd`/lib 3 | export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH 4 | mkdir -p build && cd build || exit 1 5 | cmake \ 6 | -G "MSYS Makefiles" \ 7 | -DCMAKE_BUILD_TYPE=Debug \ 8 | -DCMAKE_PREFIX_PATH=$PREFIX \ 9 | -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ 10 | -DENABLE_WASAPI=OFF \ 11 | ../ || exit 1 12 | make -j8 || exit 1 13 | make -j8 install || exit 1 14 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_mingw-x64-release.sh: -------------------------------------------------------------------------------- 1 | export PREFIX=`pwd`/clib 2 | export INSTALL_PREFIX=`pwd`/lib 3 | export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH 4 | mkdir -p build && cd build || exit 1 5 | cmake \ 6 | -G "MSYS Makefiles" \ 7 | -DCMAKE_BUILD_TYPE=Release \ 8 | -DCMAKE_PREFIX_PATH=$PREFIX \ 9 | -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ 10 | -DENABLE_WASAPI=OFF \ 11 | ../ || exit 1 12 | make -j8 || exit 1 13 | make -j8 install || exit 1 14 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_mingw-x64-wasapi-debug.sh: -------------------------------------------------------------------------------- 1 | export PREFIX=`pwd`/clib 2 | export INSTALL_PREFIX=`pwd`/lib 3 | export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH 4 | mkdir -p build && cd build || exit 1 5 | cmake \ 6 | -G "MSYS Makefiles" \ 7 | -DCMAKE_BUILD_TYPE=Debug \ 8 | -DCMAKE_PREFIX_PATH=$PREFIX \ 9 | -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ 10 | -DENABLE_WASAPI=ON \ 11 | ../ || exit 1 12 | make -j8 || exit 1 13 | make -j8 install || exit 1 14 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_mingw-x64-wasapi-release.sh: -------------------------------------------------------------------------------- 1 | export PREFIX=`pwd`/clib 2 | export INSTALL_PREFIX=`pwd`/lib 3 | export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH 4 | mkdir -p build && cd build || exit 1 5 | cmake \ 6 | -G "MSYS Makefiles" \ 7 | -DCMAKE_BUILD_TYPE=Release \ 8 | -DCMAKE_PREFIX_PATH=$PREFIX \ 9 | -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ 10 | -DENABLE_WASAPI=ON \ 11 | ../ || exit 1 12 | make -j8 || exit 1 13 | make -j8 install || exit 1 14 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_msvc-x64-debug.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET PREFIX=%CD%\clib 5 | SET PATH=%PREFIX%\bin;%PATH% 6 | SET INSTALL_PREFIX=%CD%\lib 7 | IF NOT EXIST build ( 8 | MD build || EXIT /B 1 9 | ) 10 | CD build || EXIT /B 1 11 | cmake ^ 12 | -DCMAKE_PREFIX_PATH=%PREFIX% ^ 13 | -DCMAKE_BUILD_TYPE=Debug ^ 14 | -DCMAKE_INSTALL_PREFIX=%INSTALL_PREFIX% ^ 15 | -DENABLE_WASAPI=OFF ^ 16 | ../ || EXIT /B %ERRORLEVEL% 17 | cmake --build . --config Debug && cmake --build . --config Debug --target INSTALL ^ 18 | || cmake --build . --config Debug && cmake --build . --config Debug --target INSTALL ^ 19 | || EXIT /B %ERRORLEVEL% 20 | ENDLOCAL 21 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_msvc-x64-release.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET PREFIX=%CD%\clib 5 | SET PATH=%PREFIX%\bin;%PATH% 6 | SET INSTALL_PREFIX=%CD%\lib 7 | IF NOT EXIST build ( 8 | MD build || EXIT /B 1 9 | ) 10 | CD build || EXIT /B 1 11 | cmake ^ 12 | -DCMAKE_PREFIX_PATH=%PREFIX% ^ 13 | -DCMAKE_BUILD_TYPE=RelWithDebInfo ^ 14 | -DCMAKE_INSTALL_PREFIX=%INSTALL_PREFIX% ^ 15 | -DENABLE_WASAPI=OFF ^ 16 | ../ || EXIT /B %ERRORLEVEL% 17 | cmake --build . --config RelWithDebInfo && cmake --build . --config RelWithDebInfo --target INSTALL ^ 18 | || cmake --build . --config RelWithDebInfo && cmake --build . --config RelWithDebInfo --target INSTALL ^ 19 | || EXIT /B %ERRORLEVEL% 20 | ENDLOCAL 21 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_msvc-x64-wasapi-debug.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET PREFIX=%CD%\clib 5 | SET PATH=%PREFIX%\bin;%PATH% 6 | SET INSTALL_PREFIX=%CD%\lib 7 | IF NOT EXIST build ( 8 | MD build || EXIT /B 1 9 | ) 10 | CD build || EXIT /B 1 11 | cmake ^ 12 | -DCMAKE_PREFIX_PATH=%PREFIX% ^ 13 | -DCMAKE_BUILD_TYPE=Debug ^ 14 | -DCMAKE_INSTALL_PREFIX=%INSTALL_PREFIX% ^ 15 | -DENABLE_WASAPI=ON ^ 16 | ../ || EXIT /B %ERRORLEVEL% 17 | cmake --build . --config Debug && cmake --build . --config Debug --target INSTALL ^ 18 | || cmake --build . --config Debug && cmake --build . --config Debug --target INSTALL ^ 19 | || EXIT /B %ERRORLEVEL% 20 | ENDLOCAL 21 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_core_msvc-x64-wasapi-release.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET PREFIX=%CD%\clib 5 | SET PATH=%PREFIX%\bin;%PATH% 6 | SET INSTALL_PREFIX=%CD%\lib 7 | IF NOT EXIST build ( 8 | MD build || EXIT /B 1 9 | ) 10 | CD build || EXIT /B 1 11 | cmake ^ 12 | -DCMAKE_PREFIX_PATH=%PREFIX% ^ 13 | -DCMAKE_BUILD_TYPE=RelWithDebInfo ^ 14 | -DCMAKE_INSTALL_PREFIX=%INSTALL_PREFIX% ^ 15 | -DENABLE_WASAPI=ON ^ 16 | ../ || EXIT /B %ERRORLEVEL% 17 | cmake --build . --config RelWithDebInfo && cmake --build . --config RelWithDebInfo --target INSTALL ^ 18 | || cmake --build . --config RelWithDebInfo && cmake --build . --config RelWithDebInfo --target INSTALL ^ 19 | || EXIT /B %ERRORLEVEL% 20 | ENDLOCAL 21 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_mingw-x64.sh: -------------------------------------------------------------------------------- 1 | export PREFIX=`pwd`/clib 2 | mkdir -p cbuild && cd cbuild || exit 1 3 | git clone --depth 1 'https://git.ffmpeg.org/ffmpeg.git' && cd ffmpeg || exit 1 4 | ./configure \ 5 | --enable-gpl \ 6 | --enable-shared \ 7 | --disable-static \ 8 | --enable-version3 \ 9 | --prefix=$PREFIX \ 10 | --disable-doc \ 11 | --disable-autodetect \ 12 | --disable-encoders \ 13 | --disable-filters \ 14 | "--enable-filter=volume,atempo,equalizer,aresample,aecho" \ 15 | --disable-muxers \ 16 | --enable-bzlib \ 17 | --enable-gnutls \ 18 | --enable-libcdio \ 19 | --enable-zlib \ 20 | || exit 1 21 | make -j8 || exit 1 22 | make -j8 install || exit 1 23 | -------------------------------------------------------------------------------- /scripts/build_ffmpeg_msvc-x64.sh: -------------------------------------------------------------------------------- 1 | export PREFIX=`pwd`/clib 2 | export PREFIX2=`cygpath -w $PREFIX` 3 | export "PATH=$PREFIX/bin:$PATH" 4 | export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig 5 | export "LIB=$LIB;$PREFIX2/lib" 6 | export "INCLUDE=$INCLUDE;$PREFIX2/include" 7 | mkdir -p cbuild && cd cbuild || exit 1 8 | git clone --depth 1 'https://git.ffmpeg.org/ffmpeg.git' && cd ffmpeg || exit 1 9 | ./configure \ 10 | --enable-gpl \ 11 | --enable-shared \ 12 | --disable-static \ 13 | --enable-version3 \ 14 | --prefix=${PREFIX2//\\//} \ 15 | --disable-doc \ 16 | --disable-autodetect \ 17 | --disable-encoders \ 18 | --disable-filters \ 19 | "--enable-filter=volume,atempo,equalizer,aresample,aecho" \ 20 | --disable-muxers \ 21 | --enable-bzlib \ 22 | --enable-gnutls \ 23 | --enable-libcdio \ 24 | --enable-zlib \ 25 | --pkg-config-flags="--env-only" \ 26 | --toolchain=msvc 27 | if [ $? != 0 ]; then 28 | cat ffbuild/config.log 29 | exit 1 30 | fi 31 | make -j8 || exit 1 32 | make -j8 install || exit 1 33 | mv -fv $PREFIX/bin/*.lib $PREFIX/lib || exit 1 34 | -------------------------------------------------------------------------------- /scripts/build_pkgconf_msvc-x64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET PREFIX=%CD%\clib 4 | IF NOT EXIST cbuild ( 5 | MD cbuild || EXIT /B 1 6 | ) 7 | CD cbuild || EXIT /B 1 8 | git clone --depth 1 "https://github.com/pkgconf/pkgconf" || EXIT /B %ERRORLEVEL% 9 | CD pkgconf || EXIT /B %ERRORLEVEL% 10 | meson setup build --prefix %PREFIX% -Dtests=disabled || EXIT /B %ERRORLEVEL% 11 | meson compile -C build || EXIT /B %ERRORLEVEL% 12 | meson install -C build || EXIT /B %ERRORLEVEL% 13 | CD %PREFIX%\bin || EXIT /B 1 14 | COPY /Y pkgconf.exe pkg-config.exe || EXIT /B %ERRORLEVEL% 15 | ENDLOCAL 16 | -------------------------------------------------------------------------------- /scripts/build_zlib_msvc-x64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET PREFIX=%CD%\clib 5 | SET PKG_CONFIG_DIR=%PREFIX%\lib\pkgconfig 6 | SET PATCH_DIR=%CD%\patch\zlib-msvc 7 | IF NOT EXIST cbuild ( 8 | MD cbuild || EXIT /B 1 9 | ) 10 | CD cbuild || EXIT /B 1 11 | git clone --depth 1 "https://github.com/lifegpc/zlib" || EXIT /B %ERRORLEVEL% 12 | CD zlib || EXIT /B 1 13 | IF NOT EXIST build ( 14 | MD build || EXIT /B 1 15 | ) 16 | CD build || EXIT /B 1 17 | cmake ^ 18 | -DCMAKE_PREFIX_PATH=%PREFIX% ^ 19 | -DCMAKE_BUILD_TYPE=Release ^ 20 | -DCMAKE_INSTALL_PREFIX=%PREFIX% ^ 21 | -DINSTALL_PKGCONFIG_DIR=%PKG_CONFIG_DIR% ^ 22 | ../ || EXIT /B %ERRORLEVEL% 23 | cmake --build . --config Release && cmake --build . --config Release --target INSTALL ^ 24 | || cmake --build . --config Release && cmake --build . --config Release --target INSTALL ^ 25 | || EXIT /B %ERRORLEVEL% 26 | CD %PREFIX%\include || EXIT /B 1 27 | patch -p1 zconf.h %PATCH_DIR%\zconf.h.patch || EXIT /B %ERRORLEVEL% 28 | ENDLOCAL 29 | -------------------------------------------------------------------------------- /scripts/download_cdio-paranoia_msvc-x64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL ENABLEDELAYEDEXPANSION 3 | SET TOP=%CD% 4 | SET SCRIPTS_DIR=%CD%\scripts 5 | SET DOWNLOAD_RESOURCE=%SCRIPTS_DIR%\download_resource.bat 6 | SET EXTRACT_ZIP=%SCRIPTS_DIR%\extract_zip.bat 7 | SET PREFIX=%CD%\clib 8 | SET VERSION=10.2+2.0.1 9 | SET FILE=libcdio-paranoia_release-%VERSION:+=.%_msvc16.zip 10 | IF NOT EXIST cbuild ( 11 | MD cbuild || EXIT /B 1 12 | ) 13 | CD cbuild || EXIT /B 1 14 | CALL %DOWNLOAD_RESOURCE% -o "%FILE%" "https://github.com/ShiftMediaProject/libcdio-paranoia/releases/download/release-!VERSION:+=%%%%2B!/libcdio-paranoia_release-10.2.2.0.1_msvc16.zip" || EXIT /B %ERRORLEVEL% 15 | CALL %EXTRACT_ZIP% "%FILE%" x64 "%PREFIX%" || EXIT /B %ERRORLEVEL% 16 | CD %PREFIX%/lib || EXIT /B 1 17 | IF NOT EXIST pkgconfig ( 18 | MD pkgconfig || EXIT /B 1 19 | ) 20 | CD pkgconfig || EXIT /B 1 21 | IF EXIST libcdio_paranoia.pc ( 22 | DEL /Q libcdio_paranoia.pc || EXIT /B 1 23 | ) 24 | echo prefix=%PREFIX:\=/% > libcdio_paranoia.pc 25 | echo libdir=${prefix}/lib >> libcdio_paranoia.pc 26 | echo includedir=${prefix}/include >> libcdio_paranoia.pc 27 | echo Name: libcdio_paranoia >> libcdio_paranoia.pc 28 | echo Description: CD paranoia library from libcdio >> libcdio_paranoia.pc 29 | echo Version: %VERSION:+=.% >> libcdio_paranoia.pc 30 | echo Libs: -L${libdir} -lcdio_paranoia >> libcdio_paranoia.pc 31 | echo Cflags: -I${includedir} >> libcdio_paranoia.pc 32 | ENDLOCAL 33 | -------------------------------------------------------------------------------- /scripts/download_cdio_msvc-x64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET SCRIPTS_DIR=%CD%\scripts 5 | SET DOWNLOAD_RESOURCE=%SCRIPTS_DIR%\download_resource.bat 6 | SET EXTRACT_ZIP=%SCRIPTS_DIR%\extract_zip.bat 7 | SET PREFIX=%CD%\clib 8 | SET VERSION=2.1.0-1 9 | SET FILE=libcdio_release-%VERSION%_msvc16.zip 10 | IF NOT EXIST cbuild ( 11 | MD cbuild || EXIT /B 1 12 | ) 13 | CD cbuild || EXIT /B 1 14 | CALL %DOWNLOAD_RESOURCE% -o "%FILE%" "https://github.com/ShiftMediaProject/libcdio/releases/download/release-%VERSION%/%FILE%" || EXIT /B %ERRORLEVEL% 15 | CALL %EXTRACT_ZIP% "%FILE%" x64 "%PREFIX%" || EXIT /B %ERRORLEVEL% 16 | ENDLOCAL 17 | -------------------------------------------------------------------------------- /scripts/download_gnutls_msvc-x64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET SCRIPTS_DIR=%CD%\scripts 5 | SET DOWNLOAD_RESOURCE=%SCRIPTS_DIR%\download_resource.bat 6 | SET EXTRACT_ZIP=%SCRIPTS_DIR%\extract_zip.bat 7 | SET PREFIX=%CD%\clib 8 | SET VERSION=3.7.4 9 | SET FILE=libgnutls_%VERSION%_msvc17.zip 10 | IF NOT EXIST cbuild ( 11 | MD cbuild || EXIT /B 1 12 | ) 13 | CD cbuild || EXIT /B 1 14 | CALL %DOWNLOAD_RESOURCE% -o %FILE% "https://github.com/ShiftMediaProject/gnutls/releases/download/%VERSION%/%FILE%" || EXIT /B %ERRORLEVEL% 15 | CALL %EXTRACT_ZIP% "%FILE%" x64 "%PREFIX%" || EXIT /B %ERRORLEVEL% 16 | CD %PREFIX%/lib || EXIT /B 1 17 | IF NOT EXIST pkgconfig ( 18 | MD pkgconfig || EXIT /B 1 19 | ) 20 | CD pkgconfig || EXIT /B 1 21 | IF EXIST gnutls.pc ( 22 | DEL /Q gnutls.pc || EXIT /B 1 23 | ) 24 | echo prefix=%PREFIX:\=/% > gnutls.pc 25 | echo libdir=${prefix}/lib >> gnutls.pc 26 | echo includedir=${prefix}/include >> gnutls.pc 27 | echo Name: gnutls >> gnutls.pc 28 | echo Description: Transport Security Layer implementation for the GNU system >> gnutls.pc 29 | echo URL: https://www.gnutls.org/ >> gnutls.pc 30 | echo Version: %VERSION% >> gnutls.pc 31 | echo Libs: -L${libdir} -lgnutls >> gnutls.pc 32 | echo Cflags: -I${includedir} >> gnutls.pc 33 | ENDLOCAL 34 | -------------------------------------------------------------------------------- /scripts/download_lld-rust.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET TOP=%CD% 4 | SET SCRIPTS_DIR=%CD%\scripts 5 | SET DOWNLOAD_RESOURCE=%SCRIPTS_DIR%\download_resource.bat 6 | SET VERSION=v0.0.2 7 | SET FILE=ldd-x86_64-msvc-%VERSION%.7z 8 | CALL %DOWNLOAD_RESOURCE% -o "%FILE%" "https://github.com/lifegpc/ldd-rust/releases/download/%VERSION%/%FILE%" || EXIT %ERRORLEVEL% 9 | 7z e "%FILE%" ldd.exe || EXIT %ERRORLEVEL% 10 | MOVE /Y ldd.exe ldd-rust.exe || EXIT %ERRORLEVEL% 11 | ENDLOCAL 12 | -------------------------------------------------------------------------------- /scripts/download_resource.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET DOWNLOAD_RESOURCE_PY=download_resource.py 4 | IF NOT EXIST %DOWNLOAD_RESOURCE_PY% ( 5 | SET DOWNLOAD_RESOURCE_PY=%CD%\download_resource.py 6 | IF NOT EXIST %DOWNLOAD_RESOURCE_PY% ( 7 | IF DEFINED SCRIPTS_DIR ( 8 | SET DOWNLOAD_RESOURCE_PY=%SCRIPTS_DIR%\download_resource.py 9 | ) 10 | ) 11 | ) 12 | IF NOT EXIST %DOWNLOAD_RESOURCE_PY% ( 13 | EXIT /B 1 14 | ) 15 | python %DOWNLOAD_RESOURCE_PY% %* 16 | IF ERRORLEVEL 2 ( 17 | python -m pip install --upgrade requests 18 | python %DOWNLOAD_RESOURCE_PY% %* 19 | ) 20 | IF %ERRORLEVEL% NEQ 0 ( 21 | EXIT /B %ERRORLEVEL% 22 | ) 23 | ENDLOCAL 24 | -------------------------------------------------------------------------------- /scripts/download_resource.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser, Namespace 2 | from sys import exit as _exit 3 | from traceback import print_exc 4 | from urllib.parse import urlparse 5 | from os.path import join 6 | try: 7 | from requests import Session 8 | except ImportError: 9 | _exit(2) 10 | except Exception: 11 | print_exc() 12 | _exit(1) 13 | 14 | 15 | def download_file(arg: Namespace): 16 | s = Session() 17 | fn = urlparse(arg.URL).path.split('/')[-1] 18 | output = fn 19 | if arg.output: 20 | output = arg.output 21 | elif arg.dest: 22 | output = join(arg.dest, fn) 23 | print(f'Downloading {arg.URL} to {output}') 24 | r = s.get(arg.URL, stream=True) 25 | if r.status_code >= 400: 26 | raise Exception(f'Failed to download {arg.URL}') 27 | with open(output, 'wb') as f: 28 | for chunk in r.iter_content(chunk_size=1024): 29 | if chunk: 30 | f.write(chunk) 31 | 32 | 33 | try: 34 | p = ArgumentParser(description='Download resources from github.') 35 | p.add_argument('URL', help='The URL of the resource.') 36 | p.add_argument('-o', '--output', help='The full path of output file.') 37 | p.add_argument('-d', '--dest', help='The destination directory.') 38 | arg = p.parse_intermixed_args() 39 | download_file(arg) 40 | except Exception: 41 | print_exc() 42 | _exit(1) 43 | -------------------------------------------------------------------------------- /scripts/extract_zip.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET EXTRACT_ZIP_PY=extract_zip.py 4 | IF NOT EXIST %EXTRACT_ZIP_PY% ( 5 | SET EXTRACT_ZIP_PY=%CD%\extract_zip.py 6 | IF NOT EXIST %EXTRACT_ZIP_PY% ( 7 | IF DEFINED SCRIPTS_DIR ( 8 | SET EXTRACT_ZIP_PY=%SCRIPTS_DIR%\extract_zip.py 9 | ) 10 | ) 11 | ) 12 | IF NOT EXIST %EXTRACT_ZIP_PY% ( 13 | EXIT /B 1 14 | ) 15 | python %EXTRACT_ZIP_PY% %* 16 | IF %ERRORLEVEL% NEQ 0 ( 17 | EXIT /B %ERRORLEVEL% 18 | ) 19 | ENDLOCAL 20 | -------------------------------------------------------------------------------- /scripts/extract_zip.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser, Namespace 2 | from sys import exit as _exit 3 | from traceback import print_exc 4 | from zipfile import ZipFile 5 | from os import makedirs, remove 6 | from os.path import join, relpath, dirname, exists 7 | from typing import BinaryIO 8 | 9 | 10 | def extract(f: BinaryIO, dest): 11 | if exists(dest): 12 | remove(dest) 13 | with open(dest, 'wb') as g: 14 | t = f.read(4096) 15 | while len(t) > 0: 16 | g.write(t) 17 | t = f.read(4096) 18 | 19 | 20 | def extract_file(arg: Namespace): 21 | with ZipFile(arg.FILE, 'r') as f: 22 | li = f.namelist() 23 | bin_dir = join(arg.OUTPUT, 'bin') 24 | include_dir = join(arg.OUTPUT, 'include') 25 | lib_dir = join(arg.OUTPUT, 'lib') 26 | for i in li: 27 | if i.startswith('bin/'): 28 | ll = i.split('/') 29 | if len(ll) > 1 and ll[1] == arg.ARCH: 30 | dest = join(bin_dir, relpath(i, f'bin/{arg.ARCH}')) 31 | makedirs(dirname(dest), exist_ok=True) 32 | print(f'Extracting {i} to {dest}') 33 | with f.open(i) as t: 34 | extract(t, dest) 35 | if i.startswith('include/'): 36 | dest = join(include_dir, relpath(i, 'include/')) 37 | makedirs(dirname(dest), exist_ok=True) 38 | print(f'Extracting {i} to {dest}') 39 | with f.open(i) as t: 40 | extract(t, dest) 41 | if i.startswith('lib/'): 42 | ll = i.split('/') 43 | if len(ll) > 1 and ll[1] == arg.ARCH: 44 | dest = join(lib_dir, relpath(i, f'lib/{arg.ARCH}')) 45 | makedirs(dirname(dest), exist_ok=True) 46 | print(f'Extracting {i} to {dest}') 47 | with f.open(i) as t: 48 | extract(t, dest) 49 | 50 | 51 | try: 52 | p = ArgumentParser(description='Extact resources.') 53 | p.add_argument('FILE', help='The file to extract.') 54 | p.add_argument('ARCH', help='The ARCH type.') 55 | p.add_argument('OUTPUT', help='The output directory.') 56 | arg = p.parse_intermixed_args() 57 | extract_file(arg) 58 | except Exception: 59 | print_exc() 60 | _exit(1) 61 | -------------------------------------------------------------------------------- /scripts/get_cache_key.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | from hashlib import sha256 as _sha256 3 | from os import environ 4 | from os.path import exists 5 | import sys 6 | from time import time, strftime, gmtime 7 | from typing import List 8 | 9 | 10 | def sha256(data) -> str: 11 | if isinstance(data, str): 12 | data = data.encode() 13 | elif not isinstance(data, bytes): 14 | data = str(data).encode() 15 | s = _sha256() 16 | s.update(data) 17 | return s.hexdigest() 18 | 19 | 20 | def hash_file(type, feature) -> str: 21 | fnl = [] 22 | fn = f"build_{feature}_{type}.sh" 23 | if exists(fn): 24 | fnl.append(fn) 25 | fn = f'build_{feature}_{type}.bat' 26 | if exists(fn): 27 | fnl.append(fn) 28 | fn = f'download_{feature}_{type}.bat' 29 | if exists(fn): 30 | fnl.append(fn) 31 | if len(fnl) == 0: 32 | return '' 33 | s = _sha256() 34 | for fn in fnl: 35 | with open(fn, 'rb') as f: 36 | c = f.read(256) 37 | while len(c) > 0: 38 | s.update(c) 39 | c = f.read(256) 40 | return s.hexdigest() 41 | 42 | 43 | try: 44 | p = ArgumentParser(description='Get the cache key which used in action/cache') 45 | p.add_argument('type', help='The build type') 46 | p.add_argument('features', help="The feature's name", action='append', nargs='+') 47 | args = p.parse_intermixed_args(sys.argv[1:]) 48 | features: List[str] = args.features[0] 49 | d = '' 50 | now = time() 51 | for i in features: 52 | dt = strftime('%Y-%m', gmtime(now)) 53 | h = hash_file(args.type, i) 54 | if len(h) > 0: 55 | d += f"{i}={dt}:{h}\n" 56 | print(d) 57 | github_output = environ.get('GITHUB_OUTPUT', '') 58 | if github_output != '': 59 | with open(github_output, 'a') as f: 60 | f.write(f"cache_key={sha256(d)}\n") 61 | else: 62 | print(f"::set-output name=cache_key::{sha256(d)}") 63 | except Exception: 64 | from traceback import print_exc 65 | from sys import exit 66 | print_exc() 67 | exit(1) 68 | -------------------------------------------------------------------------------- /scripts/pack_prog.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser, Namespace 2 | from os import system, devnull, environ, remove, makedirs, listdir, chdir 3 | from typing import List 4 | from sys import exit 5 | from subprocess import Popen, PIPE 6 | from re import search, IGNORECASE 7 | from os.path import splitext, exists, abspath, isdir, isfile, join, basename 8 | from tempfile import NamedTemporaryFile 9 | from shutil import copy2 10 | 11 | 12 | def add_path_ext(path: str) -> str: 13 | p, n = splitext(path) 14 | if n != '': 15 | return path 16 | else: 17 | pext = environ['PATHEXT'] 18 | pextl = pext.split(';') 19 | for ext in pextl: 20 | if ext == '': 21 | continue 22 | if exists(p + ext): 23 | return p + ext 24 | return path 25 | 26 | 27 | def check_pdb(path: str) -> str: 28 | p = splitext(path)[0] + '.pdb' 29 | if exists(p): 30 | return p 31 | 32 | 33 | def check_needed_prog(): 34 | if system(f'ldd-rust --help > {devnull}'): 35 | return False 36 | if system(f'7z --help > {devnull}'): 37 | return False 38 | return True 39 | 40 | 41 | def check_prog(prog: str) -> List[str]: 42 | r = Popen(f'ldd-rust {prog}', stdout=PIPE, stderr=PIPE) 43 | out: bytes = r.communicate()[0] 44 | r.wait() 45 | out += r.communicate()[0] 46 | if not r.returncode: 47 | sl = out.splitlines(False) 48 | rl = [] 49 | for r in sl: 50 | r = r.decode() 51 | rs = search(r'=?-?> (.+) ?(\(0x[0-9a-f]+\))?$', r, IGNORECASE) 52 | if rs is not None: 53 | rl.append(abspath(rs.groups()[0])) 54 | else: 55 | raise ValueError(f'Can not find path for {r}.') 56 | return rl 57 | return None 58 | 59 | 60 | def getUnixPath(path: str) -> str: 61 | rs = search(r'^[A-Z]:', path, IGNORECASE) 62 | if rs is None: 63 | return path.replace('\\', '/') 64 | return '/' + path[0].lower() + path[2:].replace('\\', '/') 65 | 66 | 67 | def getWindowsPath(path: str) -> str: 68 | rs = search(r'^[\\/][A-Z][\\/]', path, IGNORECASE) 69 | if rs is None: 70 | return path.replace('/', '\\') 71 | return path[1].upper() + ":" + path[2:].replace('/', '\\') 72 | 73 | 74 | def listdirs(loc: str, ignore_hidden_files: bool = True): 75 | bl = listdir(loc) 76 | r = [] 77 | for i in bl: 78 | if i.startswith('.'): 79 | if ignore_hidden_files or i == '.' or i == '..': 80 | continue 81 | p = join(loc, i) 82 | if isfile(p): 83 | r.append(p) 84 | elif isdir(p): 85 | r += listdirs(p) 86 | return r 87 | 88 | 89 | def remove_dirs(loc: str): 90 | bl = listdirs(loc, False) 91 | for i in bl: 92 | if isfile(i): 93 | remove(i) 94 | elif isdir(i): 95 | try: 96 | remove_dirs(i) 97 | except Exception: 98 | remove_dirs(i) 99 | remove(loc) 100 | 101 | class Prog: 102 | def __init__(self): 103 | self._loc = [] 104 | self.strip = False 105 | self.num_cpu = None 106 | 107 | def add_dep(self, path: str): 108 | path_w = getWindowsPath(path) 109 | if path_w.upper().startswith('C:\\WINDOWS'): 110 | return 111 | if path_w not in self._loc: 112 | print(f'add dependence: "{path_w}"') 113 | self._loc.append(path_w) 114 | 115 | def add_prog(self, path: str): 116 | pro = add_path_ext(path) 117 | pro_w = getWindowsPath(pro) 118 | if pro_w not in self._loc: 119 | print(f'add program: "{pro_w}"') 120 | self._loc.append(pro_w) 121 | 122 | def add_file(self, path: str): 123 | p = getWindowsPath(path) 124 | if p not in self._loc: 125 | print(f'add file: "{p}"') 126 | self._loc.append(p) 127 | 128 | def to_7z(self, output: str): 129 | if self.strip: 130 | makedirs('temp', exist_ok=True) 131 | p = NamedTemporaryFile(delete=False) 132 | for i in self._loc: 133 | if not self.strip: 134 | p.write((i + '\n').encode('UTF8')) 135 | else: 136 | bn = basename(i) 137 | dest = f'temp/{bn}' 138 | print(f'Copying {i} to {dest}') 139 | copy2(i, dest) 140 | pr = Popen(['strip', dest]) 141 | pr.wait() 142 | p.write((bn + '\n').encode('UTF8')) 143 | fp = p.name 144 | p.close() 145 | output = getWindowsPath(abspath(output)) 146 | try: 147 | num_cpu = '' if self.num_cpu is None else f' -mmt{self.num_cpu}' 148 | if self.strip: 149 | chdir('temp') 150 | system(f'7z a{num_cpu} -mx9 -y {output} @{fp}') 151 | if self.strip: 152 | chdir('../') 153 | except Exception: 154 | remove(fp) 155 | from traceback import print_exc 156 | print_exc() 157 | remove(fp) 158 | if self.strip: 159 | try: 160 | remove_dirs('temp') 161 | except Exception: 162 | pass 163 | 164 | 165 | def main(prog: List[str], output: str = None, adds: List[str] = None, 166 | pdbs: List[str] = None, args: Namespace = None): 167 | if output is None: 168 | output = 'programs.7z' 169 | if not check_needed_prog(): 170 | print('ldd and 7z is needed.') 171 | p = Prog() 172 | for pro in prog: 173 | pro = abspath(pro) 174 | pro = add_path_ext(pro) 175 | # pro_u = getUnixPath(pro) 176 | rel = check_prog(pro) 177 | if rel is None: 178 | print(f'Can not get dependencies for {pro},') 179 | exit(-1) 180 | p.add_prog(pro) 181 | for i in rel: 182 | p.add_dep(i) 183 | if adds is not None: 184 | for f in adds: 185 | p.add_file(f) 186 | if pdbs: 187 | for i in pdbs: 188 | pro = abspath(i) 189 | pro = add_path_ext(pro) 190 | rel = check_prog(pro) 191 | if rel is None: 192 | print(f'Can not get dependencies for {pro},') 193 | exit(-1) 194 | fn = check_pdb(pro) 195 | if fn: 196 | p.add_file(fn) 197 | for i in rel: 198 | fn = check_pdb(i) 199 | if fn: 200 | p.add_file(fn) 201 | if args and args.strip: 202 | p.strip = True 203 | if args and args.num_cpu: 204 | p.num_cpu = args.num_cpu 205 | p.to_7z(output) 206 | 207 | 208 | p = ArgumentParser(description='Pack programs into a 7-zip file.') 209 | p.add_argument('PROG', action='append', help='Program to pack', nargs='*', default=[]) 210 | p.add_argument('-p', '--pdb', action='append', help='Program\'s PDB file to pack', default=[], dest='pdb') 211 | p.add_argument('-o', '--output', help='Output file', default='programs.7z', dest='output') 212 | p.add_argument('-a', '--add', action='append', help='Additional files to pack', default=[], dest='add') 213 | p.add_argument('-s', '--strip', help='Strip before package', default=False, action='store_true', dest='strip') 214 | p.add_argument('-n', '--num_cpu', help='Number of CPU to use', default=None, type=int, dest='num_cpu') 215 | 216 | 217 | if __name__ == '__main__': 218 | try: 219 | args = p.parse_args() 220 | main(args.PROG[0], args.output, args.add, args.pdb, args) 221 | except Exception: 222 | from traceback import print_exc 223 | from sys import exit 224 | print_exc() 225 | exit(-1) 226 | -------------------------------------------------------------------------------- /src/cda.c: -------------------------------------------------------------------------------- 1 | #include "cda.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "cfileop.h" 7 | #include "cstr_util.h" 8 | #include "err.h" 9 | 10 | #if _WIN32 11 | #ifndef _O_BINARY 12 | #define _O_BINARY 0x8000 13 | #endif 14 | 15 | #ifndef _SH_DENYWR 16 | #define _SH_DENYWR 0x20 17 | #endif 18 | 19 | #ifndef _S_IREAD 20 | #define _S_IREAD 0x100 21 | #endif 22 | #else 23 | #define _O_BINARY 0 24 | #define _SH_DENYWR 0 25 | #define _S_IREAD 0 26 | #endif 27 | 28 | #define CDA_FILE_SIZE 44 29 | #define u8_buf(buf, offset) ((const uint8_t*)buf + offset) 30 | 31 | int is_cda_file(const char* url) { 32 | if (!url) return 0; 33 | char* dir = fileop_dirname(url); 34 | if (!dir) return 0; 35 | #if _WIN32 36 | // 判断是否为根盘符 37 | if (!fileop_isdrive(dir)) { 38 | free(dir); 39 | return 0; 40 | } 41 | #endif 42 | free(dir); 43 | char* l = strrchr(url, '.'); 44 | if (!l || cstr_stricmp(l, ".cda")) { 45 | return 0; 46 | } 47 | size_t size = 0; 48 | if (!fileop_get_file_size(url, &size)) return 0; 49 | if (size != CDA_FILE_SIZE) return 0; 50 | return 1; 51 | } 52 | 53 | // CDA 文件格式见 https://en.wikipedia.org/wiki/.cda_file 54 | int read_cda_file(MusicHandle* handle, const char* url) { 55 | if (!handle || !url) return FFMPEG_CORE_ERR_NULLPTR; 56 | int fd = 0; 57 | int re = 0; 58 | FILE* f = NULL; 59 | char buf[CDA_FILE_SIZE]; 60 | int num_read = 0; 61 | uint32_t chunk_size = 0; 62 | if ((re = fileop_open(url, &fd, O_RDONLY | _O_BINARY, _SH_DENYWR, _S_IREAD))) { 63 | char* errmsg = err_get_errno_message(re); 64 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": %s (%d)\n", url, errmsg ? errmsg : "", re); 65 | if (errmsg) free(errmsg); 66 | return FFMPEG_CORE_ERR_FAILED_OPEN_FILE; 67 | } 68 | f = fileop_fdopen(fd, "r"); 69 | if (!f) { 70 | fileop_close(fd); 71 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": Can not open file descriptor %d\n", url, fd); 72 | return FFMPEG_CORE_ERR_FAILED_OPEN_FILE; 73 | } 74 | handle->cda = malloc(sizeof(CDAData)); 75 | if (!handle->cda) { 76 | re = FFMPEG_CORE_ERR_OOM; 77 | goto end; 78 | } 79 | memset(handle->cda, 0, sizeof(CDAData)); 80 | if ((num_read = fread(buf, 1, CDA_FILE_SIZE, f)) < CDA_FILE_SIZE) { 81 | av_log(NULL, AV_LOG_FATAL, "Failed to read file \"%s\": %d bytes is needed, but only %d bytes was readed.\n", url, CDA_FILE_SIZE, num_read); 82 | re = FFMPEG_CORE_ERR_FAILED_READ_FILE; 83 | goto end; 84 | } 85 | if (strncmp(buf, "RIFF", 4)) { 86 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 87 | goto end; 88 | } 89 | chunk_size = cstr_read_uint32(u8_buf(buf, 4), 0); 90 | if (chunk_size != 36) { 91 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 92 | goto end; 93 | } 94 | if (strncmp(buf + 8, "CDDAfmt ", 8)) { 95 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 96 | goto end; 97 | } 98 | chunk_size = cstr_read_uint32(u8_buf(buf, 16), 0); 99 | if (chunk_size != 24) { 100 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 101 | goto end; 102 | } 103 | handle->cda->cd_format_version = cstr_read_uint16(u8_buf(buf, 20), 0); 104 | handle->cda->no = cstr_read_uint16(u8_buf(buf, 22), 0); 105 | handle->cda->range_offset = cstr_read_uint32(u8_buf(buf, 28), 0); 106 | handle->cda->duration = cstr_read_uint32(u8_buf(buf, 32), 0); 107 | re = FFMPEG_CORE_ERR_OK; 108 | end: 109 | if (f) fileop_fclose(f); 110 | return re; 111 | } 112 | 113 | int read_cda_file2(MusicInfoHandle* handle, const char* url) { 114 | if (!handle || !url) return FFMPEG_CORE_ERR_NULLPTR; 115 | int fd = 0; 116 | int re = 0; 117 | FILE* f = NULL; 118 | char buf[CDA_FILE_SIZE]; 119 | int num_read = 0; 120 | uint32_t chunk_size = 0; 121 | if ((re = fileop_open(url, &fd, O_RDONLY | _O_BINARY, _SH_DENYWR, _S_IREAD))) { 122 | char* errmsg = err_get_errno_message(re); 123 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": %s (%d)\n", url, errmsg ? errmsg : "", re); 124 | if (errmsg) free(errmsg); 125 | return FFMPEG_CORE_ERR_FAILED_OPEN_FILE; 126 | } 127 | f = fileop_fdopen(fd, "r"); 128 | if (!f) { 129 | fileop_close(fd); 130 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": Can not open file descriptor %d\n", url, fd); 131 | return FFMPEG_CORE_ERR_FAILED_OPEN_FILE; 132 | } 133 | handle->cda = malloc(sizeof(CDAData)); 134 | if (!handle->cda) { 135 | re = FFMPEG_CORE_ERR_OOM; 136 | goto end; 137 | } 138 | memset(handle->cda, 0, sizeof(CDAData)); 139 | if ((num_read = fread(buf, 1, CDA_FILE_SIZE, f)) < CDA_FILE_SIZE) { 140 | av_log(NULL, AV_LOG_FATAL, "Failed to read file \"%s\": %d bytes is needed, but only %d bytes was readed.\n", url, CDA_FILE_SIZE, num_read); 141 | re = FFMPEG_CORE_ERR_FAILED_READ_FILE; 142 | goto end; 143 | } 144 | if (strncmp(buf, "RIFF", 4)) { 145 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 146 | goto end; 147 | } 148 | chunk_size = cstr_read_uint32(u8_buf(buf, 4), 0); 149 | if (chunk_size != 36) { 150 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 151 | goto end; 152 | } 153 | if (strncmp(buf + 8, "CDDAfmt ", 8)) { 154 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 155 | goto end; 156 | } 157 | chunk_size = cstr_read_uint32(u8_buf(buf, 16), 0); 158 | if (chunk_size != 24) { 159 | re = FFMPEG_CORE_ERR_INVALID_CDA_FILE; 160 | goto end; 161 | } 162 | handle->cda->cd_format_version = cstr_read_uint16(u8_buf(buf, 20), 0); 163 | handle->cda->no = cstr_read_uint16(u8_buf(buf, 22), 0); 164 | handle->cda->range_offset = cstr_read_uint32(u8_buf(buf, 28), 0); 165 | handle->cda->duration = cstr_read_uint32(u8_buf(buf, 32), 0); 166 | re = FFMPEG_CORE_ERR_OK; 167 | end: 168 | if (f) fileop_fclose(f); 169 | return re; 170 | } 171 | 172 | const AVInputFormat* find_libcdio() { 173 | const AVInputFormat* f = NULL; 174 | f = av_input_audio_device_next(f); 175 | while (f) { 176 | if (f && !strcmp(f->name, "libcdio")) return f; 177 | f = av_input_audio_device_next(f); 178 | } 179 | return NULL; 180 | } 181 | 182 | int open_cd_device(MusicHandle* handle, const char* device) { 183 | if (!handle || !device) return FFMPEG_CORE_ERR_NULLPTR; 184 | avdevice_register_all(); 185 | const AVInputFormat* f = find_libcdio(); 186 | AVRational cda_time_base = { 1, 75 }; 187 | if (!f) { 188 | return FFMPEG_CORE_ERR_NO_LIBCDIO; 189 | } 190 | int re = 0; 191 | AVDictionary* d = NULL; 192 | av_dict_set(&d, "paranoia_mode", "verify", 0); 193 | if ((re = avformat_open_input(&handle->fmt, device, f, &d)) < 0) { 194 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": %s (%i)\n", device, av_err2str(re), re); 195 | av_dict_free(&d); 196 | return re; 197 | } 198 | if ((re = avformat_find_stream_info(handle->fmt, NULL)) < 0) { 199 | av_log(NULL, AV_LOG_FATAL, "Failed to find streams in \"%s\": %s (%i)\n", device, av_err2str(re), re); 200 | av_dict_free(&d); 201 | return re; 202 | } 203 | handle->only_part = 1; 204 | handle->part_start_pts = av_rescale_q_rnd(handle->cda->range_offset, cda_time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 205 | handle->part_end_pts = av_rescale_q_rnd((int64_t)handle->cda->duration + handle->cda->range_offset, cda_time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 206 | // handle->fmt->flags |= AVFMT_FLAG_FAST_SEEK; // 允许快速定位 207 | av_dict_free(&d); 208 | return FFMPEG_CORE_ERR_OK; 209 | } 210 | 211 | int open_cd_device2(MusicInfoHandle* handle, const char* device) { 212 | if (!handle || !device) return FFMPEG_CORE_ERR_NULLPTR; 213 | avdevice_register_all(); 214 | const AVInputFormat* f = find_libcdio(); 215 | AVRational cda_time_base = { 1, 75 }; 216 | if (!f) { 217 | return FFMPEG_CORE_ERR_NO_LIBCDIO; 218 | } 219 | int re = 0; 220 | if ((re = avformat_open_input(&handle->fmt, device, f, NULL)) < 0) { 221 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": %s (%i)\n", device, av_err2str(re), re); 222 | return re; 223 | } 224 | if ((re = avformat_find_stream_info(handle->fmt, NULL)) < 0) { 225 | av_log(NULL, AV_LOG_FATAL, "Failed to find streams in \"%s\": %s (%i)\n", device, av_err2str(re), re); 226 | return re; 227 | } 228 | return FFMPEG_CORE_ERR_OK; 229 | } 230 | 231 | int64_t get_cda_duration(CDAData* d) { 232 | if (!d) return 0; 233 | AVRational t = { 1, 75 }; 234 | return av_rescale_q_rnd(d->duration, t, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 235 | } 236 | -------------------------------------------------------------------------------- /src/cda.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_CDA_H 2 | #define _MUSICPLAYER2_CDA_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | int is_cda_file(const char* url); 8 | int read_cda_file(MusicHandle* handle, const char* url); 9 | int read_cda_file2(MusicInfoHandle* handle, const char* url); 10 | const AVInputFormat* find_libcdio(); 11 | int open_cd_device(MusicHandle* handle, const char* device); 12 | int open_cd_device2(MusicInfoHandle* handle, const char* device); 13 | int64_t get_cda_duration(CDAData* d); 14 | #if __cplusplus 15 | } 16 | #endif 17 | #endif 18 | -------------------------------------------------------------------------------- /src/ch_layout.c: -------------------------------------------------------------------------------- 1 | #include "ch_layout.h" 2 | 3 | #include 4 | 5 | #if NEW_CHANNEL_LAYOUT 6 | int get_channel_layout_channels(uint64_t channel_layout) { 7 | AVChannelLayout tmp; 8 | memset(&tmp, 0, sizeof(AVChannelLayout)); 9 | if (av_channel_layout_from_mask(&tmp, channel_layout)) { 10 | return 0; 11 | } 12 | int re = tmp.nb_channels; 13 | av_channel_layout_uninit(&tmp); 14 | return re; 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /src/ch_layout.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_CH_LAYOUT_H 2 | #define _MUSICPLAYER2_CH_LAYOUT_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | #if NEW_CHANNEL_LAYOUT 8 | #define GET_AV_CODEC_CHANNELS(context) (context->ch_layout.nb_channels) 9 | #else 10 | #define GET_AV_CODEC_CHANNELS(context) (context->channels) 11 | #endif 12 | #if NEW_CHANNEL_LAYOUT 13 | /** 14 | * @brief 从Channel Mask获取channel数 15 | * @param channel_layout Channel Mask 16 | * @return Channel数 17 | */ 18 | int get_channel_layout_channels(uint64_t channel_layout); 19 | #else 20 | #define get_channel_layout_channels av_get_channel_layout_nb_channels 21 | #endif 22 | #if __cplusplus 23 | } 24 | #endif 25 | #endif 26 | -------------------------------------------------------------------------------- /src/core.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_CORE_H 2 | #define _MUSICPLAYER2_CORE_H 3 | #if __cplusplus 4 | #include 5 | extern "C" { 6 | #endif 7 | #include "../ffmpeg_core.h" 8 | #include "ffmpeg_core_config.h" 9 | #include "libavformat/avformat.h" 10 | #include "libavcodec/avcodec.h" 11 | #include "libavdevice/avdevice.h" 12 | #include "libavutil/avutil.h" 13 | #include "libavutil/audio_fifo.h" 14 | #include "libavutil/opt.h" 15 | #include "libavutil/rational.h" 16 | #include "libavutil/timestamp.h" 17 | #include "libavfilter/avfilter.h" 18 | #include "libavfilter/buffersink.h" 19 | #include "libavfilter/buffersrc.h" 20 | #include "libswresample/swresample.h" 21 | #if HAVE_SDL3 22 | #include "SDL3/SDL.h" 23 | #else 24 | #include "SDL2/SDL.h" 25 | #endif 26 | #if _WIN32 27 | #include 28 | #define THREAD_HANDLE HANDLE 29 | #define MUTEX_HANDLE HANDLE 30 | #else 31 | #include 32 | #include 33 | #define THREAD_HANDLE pthread_t 34 | #define MUTEX_HANDLE pthread_mutex_t 35 | #define ReleaseMutex(mutex) pthread_mutex_unlock(&mutex) 36 | #define WAIT_OBJECT_0 0 37 | #define WAIT_TIMEOUT ETIMEDOUT 38 | #endif 39 | #include "c_linked_list.h" 40 | #include "urlparse.h" 41 | 42 | #ifndef __cplusplus 43 | #ifndef min 44 | #define min(x,y) (((x) < (y)) ? (x) : (y)) 45 | #endif 46 | #ifndef max 47 | #define max(a,b) (((a) > (b)) ? (a) : (b)) 48 | #endif 49 | #endif 50 | 51 | #define FFT_SAMPLE 1024 52 | 53 | #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 24, 100) 54 | #define OLD_CHANNEL_LAYOUT 1 55 | #else 56 | #define NEW_CHANNEL_LAYOUT 1 57 | #endif 58 | 59 | #if defined(__ICL) || defined (__INTEL_COMPILER) 60 | #define DISABLE_DEPRECATION_WARNINGS __pragma(warning(push)) __pragma(warning(disable:1478)) 61 | #define ENABLE_DEPRECATION_WARNINGS __pragma(warning(pop)) 62 | #elif defined(_MSC_VER) 63 | #define DISABLE_DEPRECATION_WARNINGS __pragma(warning(push)) __pragma(warning(disable:4996)) 64 | #define ENABLE_DEPRECATION_WARNINGS __pragma(warning(pop)) 65 | #else 66 | #define DISABLE_DEPRECATION_WARNINGS _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") 67 | #define ENABLE_DEPRECATION_WARNINGS _Pragma("GCC diagnostic pop") 68 | #endif 69 | 70 | typedef struct CDAData { 71 | /// version of the CD format. In May 2006, always equal to 1. 72 | uint16_t cd_format_version; 73 | /// number of the range. The first track has the number 1. 74 | uint16_t no; 75 | /// range offset, in number of frames. 76 | uint32_t range_offset; 77 | /// duration of the track, total number of frames 78 | uint32_t duration; 79 | } CDAData; 80 | typedef struct EqualizerChannel { 81 | /// The filter’s central frequency in Hz. 82 | int channel; 83 | /// The required gain or attenuation in dB. 84 | int gain; 85 | } EqualizerChannel; 86 | typedef struct EqualizerChannels { 87 | EqualizerChannel d; 88 | struct EqualizerChannels* prev; 89 | struct EqualizerChannels* next; 90 | } EqualizerChannels; 91 | #if HAVE_WASAPI 92 | typedef struct WASAPIHandle WASAPIHandle; 93 | typedef struct PositionDataList PositionDataList; 94 | #endif 95 | typedef struct MusicHandle { 96 | /// Demux 用 97 | AVFormatContext* fmt; 98 | /// 要解码的流 99 | AVStream* is; 100 | /// 解码器类型 101 | const AVCodec* codec; 102 | /// 解码器 103 | AVCodecContext* decoder; 104 | /// 用于转换音频格式 105 | struct SwrContext* swrac; 106 | /// 指定的SDL输出格式 107 | SDL_AudioSpec sdl_spec; 108 | /// 事件处理线程 109 | THREAD_HANDLE thread; 110 | #if _WIN32 111 | /// 事件处理线程线程ID 112 | DWORD thread_id; 113 | #endif 114 | /// 维护filters处理后缓冲区线程 115 | THREAD_HANDLE filter_thread; 116 | #if _WIN32 117 | /// 维护filters处理后缓冲区线程线程ID 118 | DWORD filter_thread_id; 119 | #endif 120 | /// 音频缓冲区 121 | AVAudioFifo* buffer; 122 | /// 经过filters处理后的缓冲区 123 | AVAudioFifo* filters_buffer; 124 | /// 输出格式 125 | enum AVSampleFormat target_format; 126 | /// 每样本的字节数 127 | int target_format_pbytes; 128 | /// SDL音频设备ID 129 | SDL_AudioDeviceID device_id; 130 | /// 错误信息(ffmpeg错误或Core错误 131 | int err; 132 | /// Mutex对象,作为线程锁(用于保护缓冲区和时间) 133 | MUTEX_HANDLE mutex; 134 | /// 用来确保filter graph对象可用 135 | MUTEX_HANDLE mutex2; 136 | /// 缓冲区开始时间 137 | int64_t pts; 138 | /// 缓冲区结束时间 139 | int64_t end_pts; 140 | /// 第一个sample的pts 141 | int64_t first_pts; 142 | int64_t seek_pos; 143 | /// 要播放的部分的开始时间(相对于first_pts的偏移量) 144 | int64_t part_start_pts; 145 | /// 要播放的部分的结束时间(相对于first_pts的偏移量) 146 | int64_t part_end_pts; 147 | /// 设置 148 | FfmpegCoreSettings* s; 149 | /// 用于设置filter 150 | AVFilterGraph* graph; 151 | /// filter 输入口 152 | AVFilterContext* filter_inp; 153 | /// filter 输出口 154 | AVFilterContext* filter_out; 155 | /// filter 链 156 | c_linked_list* filters; 157 | /// CDA 文件信息 158 | CDAData* cda; 159 | #if OLD_CHANNEL_LAYOUT 160 | /// 输出时的声道布局 161 | uint64_t output_channel_layout; 162 | #else 163 | /// 输出时的声道布局 164 | AVChannelLayout output_channel_layout; 165 | #endif 166 | /// 播放地址 167 | char* url; 168 | /// 解析后的播放地址 169 | UrlParseResult* parsed_url; 170 | /// 当前重新打开次数 171 | int retry_count; 172 | /// 最近一个包的时间 173 | int64_t last_pkt_pts; 174 | /// 当去filters链从buffer读入的数据量(仅复杂的filters链) 175 | int filters_buffer_offset; 176 | #if HAVE_WASAPI 177 | WASAPIHandle* wasapi; 178 | /// 存储WASAPI缓冲区信息 179 | PositionDataList* position_data; 180 | /// 用来确保WASAPI缓冲区信同时被读取、修改以及确保WASAPI存在 181 | HANDLE mutex3; 182 | /// 最近一次尝试重新初始化WASAPI时间 183 | FILETIME wasapi_last_tried; 184 | #endif 185 | /// SDL是否被初始化 186 | unsigned char sdl_initialized : 1; 187 | /// 让事件处理线程退出标志位 188 | unsigned char stoping : 1; 189 | /// 是否已读到文件尾部/读取位置达到要播放的部分的结束时间 190 | unsigned char is_eof : 1; 191 | /// 是否有错误 192 | unsigned char have_err : 1; 193 | /// 是否需要Seek 194 | unsigned char is_seek : 1; 195 | /// 是否需要设置新的缓冲区时间 196 | unsigned char set_new_pts : 1; 197 | unsigned char is_playing : 1; 198 | /// 设置是内部分配 199 | unsigned char settings_is_alloc : 1; 200 | /// 需要设置新的filters链 201 | unsigned char need_reinit_filters : 1; 202 | /// 是否正在播放CDA文件 203 | unsigned char is_cda : 1; 204 | /// 是否仅播放一部分内容 205 | unsigned char only_part : 1; 206 | /// 是否为本地文件 207 | unsigned char is_file : 1; 208 | /// 是否正在重新打开文件 209 | unsigned char is_reopen : 1; 210 | /// 是否是简单的filters链 211 | unsigned char is_easy_filters : 1; 212 | /// 刚初始化完复杂的filters,等待filters填充数据 213 | unsigned char is_wait_filters : 1; 214 | #if HAVE_WASAPI 215 | /// 当前handle是否使用WASAPI 216 | unsigned char is_use_wasapi : 1; 217 | /// WASAPI是否被初始化 218 | unsigned char wasapi_initialized : 1; 219 | /// 当前handle是否启用独占模式 220 | unsigned char is_exclusive: 1; 221 | /// 需要重新初始化WASAPI 222 | unsigned char need_reinit_wasapi: 1; 223 | #endif 224 | } MusicHandle; 225 | typedef struct MusicInfoHandle { 226 | AVFormatContext* fmt; 227 | AVStream* is; 228 | CDAData* cda; 229 | } MusicInfoHandle; 230 | typedef struct FfmpegCoreSettings { 231 | /// 音量 232 | int volume; 233 | /// 速度 234 | float speed; 235 | /// 缓存长度(单位s) 236 | int cache_length; 237 | /// 最大重试次数 238 | int max_retry_count; 239 | /// 非本地文件重试间隔时间(单位s) 240 | int url_retry_interval; 241 | /// 均衡器 242 | EqualizerChannels* equalizer_channels; 243 | /// 最大等待时间(单位ms),seek等操作最长等待完成时间 244 | int max_wait_time; 245 | #if HAVE_WASAPI 246 | /// WASAPI 独占模式最小缓冲区大小(单位:ms) 247 | int wasapi_min_buffer_time; 248 | #endif 249 | /// 混响强度 250 | float reverb_mix; 251 | /// 混响持续时间 252 | float reverb_delay; 253 | /// 混响类型 254 | int reverb_type; 255 | /// 矩阵编码方式 256 | int matrix_encoding; 257 | /// 中置声道混流级别 258 | double center_mix_level; 259 | /// 环绕声道混流级别 260 | double surround_mix_level; 261 | /// LFE声道混流级别 262 | double lfe_mix_level; 263 | /// 最大等待缓冲区时间(超时会填充空白数据)(单位:ms) 264 | int max_wait_buffer_time; 265 | #if HAVE_WASAPI 266 | /// 是否使用WASAPI 267 | unsigned char use_wasapi : 1; 268 | /// 是否启用独占模式 269 | unsigned char enable_exclusive : 1; 270 | #endif 271 | /// 是否启用混流 272 | unsigned char enable_mixing : 1; 273 | /// 是否对双声道来源禁用混流 274 | unsigned char do_not_mix_stereo : 1; 275 | /// 是否启用标准化矩阵 276 | unsigned char normalize_matrix : 1; 277 | /// 是否启用音量保护 278 | unsigned char clip_protection : 1; 279 | } FfmpegCoreSettings; 280 | #if __cplusplus 281 | } 282 | #if _WIN32 283 | std::wstring get_metadata_str(AVDictionary* dict, const char* key, int flags); 284 | #else 285 | std::string get_metadata_str(AVDictionary* dict, const char* key, int flags); 286 | #endif 287 | inline enum AVRounding operator|(enum AVRounding a, enum AVRounding b) { 288 | return static_cast(static_cast(a) | static_cast(b)); 289 | } 290 | #endif 291 | #endif 292 | -------------------------------------------------------------------------------- /src/decode.c: -------------------------------------------------------------------------------- 1 | #include "decode.h" 2 | 3 | #include "libavutil/timestamp.h" 4 | 5 | int open_decoder(MusicHandle* handle) { 6 | if (!handle || !handle->fmt || !handle->is) return FFMPEG_CORE_ERR_NULLPTR; 7 | handle->codec = avcodec_find_decoder(handle->is->codecpar->codec_id); 8 | if (!handle->codec) return FFMPEG_CORE_ERR_NO_AUDIO_OR_DECODER; 9 | handle->decoder = avcodec_alloc_context3(handle->codec); 10 | if (!handle->decoder) return FFMPEG_CORE_ERR_OOM; 11 | int re = 0; 12 | // 从输入流复制参数 13 | if ((re = avcodec_parameters_to_context(handle->decoder, handle->is->codecpar)) < 0) { 14 | av_log(NULL, AV_LOG_FATAL, "Failed to copy parameters from input stream: %s (%i)\n", av_err2str(re), re); 15 | return re; 16 | } 17 | #if OLD_CHANNEL_LAYOUT || FF_API_OLD_CHANNEL_LAYOUT 18 | DISABLE_DEPRECATION_WARNINGS 19 | if (handle->decoder->channel_layout == 0) { 20 | // 如果未设置,设置为默认值 21 | handle->decoder->channel_layout = av_get_default_channel_layout(handle->decoder->channels); 22 | } 23 | ENABLE_DEPRECATION_WARNINGS 24 | #endif 25 | // 打开解码器 26 | if ((re = avcodec_open2(handle->decoder, handle->codec, NULL)) < 0) { 27 | av_log(NULL, AV_LOG_FATAL, "Failed to open decoder \"%s\": %s (%i)\n", handle->codec->name, av_err2str(re), re); 28 | return re; 29 | } 30 | return FFMPEG_CORE_ERR_OK; 31 | } 32 | 33 | int reopen_decoder(MusicHandle* handle) { 34 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 35 | if (handle->decoder) { 36 | avcodec_free_context(&handle->decoder); 37 | } 38 | handle->decoder = avcodec_alloc_context3(handle->codec); 39 | if (!handle->decoder) return FFMPEG_CORE_ERR_OOM; 40 | int re = 0; 41 | // 从输入流复制参数 42 | if ((re = avcodec_parameters_to_context(handle->decoder, handle->is->codecpar)) < 0) { 43 | av_log(NULL, AV_LOG_FATAL, "Failed to copy parameters from input stream: %s (%i)\n", av_err2str(re), re); 44 | return re; 45 | } 46 | #if OLD_CHANNEL_LAYOUT || FF_API_OLD_CHANNEL_LAYOUT 47 | DISABLE_DEPRECATION_WARNINGS 48 | if (handle->decoder->channel_layout == 0) { 49 | // 如果未设置,设置为默认值 50 | handle->decoder->channel_layout = av_get_default_channel_layout(handle->decoder->channels); 51 | } 52 | ENABLE_DEPRECATION_WARNINGS 53 | #endif 54 | // 打开解码器 55 | if ((re = avcodec_open2(handle->decoder, handle->codec, NULL)) < 0) { 56 | av_log(NULL, AV_LOG_FATAL, "Failed to open decoder \"%s\": %s (%i)\n", handle->codec->name, av_err2str(re), re); 57 | return re; 58 | } 59 | return FFMPEG_CORE_ERR_OK; 60 | } 61 | 62 | int decode_audio_internal(MusicHandle* handle, char* writed, AVFrame* frame) { 63 | if (!handle || !writed || !frame) return FFMPEG_CORE_ERR_NULLPTR; 64 | int re = 0; 65 | re = avcodec_receive_frame(handle->decoder, frame); 66 | if (re >= 0) { 67 | if (handle->first_pts == INT64_MIN) { 68 | handle->first_pts = av_rescale_q_rnd(frame->pts, handle->is->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 69 | av_log(NULL, AV_LOG_VERBOSE, "first_pts: %s\n", av_ts2timestr(handle->first_pts, &AV_TIME_BASE_Q)); 70 | if (handle->only_part) { 71 | // 定位到开始位置 72 | if (handle->part_start_pts > 0) { 73 | handle->is_seek = 1; 74 | handle->seek_pos = handle->part_start_pts; 75 | } 76 | } 77 | } 78 | if (handle->set_new_pts && frame->pts != AV_NOPTS_VALUE) { 79 | av_log(NULL, AV_LOG_VERBOSE, "pts: %s\n", av_ts2timestr(frame->pts, &handle->is->time_base)); 80 | handle->pts = av_rescale_q_rnd(frame->pts, handle->is->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) - handle->first_pts; 81 | handle->end_pts = handle->pts; 82 | handle->set_new_pts = 0; 83 | } else if (handle->set_new_pts) { 84 | av_log(NULL, AV_LOG_VERBOSE, "skip NOPTS frame.\n"); 85 | // 跳过NOPTS的frame 86 | goto end; 87 | } 88 | // 整段数据在结束位置之后,跳过 89 | if (handle->only_part && (frame->pts - handle->first_pts) >= handle->part_end_pts) { 90 | handle->is_eof = 1; 91 | goto end; 92 | } 93 | re = convert_samples_and_add_to_fifo(handle, frame, writed); 94 | goto end; 95 | } else if (re == AVERROR(EAGAIN)) { 96 | // 数据不够,继续读取 97 | re = FFMPEG_CORE_ERR_OK; 98 | goto end; 99 | } else if (re == AVERROR_EOF) { 100 | handle->is_eof = 1; 101 | re = FFMPEG_CORE_ERR_OK; 102 | goto end; 103 | } 104 | end: 105 | return re; 106 | } 107 | 108 | int decode_audio(MusicHandle* handle, char* writed) { 109 | if (!handle || !writed) return FFMPEG_CORE_ERR_NULLPTR; 110 | AVPacket pkt; 111 | AVFrame* frame = av_frame_alloc(); 112 | *writed = 0; 113 | if (!frame) { 114 | return FFMPEG_CORE_ERR_OOM; 115 | } 116 | int re = FFMPEG_CORE_ERR_OK; 117 | while (1) { 118 | if ((re = decode_audio_internal(handle, writed, frame))) { 119 | goto end; 120 | } 121 | if (*writed || handle->is_eof) { 122 | goto end; 123 | } 124 | if ((re = av_read_frame(handle->fmt, &pkt)) < 0) { 125 | if (re == AVERROR_EOF) { 126 | handle->is_eof = 1; 127 | re = FFMPEG_CORE_ERR_OK; 128 | goto end; 129 | } 130 | goto end; 131 | } 132 | if (pkt.stream_index != handle->is->index) { 133 | // 其他流,跳过并释放引用 134 | av_packet_unref(&pkt); 135 | continue; 136 | } 137 | handle->last_pkt_pts = av_rescale_q_rnd(pkt.pts, handle->is->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 138 | if ((re = avcodec_send_packet(handle->decoder, &pkt)) < 0) { 139 | if (re == AVERROR(EAGAIN)) { 140 | re = 0; 141 | av_packet_unref(&pkt); 142 | continue; 143 | } 144 | av_packet_unref(&pkt); 145 | goto end; 146 | } 147 | av_packet_unref(&pkt); 148 | if ((re = decode_audio_internal(handle, writed, frame))) { 149 | goto end; 150 | } 151 | if (*writed || handle->is_eof) break; 152 | } 153 | end: 154 | if (frame) av_frame_free(&frame); 155 | return re; 156 | } 157 | 158 | int convert_samples_and_add_to_fifo(MusicHandle* handle, AVFrame* frame, char* writed) { 159 | if (!handle || !frame || !writed) return FFMPEG_CORE_ERR_NULLPTR; 160 | uint8_t** converted_input_samples = NULL; 161 | int re = FFMPEG_CORE_ERR_OK; 162 | AVRational base = { 1, handle->decoder->sample_rate }, target = { 1, handle->sdl_spec.freq }; 163 | int samples = frame->nb_samples; 164 | if (handle->only_part) { 165 | int tmp = av_rescale_q_rnd(handle->part_end_pts - handle->end_pts + handle->first_pts, AV_TIME_BASE_Q, base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 166 | if (samples >= tmp) { 167 | samples = tmp; 168 | handle->is_eof = 1; 169 | } 170 | } 171 | /// 输出的样本数 172 | int64_t frames = av_rescale_q_rnd(samples, base, target, AV_ROUND_UP | AV_ROUND_PASS_MINMAX); 173 | /// 实际输出样本数 174 | int converted_samples = 0; 175 | #if _WIN32 176 | DWORD res = 0; 177 | #else 178 | int res = 0; 179 | #endif 180 | if (!(converted_input_samples = malloc(sizeof(void*) * handle->sdl_spec.channels))) { 181 | re = FFMPEG_CORE_ERR_OOM; 182 | goto end; 183 | } 184 | memset(converted_input_samples, 0, sizeof(void*) * handle->sdl_spec.channels); 185 | if ((re = av_samples_alloc(converted_input_samples, NULL, handle->sdl_spec.channels, frames, handle->target_format, 0)) < 0) { 186 | re = FFMPEG_CORE_ERR_OOM; 187 | goto end; 188 | } 189 | re = 0; 190 | if ((converted_samples = swr_convert(handle->swrac, converted_input_samples, frames, (const uint8_t**)frame->extended_data, samples)) < 0) { 191 | re = converted_samples; 192 | goto end; 193 | } 194 | #if _WIN32 195 | res = WaitForSingleObject(handle->mutex, INFINITE); 196 | if (res != WAIT_OBJECT_0) { 197 | #else 198 | res = pthread_mutex_lock(&handle->mutex); 199 | if (res != 0) { 200 | #endif 201 | re = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 202 | goto end; 203 | } 204 | if ((converted_samples = av_audio_fifo_write(handle->buffer, (void**)converted_input_samples, converted_samples)) < 0) { 205 | ReleaseMutex(handle->mutex); 206 | re = converted_samples; 207 | goto end; 208 | } 209 | handle->end_pts += av_rescale_q_rnd(converted_samples, target, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 210 | *writed = 1; 211 | ReleaseMutex(handle->mutex); 212 | end: 213 | if (converted_input_samples) { 214 | av_freep(&converted_input_samples[0]); 215 | free(converted_input_samples); 216 | } 217 | return re; 218 | } 219 | -------------------------------------------------------------------------------- /src/decode.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_DECODE_H 2 | #define _MUSICPLAYER2_DECODE_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | int open_decoder(MusicHandle* handle); 8 | int reopen_decoder(MusicHandle* handle); 9 | /** 10 | * @brief 解码 11 | * @param handle Handle 12 | * @param writed 是否成功往缓冲区添加数据 13 | * @return 非0如果发生错误 14 | */ 15 | int decode_audio(MusicHandle* handle, char* writed); 16 | int convert_samples_and_add_to_fifo(MusicHandle* handle, AVFrame* frame, char* writed); 17 | #if __cplusplus 18 | } 19 | #endif 20 | #endif 21 | -------------------------------------------------------------------------------- /src/equalizer.c: -------------------------------------------------------------------------------- 1 | #include "equalizer.h" 2 | 3 | int get_equalizer_precision(enum AVSampleFormat f) { 4 | switch (f) { 5 | case AV_SAMPLE_FMT_U8: 6 | case AV_SAMPLE_FMT_S16: 7 | case AV_SAMPLE_FMT_U8P: 8 | case AV_SAMPLE_FMT_S16P: 9 | return 0; 10 | case AV_SAMPLE_FMT_S32: 11 | case AV_SAMPLE_FMT_S32P: 12 | return 1; 13 | case AV_SAMPLE_FMT_DBL: 14 | case AV_SAMPLE_FMT_DBLP: 15 | return 3; 16 | case AV_SAMPLE_FMT_FLT: 17 | case AV_SAMPLE_FMT_FLTP: 18 | default: 19 | return 2; 20 | } 21 | } 22 | 23 | int create_equalizer_filter(AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, int channel, int gain, enum AVSampleFormat f) { 24 | if (!graph || !src || !list) return FFMPEG_CORE_ERR_NULLPTR; 25 | char args[128]; 26 | char name[32]; 27 | const AVFilter* eq = avfilter_get_by_name("equalizer"); 28 | snprintf(args, sizeof(args), "f=%d:g=%d:r=%d", channel, gain, get_equalizer_precision(f)); 29 | snprintf(name, sizeof(name), "equalizer%d", channel); 30 | int re = 0; 31 | AVFilterContext* context = NULL; 32 | if ((re = avfilter_graph_create_filter(&context, eq, name, args, NULL, graph)) < 0) { 33 | av_log(NULL, AV_LOG_FATAL, "Failed to create equalizer filter \"%s\": %s (%i)\n", name, av_err2str(re), re); 34 | return re; 35 | } 36 | if (!c_linked_list_append(list, (void*)context)) { 37 | av_log(NULL, AV_LOG_FATAL, "Failed to append filter \"%s\" to list.\n", context->name); 38 | return FFMPEG_CORE_ERR_OOM; 39 | } 40 | if (c_linked_list_count(*list) > 1) { 41 | AVFilterContext* last = c_linked_list_tail(*list)->prev->d; 42 | if ((re = avfilter_link(last, 0, context, 0)) < 0) { 43 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", last->name, 0, context->name, 0, av_err2str(re), re); 44 | return re; 45 | } 46 | } else { 47 | if ((re = avfilter_link(src, 0, context, 0)) < 0) { 48 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", src->name, 0, context->name, 0, av_err2str(re), re); 49 | return re; 50 | } 51 | } 52 | return FFMPEG_CORE_ERR_OK; 53 | } 54 | -------------------------------------------------------------------------------- /src/equalizer.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_EQUALIZER_H 2 | #define _MUSICPLAYER2_EQUALIZER_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | int get_equalizer_precision(enum AVSampleFormat f); 8 | /** 9 | * @brief 创建 equalizer filter 10 | * @param index Filter 序号 11 | * @param graph Graph 12 | * @param src Graph 13 | * @param list Filters 列表 14 | * @param channel 中心频率(hz) 15 | * @param gain 16 | * @return 17 | */ 18 | int create_equalizer_filter(AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, int channel, int gain, enum AVSampleFormat f); 19 | #if __cplusplus 20 | } 21 | #endif 22 | #endif 23 | -------------------------------------------------------------------------------- /src/equalizer_settings.cpp: -------------------------------------------------------------------------------- 1 | #include "equalizer_settings.h" 2 | 3 | #include "dict.h" 4 | 5 | #define INTYPE Dict* 6 | #define OUTTYPE EqualizerChannels* 7 | 8 | void free_equalizer_channels(EqualizerChannels** channels) { 9 | auto c = (INTYPE*)channels; 10 | if (c) { 11 | auto c2 = *c; 12 | dict_free(c2); 13 | *channels = (OUTTYPE)c2; 14 | } 15 | } 16 | 17 | int set_equalizer_channel(EqualizerChannels** channels, int channel, int gain) { 18 | if (!channels) return FFMPEG_CORE_ERR_NULLPTR; 19 | auto c = (INTYPE)(*channels); 20 | if (gain == 0) { 21 | dict_delete(c, channel); 22 | } else { 23 | if (!dict_set(c, channel, gain)) { 24 | *channels = (OUTTYPE)c; 25 | return FFMPEG_CORE_ERR_OOM; 26 | } 27 | } 28 | *channels = (OUTTYPE)c; 29 | return FFMPEG_CORE_ERR_OK; 30 | } 31 | 32 | size_t equalizer_channel_count(EqualizerChannels* channels) { 33 | return dict_count((INTYPE)channels); 34 | } 35 | -------------------------------------------------------------------------------- /src/equalizer_settings.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_EQUALIZER_SETTINGS_H 2 | #define _MUSICPLAYER2_EQUALIZER_SETTINGS_H 3 | #if __cplusplus 4 | #include "core.h" 5 | extern "C" { 6 | #endif 7 | #if !__cplusplus 8 | #include "core.h" 9 | #endif 10 | void free_equalizer_channels(EqualizerChannels** channels); 11 | int set_equalizer_channel(EqualizerChannels** channels, int channel, int gain); 12 | size_t equalizer_channel_count(EqualizerChannels* channels); 13 | #if __cplusplus 14 | } 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /src/fft_data.c: -------------------------------------------------------------------------------- 1 | #include "fft_data.h" 2 | 3 | #include 4 | #include "libavcodec/avfft.h" 5 | #include "ch_layout.h" 6 | 7 | int ffmpeg_core_get_fft_data(MusicHandle* handle, float* fft_data, int len) { 8 | if (!handle || !fft_data) return FFMPEG_CORE_ERR_NULLPTR; 9 | #if HAVE_WASAPI 10 | if (handle->need_reinit_wasapi) { 11 | memset(fft_data, 0, sizeof(float) * len); 12 | return FFMPEG_CORE_ERR_OK; 13 | } 14 | #endif 15 | if (len > FFT_SAMPLE / 2) return FFMPEG_CORE_ERR_TOO_BIG_FFT_DATA_LEN; 16 | int cal_samples = FFT_SAMPLE; 17 | AVFrame* f = av_frame_alloc(), * f2 = NULL; 18 | RDFTContext* context = NULL; 19 | SwrContext* swr = NULL; 20 | #if _WIN32 21 | DWORD re = 0; 22 | #else 23 | int re = 0; 24 | #endif 25 | int r = FFMPEG_CORE_ERR_OK; 26 | int nbits = log2(cal_samples); 27 | int total_samples = FFT_SAMPLE * 10; 28 | float* datas = NULL; 29 | int inv = cal_samples / len; 30 | if (!f) { 31 | return FFMPEG_CORE_ERR_OOM; 32 | } 33 | f->format = handle->target_format; 34 | f->nb_samples = total_samples; 35 | #if NEW_CHANNEL_LAYOUT 36 | if ((r = av_channel_layout_copy(&f->ch_layout, &handle->output_channel_layout))) { 37 | memset(fft_data, 0, sizeof(float) * len); 38 | goto end; 39 | } 40 | #else 41 | f->channel_layout = handle->output_channel_layout; 42 | f->channels = handle->sdl_spec.channels; 43 | #endif 44 | if ((r = av_frame_get_buffer(f, 0)) < 0) { 45 | memset(fft_data, 0, sizeof(float) * len); 46 | goto end; 47 | } 48 | if ((r = av_frame_make_writable(f)) < 0) { 49 | memset(fft_data, 0, sizeof(float) * len); 50 | goto end; 51 | } 52 | #if _WIN32 53 | re = WaitForSingleObject(handle->mutex, INFINITE); 54 | if (re != WAIT_OBJECT_0) { 55 | #else 56 | re = pthread_mutex_lock(&handle->mutex); 57 | if (re != 0) { 58 | #endif 59 | r = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 60 | memset(fft_data, 0, sizeof(float) * len); 61 | goto end; 62 | } 63 | if (av_audio_fifo_size(handle->buffer) < total_samples) { 64 | ReleaseMutex(handle->mutex); 65 | r = FFMPEG_CORE_ERR_OK; 66 | memset(fft_data, 0, sizeof(float) * len); 67 | goto end; 68 | } 69 | if ((r = av_audio_fifo_peek(handle->buffer, (void**)f->data, total_samples)) < 0) { 70 | ReleaseMutex(handle->mutex); 71 | memset(fft_data, 0, sizeof(float) * len); 72 | goto end; 73 | } 74 | ReleaseMutex(handle->mutex); 75 | r = 0; 76 | if (!(context = av_rdft_init(nbits, DFT_R2C))) { 77 | r = FFMPEG_CORE_ERR_OOM; 78 | memset(fft_data, 0, sizeof(float) * len); 79 | goto end; 80 | } 81 | if (f->format == AV_SAMPLE_FMT_FLT && GET_AV_CODEC_CHANNELS(f) == 1) { 82 | for (int j = 0; j < 10; j++) { 83 | av_rdft_calc(context, (FFTSample*)f->data[0] + (size_t)FFT_SAMPLE * j); 84 | } 85 | } else { 86 | #if NEW_CHANNEL_LAYOUT 87 | AVChannelLayout tmp; 88 | memset(&tmp, 0, sizeof(AVChannelLayout)); 89 | av_channel_layout_default(&tmp, 1); 90 | if ((r = swr_alloc_set_opts2(&swr, &tmp, AV_SAMPLE_FMT_FLT, handle->sdl_spec.freq, &handle->output_channel_layout, handle->target_format, handle->sdl_spec.freq, 0, NULL))) { 91 | av_channel_layout_uninit(&tmp); 92 | memset(fft_data, 0, sizeof(float) * len); 93 | goto end; 94 | } 95 | av_channel_layout_uninit(&tmp); 96 | #else 97 | swr = swr_alloc_set_opts(NULL, av_get_default_channel_layout(1), AV_SAMPLE_FMT_FLT, handle->sdl_spec.freq, handle->output_channel_layout, handle->target_format, handle->sdl_spec.freq, 0, NULL); 98 | #endif 99 | if (!swr) { 100 | r = FFMPEG_CORE_ERR_OOM; 101 | memset(fft_data, 0, sizeof(float) * len); 102 | goto end; 103 | } 104 | if ((r = swr_init(swr)) < 0) { 105 | memset(fft_data, 0, sizeof(float) * len); 106 | goto end; 107 | } 108 | f2 = av_frame_alloc(); 109 | if (!f2) { 110 | r = FFMPEG_CORE_ERR_OOM; 111 | memset(fft_data, 0, sizeof(float) * len); 112 | goto end; 113 | } 114 | f2->format = AV_SAMPLE_FMT_FLT; 115 | #if NEW_CHANNEL_LAYOUT 116 | av_channel_layout_default(&f2->ch_layout, 1); 117 | #else 118 | f2->channels = 1; 119 | f2->channel_layout = av_get_default_channel_layout(1); 120 | #endif 121 | f2->nb_samples = total_samples; 122 | if ((r = av_frame_get_buffer(f2, 0)) < 0) { 123 | memset(fft_data, 0, sizeof(float) * len); 124 | goto end; 125 | } 126 | if ((r = av_frame_make_writable(f2)) < 0) { 127 | memset(fft_data, 0, sizeof(float) * len); 128 | goto end; 129 | } 130 | if ((r = swr_convert(swr, f2->data, f2->nb_samples, (const uint8_t**)f->data, f->nb_samples)) < 0) { 131 | memset(fft_data, 0, sizeof(float) * len); 132 | goto end; 133 | } 134 | r = 0; 135 | for (int j = 0; j < 10; j++) 136 | av_rdft_calc(context, (FFTSample*)f2->data[0] + (size_t)j * FFT_SAMPLE); 137 | } 138 | datas = f2 ? (float*)f2->data[0] : (float*)f->data[0]; 139 | memset(fft_data, 0, sizeof(float) * len); 140 | for (int j = 0; j < 10; j++) { 141 | for (int i = 0; i < len; i++) { 142 | if (i == 0) 143 | fft_data[i] += datas[i + FFT_SAMPLE * j] / FFT_SAMPLE / 10; 144 | else 145 | fft_data[i] += datas[i + FFT_SAMPLE * j] / FFT_SAMPLE / 5; 146 | } 147 | } 148 | r = 0; 149 | end: 150 | if (f) av_frame_free(&f); 151 | if (context) av_rdft_end(context); 152 | if (swr) swr_free(&swr); 153 | if (f2) av_frame_free(&f2); 154 | return r; 155 | } 156 | -------------------------------------------------------------------------------- /src/fft_data.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_FFT_DATA_H 2 | #define _MUSICPLAYER2_FFT_DATA_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | #if __cplusplus 8 | } 9 | #endif 10 | #endif 11 | -------------------------------------------------------------------------------- /src/file.cpp: -------------------------------------------------------------------------------- 1 | #include "file.h" 2 | 3 | #include 4 | #include "fileop.h" 5 | 6 | #define chkstr(s) (s ? s : "") 7 | 8 | int is_file(UrlParseResult* url) { 9 | if (!url) return 0; 10 | std::string scheme(chkstr(url->scheme)); 11 | if (scheme.empty() || scheme == "file") return 1; 12 | if (scheme.length() == 1 && isalpha(scheme[0])) return 1; 13 | return 0; 14 | } 15 | 16 | int is_file_exists(MusicHandle* handle) { 17 | if (!handle || !handle->is_file) return 0; 18 | std::string scheme(chkstr(handle->parsed_url->scheme)); 19 | if (scheme != "file") { 20 | return fileop::exists(handle->url); 21 | } else { 22 | std::string fn(chkstr(handle->parsed_url->netloc)); 23 | fn += chkstr(handle->parsed_url->path); 24 | std::string query(chkstr(handle->parsed_url->query)); 25 | if (!query.empty()) { 26 | fn += "?"; 27 | fn += query; 28 | } 29 | std::string fragment(chkstr(handle->parsed_url->fragment)); 30 | if (!fragment.empty()) { 31 | fn += "#"; 32 | fn += fragment; 33 | } 34 | return fileop::exists(fn); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/file.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_FILE_H 2 | #define _MUSICPLAYER2_FILE_H 3 | #include "core.h" 4 | #if __cplusplus 5 | extern "C" { 6 | #endif 7 | int is_file(UrlParseResult* url); 8 | int is_file_exists(MusicHandle* handle); 9 | #if __cplusplus 10 | } 11 | #endif 12 | #endif 13 | -------------------------------------------------------------------------------- /src/filter.c: -------------------------------------------------------------------------------- 1 | #include "filter.h" 2 | 3 | #include "output.h" 4 | #include "volume.h" 5 | #include "speed.h" 6 | #include "equalizer.h" 7 | #include "reverb.h" 8 | 9 | #include "time_util.h" 10 | 11 | int need_filters(FfmpegCoreSettings* s) { 12 | if (!s) return 0; 13 | if (!avfilter_get_by_name("abuffersink") || !avfilter_get_by_name("abuffer")) { 14 | return 0; 15 | } 16 | if (s->volume != 100 && avfilter_get_by_name("volume")) { 17 | return 1; 18 | } 19 | if (get_speed(s->speed) != 1000 && avfilter_get_by_name("atempo")) { 20 | return 1; 21 | } 22 | if (s->equalizer_channels && avfilter_get_by_name("equalizer")) { 23 | return 1; 24 | } 25 | if (s->reverb_type && avfilter_get_by_name("aecho")) { 26 | return 1; 27 | } 28 | return 0; 29 | } 30 | 31 | int init_filters(MusicHandle* handle) { 32 | if (!handle || !handle->s) return FFMPEG_CORE_ERR_NULLPTR; 33 | if (!need_filters(handle->s)) return FFMPEG_CORE_ERR_OK; 34 | int re = FFMPEG_CORE_ERR_OK; 35 | int is_easy_filters = 1; 36 | int speed = get_speed(handle->s->speed); 37 | if ((re = create_src_and_sink(&handle->graph, &handle->filter_inp, &handle->filter_out, handle))) { 38 | return re; 39 | } 40 | if (handle->s->volume != 100 && avfilter_get_by_name("volume")) { 41 | if ((re = create_volume_filter(0, handle->graph, handle->filter_inp, &handle->filters, handle->s->volume, handle->target_format))) { 42 | return re; 43 | } 44 | } 45 | if (speed != 1000 && avfilter_get_by_name("atempo")) { 46 | int index = 0; 47 | while (speed != 1000) { 48 | if ((re = create_speed_filter(index, handle->graph, handle->filter_inp, &handle->filters, &speed))) { 49 | return re; 50 | } 51 | index++; 52 | } 53 | is_easy_filters = 0; 54 | } 55 | if (handle->s->equalizer_channels && avfilter_get_by_name("equalizer")) { 56 | EqualizerChannels* now = handle->s->equalizer_channels; 57 | if ((re = create_equalizer_filter(handle->graph, handle->filter_inp, &handle->filters, now->d.channel, now->d.gain, handle->target_format))) { 58 | return re; 59 | } 60 | while (now->next) { 61 | now = now->next; 62 | if ((re = create_equalizer_filter(handle->graph, handle->filter_inp, &handle->filters, now->d.channel, now->d.gain, handle->target_format))) { 63 | return re; 64 | } 65 | } 66 | is_easy_filters = 0; 67 | } 68 | if (handle->s->reverb_type && avfilter_get_by_name("aecho")) { 69 | if ((re = create_reverb_filter(handle->graph, handle->filter_inp, &handle->filters, handle->s->reverb_delay, handle->s->reverb_mix, handle->s->reverb_type))) { 70 | return re; 71 | } 72 | is_easy_filters = 0; 73 | } 74 | if (c_linked_list_count(handle->filters) == 0) { 75 | avfilter_graph_free(&handle->graph); 76 | handle->graph = NULL; 77 | handle->filter_inp = NULL; 78 | handle->filter_out = NULL; 79 | return FFMPEG_CORE_ERR_OK; 80 | } 81 | AVFilterContext* last = c_linked_list_tail(handle->filters)->d; 82 | if ((re = avfilter_link(last, 0, handle->filter_out, 0)) < 0) { 83 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", last->name, 0, handle->filter_out->name, 0, av_err2str(re), re); 84 | return re; 85 | } 86 | if ((re = avfilter_graph_config(handle->graph, NULL)) < 0) { 87 | av_log(NULL, AV_LOG_FATAL, "Failed to check config of filters: %s (%i)\n", av_err2str(re), re); 88 | return re; 89 | } 90 | handle->is_easy_filters = is_easy_filters; 91 | if (!handle->is_easy_filters) { 92 | handle->is_wait_filters = 1; 93 | } 94 | return FFMPEG_CORE_ERR_OK; 95 | } 96 | 97 | int reinit_filters(MusicHandle* handle) { 98 | if (!handle || !handle->s) return FFMPEG_CORE_ERR_NULLPTR; 99 | if (!need_filters(handle->s)) { 100 | if (!handle->graph) return FFMPEG_CORE_ERR_OK; 101 | #if _WIN32 102 | DWORD re = WaitForSingleObject(handle->mutex2, INFINITE); 103 | if (re != WAIT_OBJECT_0) { 104 | #else 105 | int re = pthread_mutex_lock(&handle->mutex2); 106 | if (re != 0) { 107 | #endif 108 | return FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 109 | } 110 | #if _WIN32 111 | re = WaitForSingleObject(handle->mutex, INFINITE); 112 | if (re != WAIT_OBJECT_0) { 113 | #else 114 | re = pthread_mutex_lock(&handle->mutex); 115 | if (re != 0) { 116 | #endif 117 | ReleaseMutex(handle->mutex2); 118 | return FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 119 | } 120 | avfilter_graph_free(&handle->graph); 121 | handle->graph = NULL; 122 | handle->filter_inp = NULL; 123 | handle->filter_out = NULL; 124 | c_linked_list_clear(&handle->filters, NULL); 125 | av_audio_fifo_reset(handle->filters_buffer); 126 | handle->filters_buffer_offset = 0; 127 | ReleaseMutex(handle->mutex); 128 | ReleaseMutex(handle->mutex2); 129 | return FFMPEG_CORE_ERR_OK; 130 | } 131 | int re = FFMPEG_CORE_ERR_OK; 132 | AVFilterGraph* graph = NULL; 133 | AVFilterContext* inc = NULL, * outc = NULL; 134 | c_linked_list* list = NULL; 135 | int is_easy_filters = 1; 136 | int speed = get_speed(handle->s->speed); 137 | if ((re = create_src_and_sink(&graph, &inc, &outc, handle)) < 0) { 138 | goto end; 139 | } 140 | if (handle->s->volume != 100 && avfilter_get_by_name("volume")) { 141 | if ((re = create_volume_filter(0, graph, inc, &list, handle->s->volume, handle->target_format))) { 142 | goto end; 143 | } 144 | } 145 | if (speed != 1000 && avfilter_get_by_name("atempo")) { 146 | int index = 0; 147 | while (speed != 1000) { 148 | if ((re = create_speed_filter(index, graph, inc, &list, &speed))) { 149 | goto end; 150 | } 151 | index++; 152 | } 153 | is_easy_filters = 0; 154 | } 155 | if (handle->s->equalizer_channels && avfilter_get_by_name("equalizer")) { 156 | EqualizerChannels* now = handle->s->equalizer_channels; 157 | if ((re = create_equalizer_filter(graph, inc, &list, now->d.channel, now->d.gain, handle->target_format))) { 158 | goto end; 159 | } 160 | while (now->next) { 161 | now = now->next; 162 | if ((re = create_equalizer_filter(graph, inc, &list, now->d.channel, now->d.gain, handle->target_format))) { 163 | goto end; 164 | } 165 | } 166 | is_easy_filters = 0; 167 | } 168 | if (handle->s->reverb_type && avfilter_get_by_name("aecho")) { 169 | if ((re = create_reverb_filter(graph, inc, &list, handle->s->reverb_delay, handle->s->reverb_mix, handle->s->reverb_type))) { 170 | goto end; 171 | } 172 | is_easy_filters = 0; 173 | } 174 | if (c_linked_list_count(list) == 0) { 175 | if (handle->graph) { 176 | #if _WIN32 177 | DWORD r = WaitForSingleObject(handle->mutex2, INFINITE); 178 | #else 179 | int r = pthread_mutex_lock(&handle->mutex2); 180 | #endif 181 | if (r != WAIT_OBJECT_0) { 182 | re = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 183 | goto end; 184 | } 185 | #if _WIN32 186 | r = WaitForSingleObject(handle->mutex, INFINITE); 187 | #else 188 | r = pthread_mutex_lock(&handle->mutex); 189 | #endif 190 | if (r != WAIT_OBJECT_0) { 191 | ReleaseMutex(handle->mutex2); 192 | re = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 193 | goto end; 194 | } 195 | avfilter_graph_free(&handle->graph); 196 | handle->graph = NULL; 197 | handle->filter_inp = NULL; 198 | handle->filter_out = NULL; 199 | c_linked_list_clear(&handle->filters, NULL); 200 | av_audio_fifo_reset(handle->filters_buffer); 201 | handle->filters_buffer_offset = 0; 202 | ReleaseMutex(handle->mutex); 203 | ReleaseMutex(handle->mutex2); 204 | } 205 | re = FFMPEG_CORE_ERR_OK; 206 | goto end; 207 | } 208 | AVFilterContext* last = c_linked_list_tail(list)->d; 209 | if ((re = avfilter_link(last, 0, outc, 0)) < 0) { 210 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", last->name, 0, outc->name, 0, av_err2str(re), re); 211 | goto end; 212 | } 213 | if ((re = avfilter_graph_config(graph, NULL)) < 0) { 214 | av_log(NULL, AV_LOG_FATAL, "Failed to check config of filters: %s (%i)\n", av_err2str(re), re); 215 | goto end; 216 | } 217 | #if _WIN32 218 | DWORD r = WaitForSingleObject(handle->mutex2, INFINITE); 219 | #else 220 | int r = pthread_mutex_lock(&handle->mutex2); 221 | #endif 222 | if (r != WAIT_OBJECT_0) { 223 | re = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 224 | goto end; 225 | } 226 | #if _WIN32 227 | r = WaitForSingleObject(handle->mutex, INFINITE); 228 | #else 229 | r = pthread_mutex_lock(&handle->mutex); 230 | #endif 231 | if (r != WAIT_OBJECT_0) { 232 | ReleaseMutex(handle->mutex2); 233 | re = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 234 | goto end; 235 | } 236 | if (handle->graph) { 237 | avfilter_graph_free(&handle->graph); 238 | handle->graph = NULL; 239 | handle->filter_inp = NULL; 240 | handle->filter_out = NULL; 241 | av_audio_fifo_reset(handle->filters_buffer); 242 | handle->filters_buffer_offset = 0; 243 | c_linked_list_clear(&handle->filters, NULL); 244 | } 245 | handle->graph = graph; 246 | handle->filter_inp = inc; 247 | handle->filter_out = outc; 248 | handle->filters = list; 249 | handle->is_easy_filters = is_easy_filters; 250 | if (!handle->is_easy_filters) { 251 | handle->is_wait_filters = 1; 252 | } 253 | ReleaseMutex(handle->mutex); 254 | ReleaseMutex(handle->mutex2); 255 | return FFMPEG_CORE_ERR_OK; 256 | end: 257 | if (graph) { 258 | avfilter_graph_free(&graph); 259 | c_linked_list_clear(&list, NULL); 260 | } 261 | return re; 262 | } 263 | 264 | int create_src_and_sink(AVFilterGraph** graph, AVFilterContext** src, AVFilterContext** sink, MusicHandle* handle) { 265 | if (!graph || !src || !sink) return FFMPEG_CORE_ERR_NULLPTR; 266 | const AVFilter* buffersink = avfilter_get_by_name("abuffersink"), * buffer = avfilter_get_by_name("abuffer"); 267 | if (!(*graph = avfilter_graph_alloc())) { 268 | av_log(NULL, AV_LOG_FATAL, "Failed to allocate filter graph.\n"); 269 | return FFMPEG_CORE_ERR_OOM; 270 | } 271 | int re = 0; 272 | char args[1024]; 273 | char channel_layout[512]; 274 | // 输入的设置:描述见 ffmpeg -h filter=abuffer 275 | #if NEW_CHANNEL_LAYOUT 276 | av_channel_layout_describe(&handle->output_channel_layout, channel_layout, sizeof(channel_layout)); 277 | #else 278 | uint64_t layout = handle->output_channel_layout; 279 | av_get_channel_layout_string(channel_layout, sizeof(channel_layout), handle->sdl_spec.channels, layout); 280 | #endif 281 | snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s:channels=%d", handle->is->time_base.num, handle->is->time_base.den, handle->sdl_spec.freq, av_get_sample_fmt_name(handle->target_format), channel_layout, handle->sdl_spec.channels); 282 | if ((re = avfilter_graph_create_filter(src, buffer, "in", args, NULL, *graph)) < 0) { 283 | av_log(NULL, AV_LOG_FATAL, "Failed to create input filter: %s (%i)\n", av_err2str(re), re); 284 | return re; 285 | } 286 | #if NEW_CHANNEL_LAYOUT 287 | snprintf(args, sizeof(args), "ch_layouts=%s", channel_layout); 288 | if ((re = avfilter_graph_create_filter(sink, buffersink, "out", args, NULL, *graph)) < 0) { 289 | #else 290 | if ((re = avfilter_graph_create_filter(sink, buffersink, "out", NULL, NULL, *graph)) < 0) { 291 | #endif 292 | av_log(NULL, AV_LOG_FATAL, "Failed to create output filter: %s (%i)\n", av_err2str(re), re); 293 | return re; 294 | } 295 | // 输出设置 296 | // 描述见 ffmpeg -h filter=abuffersink 297 | // 具体类型参考 ffmpeg 源代码 libavfilter/buffersink.c 里的 abuffersink_options 298 | enum AVSampleFormat sample_fmts[2] = { handle->target_format , AV_SAMPLE_FMT_NONE }; 299 | if ((re = av_opt_set_int_list(*sink, "sample_fmts", sample_fmts, AV_SAMPLE_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0) { 300 | av_log(NULL, AV_LOG_FATAL, "Failed to set sample_fmts to output filter: %s (%i)\n", av_err2str(re), re); 301 | return re; 302 | } 303 | int sample_rates[2] = { handle->sdl_spec.freq , 0 }; 304 | if ((re = av_opt_set_int_list(*sink, "sample_rates", sample_rates, 0, AV_OPT_SEARCH_CHILDREN)) < 0) { 305 | av_log(NULL, AV_LOG_FATAL, "Failed to set sample_rates to output filter: %s (%i)\n", av_err2str(re), re); 306 | return re; 307 | } 308 | #if OLD_CHANNEL_LAYOUT 309 | int64_t channel_layouts[2] = { layout , 0 }; 310 | if ((re = av_opt_set_int_list(*sink, "channel_layouts", channel_layouts, 0, AV_OPT_SEARCH_CHILDREN)) < 0) { 311 | av_log(NULL, AV_LOG_FATAL, "Failed to set channel_layouts to output filter: %s (%i)\n", av_err2str(re), re); 312 | return re; 313 | } 314 | int channel_counts[2] = { handle->sdl_spec.channels, 0 }; 315 | if ((re = av_opt_set_int_list(*sink, "channel_counts", channel_counts, 0, AV_OPT_SEARCH_CHILDREN)) < 0) { 316 | av_log(NULL, AV_LOG_FATAL, "Failed to set channel_counts to output filter: %s (%i)\n", av_err2str(re), re); 317 | return re; 318 | } 319 | #endif 320 | return FFMPEG_CORE_ERR_OK; 321 | } 322 | 323 | int add_data_to_filters_buffer(MusicHandle* handle) { 324 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 325 | #if _WIN32 326 | DWORD re = WaitForSingleObject(handle->mutex2, INFINITE); 327 | #else 328 | int re = pthread_mutex_lock(&handle->mutex2); 329 | struct timespec ts; 330 | size_t ts_now; 331 | #endif 332 | int r = FFMPEG_CORE_ERR_OK; 333 | AVFrame* in = NULL, * out = NULL; 334 | int samples_need = 1000; 335 | int samples_need_in = 0; 336 | /// 音频缓冲区buffer要peek的起始位置 337 | int input_samples_offset = 0; 338 | int buffer_size = 0; 339 | int writed = 0; 340 | unsigned char have_mutex = 0; 341 | AVRational base = { 1000, 1000 }, target = { 1, 1 }; 342 | if (re == WAIT_TIMEOUT) return FFMPEG_CORE_ERR_OK; 343 | else if (re != WAIT_OBJECT_0) return FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 344 | #if _WIN32 345 | re = WaitForSingleObject(handle->mutex, 10); 346 | #else 347 | ts_now = time_time_ns(); 348 | ts_now += 10 * 1000000; 349 | ts.tv_sec = ts_now / 1000000000; 350 | ts.tv_nsec = ts_now % 1000000000; 351 | re = pthread_mutex_timedlock(&handle->mutex, &ts); 352 | #endif 353 | if (re == WAIT_TIMEOUT) { 354 | ReleaseMutex(handle->mutex2); 355 | return FFMPEG_CORE_ERR_OK; 356 | } else if (re != WAIT_OBJECT_0) { 357 | ReleaseMutex(handle->mutex2); 358 | return FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 359 | } 360 | have_mutex = 1; 361 | if (!handle->graph || handle->is_easy_filters) { 362 | ReleaseMutex(handle->mutex); 363 | ReleaseMutex(handle->mutex2); 364 | return FFMPEG_CORE_ERR_OK; 365 | } 366 | buffer_size = av_audio_fifo_size(handle->filters_buffer); 367 | if (buffer_size > handle->sdl_spec.freq) { 368 | r = FFMPEG_CORE_ERR_OK; 369 | goto end; 370 | } 371 | base.num = get_speed(handle->s->speed); 372 | input_samples_offset = handle->filters_buffer_offset; 373 | samples_need_in = av_rescale_q_rnd(samples_need, base, target, AV_ROUND_UP | AV_ROUND_PASS_MINMAX); 374 | if (av_audio_fifo_size(handle->buffer) <= input_samples_offset) { 375 | ReleaseMutex(handle->mutex); 376 | have_mutex = 0; 377 | if (handle->is_eof) { 378 | if ((r = av_buffersrc_add_frame(handle->filter_inp, NULL)) < 0) { 379 | goto end; 380 | } 381 | out = av_frame_alloc(); 382 | if (!out) { 383 | r = FFMPEG_CORE_ERR_OOM; 384 | goto end; 385 | } 386 | goto outp; 387 | } 388 | r = FFMPEG_CORE_ERR_OK; 389 | goto end; 390 | } 391 | in = av_frame_alloc(); 392 | out = av_frame_alloc(); 393 | if (!in || !out) { 394 | r = FFMPEG_CORE_ERR_OOM; 395 | goto end; 396 | } 397 | #if NEW_CHANNEL_LAYOUT 398 | if ((r = av_channel_layout_copy(&in->ch_layout, &handle->output_channel_layout))) { 399 | goto end; 400 | } 401 | #else 402 | in->channels = handle->sdl_spec.channels; 403 | in->channel_layout = handle->output_channel_layout; 404 | #endif 405 | in->format = handle->target_format; 406 | in->sample_rate = handle->sdl_spec.freq; 407 | samples_need_in = min(samples_need_in, av_audio_fifo_size(handle->buffer) - input_samples_offset); 408 | in->nb_samples = samples_need_in; 409 | if ((r = av_frame_get_buffer(in, 0)) < 0) { 410 | goto end; 411 | } 412 | writed = av_audio_fifo_peek_at(handle->buffer, (void**)in->data, samples_need_in, input_samples_offset); 413 | if (writed < 0) { 414 | r = writed; 415 | goto end; 416 | } 417 | handle->filters_buffer_offset += writed; 418 | in->nb_samples = writed; 419 | ReleaseMutex(handle->mutex); 420 | have_mutex = 0; 421 | if ((r = av_buffersrc_add_frame(handle->filter_inp, in)) < 0) { 422 | goto end; 423 | } 424 | outp: 425 | if ((r = av_buffersink_get_frame(handle->filter_out, out)) < 0) { 426 | if (r == AVERROR(EAGAIN)) r = FFMPEG_CORE_ERR_OK; 427 | goto end; 428 | } 429 | #if _WIN32 430 | re = WaitForSingleObject(handle->mutex, INFINITE); 431 | #else 432 | re = pthread_mutex_lock(&handle->mutex); 433 | #endif 434 | if (re != WAIT_OBJECT_0) { 435 | r = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 436 | goto end; 437 | } 438 | have_mutex = 1; 439 | if ((r = av_audio_fifo_write(handle->filters_buffer, (void*)out->data, out->nb_samples)) < 0) { 440 | goto end; 441 | } 442 | r = FFMPEG_CORE_ERR_OK; 443 | end: 444 | if (in) av_frame_free(&in); 445 | if (out) av_frame_free(&out); 446 | if (have_mutex) ReleaseMutex(handle->mutex); 447 | ReleaseMutex(handle->mutex2); 448 | return r; 449 | } 450 | 451 | void reset_filters_buffer(MusicHandle* handle) { 452 | if (!handle) return; 453 | av_audio_fifo_reset(handle->filters_buffer); 454 | handle->filters_buffer_offset = 0; 455 | if (!handle->is_easy_filters) { 456 | handle->is_wait_filters = 1; 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /src/filter.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_FILTER_H 2 | #define _MUSICPLAYER2_FILTER_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | int need_filters(FfmpegCoreSettings* s); 8 | int init_filters(MusicHandle* handle); 9 | int reinit_filters(MusicHandle* handle); 10 | /** 11 | * @brief 新建一个新的FilterGraph,并且分配好输出和输入 12 | * @param graph FilterGraph 13 | * @param src 输入节点 14 | * @param sink 输出节点 15 | * @param handle 读取必要的数据用 16 | * @return 17 | */ 18 | int create_src_and_sink(AVFilterGraph** graph, AVFilterContext** src, AVFilterContext** sink, MusicHandle* handle); 19 | /// 往filters_buffer塞数据 20 | int add_data_to_filters_buffer(MusicHandle* handle); 21 | void reset_filters_buffer(MusicHandle* handle); 22 | #if __cplusplus 23 | } 24 | #endif 25 | #endif 26 | -------------------------------------------------------------------------------- /src/linked_list/float_linked_list.cpp: -------------------------------------------------------------------------------- 1 | #include "float_linked_list.h" 2 | 3 | #include 4 | #include "cpp2c.h" 5 | #include "linked_list.h" 6 | 7 | #define INTYPE struct LinkedList* 8 | #define OUTTYPE float_linked_list* 9 | 10 | int float_linked_list_append(float_linked_list** list, float data) { 11 | if (!list) return 0; 12 | auto l = (INTYPE)(*list); 13 | auto re = linked_list_append(l, &data); 14 | *list = (OUTTYPE)l; 15 | return re ? 1 : 0; 16 | } 17 | 18 | void float_linked_list_clear(float_linked_list** list) { 19 | if (!list) return; 20 | auto l = (INTYPE)(*list); 21 | linked_list_clear(l); 22 | *list = (OUTTYPE)l; 23 | } 24 | 25 | char* float_linked_list_join(float_linked_list* list, char join_char) { 26 | if (!list) return nullptr; 27 | std::string s; 28 | char buf[32]; 29 | snprintf(buf, sizeof(buf), "%f", list->d); 30 | s = buf; 31 | float_linked_list* cur = list; 32 | while (cur->next) { 33 | cur = cur->next; 34 | snprintf(buf, sizeof(buf), "%f", cur->d); 35 | s += join_char; 36 | s += buf; 37 | } 38 | char* re = nullptr; 39 | if (cpp2c::string2char(s, re)) return re; 40 | return nullptr; 41 | } 42 | -------------------------------------------------------------------------------- /src/linked_list/float_linked_list.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPALYER2_FLOAT_LINKED_LIST_H 2 | #define _MUSICPALYER2_FLOAT_LINKED_LIST_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | typedef struct float_linked_list { 7 | float d; 8 | struct float_linked_list* prev; 9 | struct float_linked_list* next; 10 | } float_linked_list; 11 | int float_linked_list_append(float_linked_list** list, float data); 12 | void float_linked_list_clear(float_linked_list** list); 13 | char* float_linked_list_join(float_linked_list* list, char join_char); 14 | #if __cplusplus 15 | } 16 | #endif 17 | #endif 18 | -------------------------------------------------------------------------------- /src/linked_list/position_data_linked_list.cpp: -------------------------------------------------------------------------------- 1 | #include "position_data_linked_list.h" 2 | 3 | #include "linked_list.h" 4 | 5 | #define INTYPE struct LinkedList* 6 | #define OUTTYPE PositionDataList* 7 | 8 | int position_data_list_append(PositionDataList** list, int64_t length, char have_data) { 9 | if (!list) return 0; 10 | auto l = (INTYPE)(*list); 11 | PositionData p = { length, have_data }; 12 | auto re = linked_list_append(l, &p); 13 | *list = (OUTTYPE)l; 14 | return re ? 1 : 0; 15 | } 16 | 17 | void position_data_list_clear(PositionDataList** list) { 18 | if (!list) return; 19 | auto l = (INTYPE)(*list); 20 | linked_list_clear(l); 21 | *list = (OUTTYPE)l; 22 | } 23 | 24 | void position_data_remove_before(PositionDataList* node) { 25 | linked_list_remove_before((INTYPE)node); 26 | } 27 | 28 | PositionDataList* position_data_list_tail(PositionDataList* list) { 29 | auto l = (INTYPE)list; 30 | return (OUTTYPE)linked_list_tail(l); 31 | } 32 | -------------------------------------------------------------------------------- /src/linked_list/position_data_linked_list.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPALYER2_POSITION_DATA_LINKED_LIST_H 2 | #define _MUSICPALYER2_POSITION_DATA_LINKED_LIST_H 3 | #if __cplusplus 4 | #include "../core.h" 5 | extern "C" { 6 | #endif 7 | #if !__cplusplus 8 | #include "../core.h" 9 | #endif 10 | /// 存放WASAPI缓冲区内一段数据的类型 11 | typedef struct PositionData { 12 | /// 数据长度 13 | int64_t length; 14 | /// 是否有实际数据 15 | char have_data; 16 | } PositionData; 17 | #if __cplusplus 18 | typedef struct PositionDataList: public PositionData { 19 | #else 20 | typedef struct PositionDataList { 21 | struct PositionData; 22 | #endif 23 | struct PositionDataList* prev; 24 | struct PositionDataList* next; 25 | } PositionDataList; 26 | int position_data_list_append(PositionDataList** list, int64_t length, char have_data); 27 | void position_data_list_clear(PositionDataList** list); 28 | void position_data_remove_before(PositionDataList* node); 29 | PositionDataList* position_data_list_tail(PositionDataList* list); 30 | #if __cplusplus 31 | } 32 | #endif 33 | #endif 34 | -------------------------------------------------------------------------------- /src/loop.c: -------------------------------------------------------------------------------- 1 | #include "loop.h" 2 | 3 | #include 4 | #include "libavutil/timestamp.h" 5 | #include "decode.h" 6 | #include "filter.h" 7 | #include "file.h" 8 | #include "cda.h" 9 | #include "open.h" 10 | #if HAVE_WASAPI 11 | #include "wasapi.h" 12 | #endif 13 | #include "time_util.h" 14 | 15 | #define ft2ts(t) (((uint64_t)t.dwHighDateTime << 32) | (uint64_t)t.dwLowDateTime) 16 | 17 | int seek_to_pos(MusicHandle* handle) { 18 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 19 | int re = FFMPEG_CORE_ERR_OK; 20 | #if _WIN32 21 | DWORD r = WaitForSingleObject(handle->mutex, INFINITE); 22 | #else 23 | int r = pthread_mutex_lock(&handle->mutex); 24 | #endif 25 | AVRational base = { 1, handle->sdl_spec.freq }; 26 | if (r != WAIT_OBJECT_0) { 27 | re = FFMPEG_CORE_ERR_WAIT_MUTEX_FAILED; 28 | goto end; 29 | } 30 | if (handle->seek_pos >= handle->pts && handle->seek_pos <= handle->end_pts) { 31 | // 已经在缓冲区,直接从缓冲区移除不需要的数据 32 | int64_t samples = min(av_rescale_q_rnd(handle->seek_pos - handle->pts, AV_TIME_BASE_Q, base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX), av_audio_fifo_size(handle->buffer)); 33 | if ((re = av_audio_fifo_drain(handle->buffer, samples)) < 0) { 34 | av_log(NULL, AV_LOG_FATAL, "Failed to drain %" PRIi64 " samples in buffer: %s (%i)\n", samples, av_err2str(re), re); 35 | ReleaseMutex(handle->mutex); 36 | goto end; 37 | } 38 | // 增大当前时间 39 | handle->pts += av_rescale_q_rnd(samples, base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 40 | } else { 41 | // 不在缓冲区,调用av_seek_frame并清空缓冲区 42 | int flags = 0; 43 | // 修复flac文件解码完之后,继续调用解码器会导致报错的BUG 44 | // 同时解决decoder内多余buffer的问题 45 | av_log(NULL, AV_LOG_VERBOSE, "Try to reopen decoder \"%s\".\n", handle->codec->name ? handle->codec->name : "(null)"); 46 | if ((re = reopen_decoder(handle))) { 47 | ReleaseMutex(handle->mutex); 48 | goto end; 49 | } 50 | // 指的是定位到指定位置之前的关键帧,而不是从后往前定位 51 | flags |= AVSEEK_FLAG_BACKWARD; 52 | if ((re = av_seek_frame(handle->fmt, -1, handle->seek_pos + handle->first_pts, flags)) < 0) { 53 | av_log(NULL, AV_LOG_FATAL, "Failed to seek frame %" PRIi64 ": %s (%i)\n", handle->seek_pos + handle->first_pts, av_err2str(re), re); 54 | ReleaseMutex(handle->mutex); 55 | goto end; 56 | } 57 | re = 0; 58 | av_audio_fifo_reset(handle->buffer); 59 | handle->set_new_pts = 1; 60 | handle->is_eof = 0; 61 | } 62 | reset_filters_buffer(handle); 63 | ReleaseMutex(handle->mutex); 64 | end: 65 | handle->is_seek = 0; 66 | return re; 67 | } 68 | 69 | int reopen_file(MusicHandle* handle) { 70 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 71 | int re = FFMPEG_CORE_ERR_OK; 72 | if (handle->is_file) { 73 | int doing = 0; 74 | while (1) { 75 | doing = 0; 76 | if (handle->stoping) return FFMPEG_CORE_ERR_OK; 77 | if (basic_event_handle(handle)) { 78 | doing = 1; 79 | } 80 | if (is_file_exists(handle)) break; 81 | if (!doing) mssleep(10); 82 | } 83 | } else { 84 | int doing = 0; 85 | size_t st, now = 0; 86 | st = time_time_ns(); 87 | now = st; 88 | while ((now - st) < ((size_t)1000000000 * handle->s->url_retry_interval)) { 89 | doing = 0; 90 | if (handle->stoping) return FFMPEG_CORE_ERR_OK; 91 | if (basic_event_handle(handle)) { 92 | doing = 1; 93 | } 94 | if (!doing) mssleep(10); 95 | now = time_time_ns(); 96 | } 97 | } 98 | if (handle->fmt) avformat_close_input(&handle->fmt); 99 | if (handle->decoder) avcodec_free_context(&handle->decoder); 100 | if (handle->is_cda) { 101 | if ((re = open_cd_device(handle, handle->url))) { 102 | return re; 103 | } 104 | } else { 105 | if ((re = open_input(handle, handle->url))) { 106 | return re; 107 | } 108 | } 109 | if ((re = find_audio_stream(handle))) { 110 | return re; 111 | } 112 | if ((re = open_decoder(handle))) { 113 | return re; 114 | } 115 | av_log(NULL, AV_LOG_VERBOSE, "The target pts: %s\n", av_ts2timestr(handle->last_pkt_pts, &AV_TIME_BASE_Q)); 116 | if ((re = av_seek_frame(handle->fmt, -1, handle->last_pkt_pts, AVSEEK_FLAG_ANY)) < 0) { 117 | return re; 118 | } 119 | AVPacket pkt; 120 | while (1) { 121 | int64_t tmppts = 0; 122 | if ((re = av_read_frame(handle->fmt, &pkt)) < 0) { 123 | return re; 124 | } 125 | if (pkt.stream_index != handle->is->index) { 126 | av_packet_unref(&pkt); 127 | continue; 128 | } 129 | tmppts = av_rescale_q_rnd(pkt.pts, handle->is->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 130 | if (tmppts < handle->last_pkt_pts) { 131 | continue; 132 | } 133 | break; 134 | } 135 | av_log(NULL, AV_LOG_VERBOSE, "The packet pts after seek: %s\n", av_ts2timestr(pkt.pts, &handle->is->time_base)); 136 | av_packet_unref(&pkt); 137 | handle->is_reopen = 0; 138 | return FFMPEG_CORE_ERR_OK; 139 | } 140 | 141 | int basic_event_handle(MusicHandle* h) { 142 | if (!h) return 0; 143 | if (h->need_reinit_filters) { 144 | int re = reinit_filters(h); 145 | if (re) { 146 | h->have_err = 1; 147 | h->err = re; 148 | av_log(NULL, AV_LOG_WARNING, "%s %i: Error when calling reinit_filters: %s (%i).\n", __FILE__, __LINE__, av_err2str(re), re); 149 | } 150 | h->need_reinit_filters = 0; 151 | return 1; 152 | } 153 | return 0; 154 | } 155 | 156 | #if _WIN32 157 | DWORD WINAPI event_loop(LPVOID handle) { 158 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 159 | #else 160 | void* event_loop(void* handle) { 161 | if (!handle) pthread_exit(NULL); 162 | #endif 163 | MusicHandle* h = (MusicHandle*)handle; 164 | int samples = h->decoder->sample_rate * 15; 165 | /// 本次循环是否有做事 166 | char doing = 0; 167 | /// 是否往缓冲区加了数据 168 | char writed = 0; 169 | int buffered_size = h->sdl_spec.freq * h->s->cache_length; 170 | while (1) { 171 | doing = 0; 172 | if (h->stoping) break; 173 | #if HAVE_WASAPI 174 | if (!h->is_reopen && h->need_reinit_wasapi) { 175 | FILETIME now; 176 | GetSystemTimePreciseAsFileTime(&now); 177 | if (ft2ts(now) - ft2ts(h->wasapi_last_tried) > 10000000) { 178 | int re = reinit_wasapi_output(h); 179 | if (re) { 180 | av_log(NULL, AV_LOG_VERBOSE, "Reinitializ WASAPI failed: %i.\n", re); 181 | GetSystemTimePreciseAsFileTime(&h->wasapi_last_tried); 182 | } else { 183 | h->need_reinit_wasapi = 0; 184 | // 需要重新初始化filters以适应可能的输出格式变化 185 | h->need_reinit_filters = 1; 186 | if (h->is_playing) play_WASAPI_device(h, 1); 187 | } 188 | doing = 1; 189 | } 190 | goto end; 191 | } 192 | #endif 193 | if (basic_event_handle(h)) { 194 | doing = 1; 195 | goto end; 196 | } 197 | if (h->is_reopen) { 198 | /// 禁用了重试或重试次数达上限 199 | if (!h->s->max_retry_count || (h->s->max_retry_count > 0 && h->retry_count < h->s->max_retry_count)) { 200 | goto end; 201 | } 202 | h->retry_count += 1; 203 | av_log(NULL, AV_LOG_VERBOSE, "Try to reopen file \"%s\" %i times.\n", h->url, h->retry_count); 204 | int re = reopen_file(h); 205 | if (re) { 206 | av_log(NULL, AV_LOG_VERBOSE, "Reopen failed: %i.\n", re); 207 | h->err = re; 208 | } else { 209 | h->have_err = 0; 210 | h->err = 0; 211 | h->retry_count = 0; 212 | } 213 | doing = 1; 214 | goto end; 215 | } 216 | if (h->is_seek && h->first_pts != INT64_MIN) { 217 | int re = seek_to_pos(h); 218 | if (re) { 219 | h->have_err = 1; 220 | h->err = re; 221 | av_log(NULL, AV_LOG_WARNING, "%s %i: Error when calling seek_to_pos: %i.\n", __FILE__, __LINE__, re); 222 | } 223 | doing = 1; 224 | goto end; 225 | } 226 | if (!h->is_eof) { 227 | buffered_size = h->sdl_spec.freq * h->s->cache_length; 228 | if (av_audio_fifo_size(h->buffer) < buffered_size) { 229 | int re = decode_audio(handle, &writed); 230 | if (re) { 231 | av_log(NULL, AV_LOG_WARNING, "%s %i: Error when calling decode_audio: %s (%i).\n", __FILE__, __LINE__, av_err2str(re), re); 232 | h->have_err = 1; 233 | h->err = re; 234 | av_log(NULL, AV_LOG_VERBOSE, "Try to reopen file \"%s\".\n", h->url); 235 | h->is_reopen = 1; 236 | if (h->s->max_retry_count) { 237 | h->retry_count += 1; 238 | re = reopen_file(h); 239 | if (re) { 240 | av_log(NULL, AV_LOG_VERBOSE, "Reopen failed: %i.\n", re); 241 | h->err = re; 242 | } else { 243 | h->have_err = 0; 244 | h->err = 0; 245 | h->retry_count = 0; 246 | } 247 | } 248 | } 249 | doing = 1; 250 | } 251 | } else { 252 | // 播放完毕,自动停止播放 253 | if (av_audio_fifo_size(h->buffer) == 0) { 254 | #if HAVE_WASAPI 255 | if (h->is_use_wasapi) { 256 | play_WASAPI_device(h, 0); 257 | } else { 258 | SDL_PauseAudioDevice(h->device_id, 1); 259 | } 260 | #else 261 | SDL_PauseAudioDevice(h->device_id, 1); 262 | #endif 263 | h->is_playing = 0; 264 | } 265 | } 266 | end: 267 | if (!doing) { 268 | mssleep(10); 269 | } 270 | } 271 | #if _WIN32 272 | return FFMPEG_CORE_ERR_OK; 273 | #else 274 | pthread_exit(NULL); 275 | #endif 276 | } 277 | 278 | #if _WIN32 279 | DWORD WINAPI filter_loop(LPVOID handle) { 280 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 281 | #else 282 | void* filter_loop(void* handle) { 283 | if (!handle) pthread_exit(NULL); 284 | #endif 285 | MusicHandle* h = (MusicHandle*)handle; 286 | char doing = 0; 287 | while (1) { 288 | doing = 0; 289 | if (h->stoping) break; 290 | #if HAVE_WASAPI 291 | if (h->need_reinit_wasapi) { 292 | goto end; 293 | } 294 | #endif 295 | if (h->graph && !h->is_easy_filters) { 296 | int re = add_data_to_filters_buffer(h); 297 | if (re) { 298 | h->have_err = 1; 299 | h->err = re; 300 | av_log(NULL, AV_LOG_WARNING, "%s %i: Error when calling add_data_to_filters_buffer: %s (%i).\n", __FILE__, __LINE__, av_err2str(re), re); 301 | } 302 | doing = 1; 303 | } 304 | end: 305 | if (!doing) { 306 | mssleep(10); 307 | } 308 | } 309 | #if _WIN32 310 | return FFMPEG_CORE_ERR_OK; 311 | #else 312 | pthread_exit(NULL); 313 | #endif 314 | } 315 | -------------------------------------------------------------------------------- /src/loop.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_LOOP_H 2 | #define _MUSICPLAYER2_LOOP_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | int seek_to_pos(MusicHandle* handle); 8 | int reopen_file(MusicHandle* handle); 9 | /// 基础事件处理,如果处理过返回1反之0 10 | int basic_event_handle(MusicHandle* handle); 11 | #if _WIN32 12 | DWORD WINAPI event_loop(LPVOID handle); 13 | DWORD WINAPI filter_loop(LPVOID handle); 14 | #else 15 | void* event_loop(void* handle); 16 | void* filter_loop(void* handle); 17 | #endif 18 | #if __cplusplus 19 | } 20 | #endif 21 | #endif 22 | -------------------------------------------------------------------------------- /src/mixing.c: -------------------------------------------------------------------------------- 1 | #include "mixing.h" 2 | #include "ch_layout.h" 3 | 4 | void set_mixing_opts(MusicHandle* handle) { 5 | if (!handle->decoder || !handle->swrac) return; 6 | int nb_channels = GET_AV_CODEC_CHANNELS(handle->decoder); 7 | if (handle->s->enable_mixing && (!handle->s->do_not_mix_stereo || nb_channels != 2)) { 8 | av_opt_set_int(handle->swrac, "clip_protection", !handle->s->normalize_matrix && handle->s->clip_protection, 0); 9 | av_opt_set_int(handle->swrac, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); 10 | av_opt_set_double(handle->swrac, "center_mix_level", handle->s->center_mix_level, 0); 11 | av_opt_set_double(handle->swrac, "surround_mix_level", handle->s->surround_mix_level, 0); 12 | av_opt_set_double(handle->swrac, "lfe_mix_level", handle->s->lfe_mix_level, 0); 13 | av_opt_set_double(handle->swrac, "rematrix_maxval", handle->s->normalize_matrix ? 1.0 : 0.0, 0); 14 | av_opt_set_int(handle->swrac, "matrix_encoding", handle->s->matrix_encoding, 0); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/mixing.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_MIXING_H 2 | #define _MUSICPLAYER2_MIXING_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | 8 | void set_mixing_opts(MusicHandle* handle); 9 | 10 | #if __cplusplus 11 | } 12 | #endif 13 | #endif 14 | -------------------------------------------------------------------------------- /src/open.c: -------------------------------------------------------------------------------- 1 | #include "open.h" 2 | 3 | int open_input(MusicHandle* handle, const char* url) { 4 | if (!handle || !url) return FFMPEG_CORE_ERR_NULLPTR; 5 | int re = 0; 6 | if ((re = avformat_open_input(&handle->fmt, url, NULL, NULL)) < 0) { 7 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": %s (%i)\n", url, av_err2str(re), re); 8 | return re; 9 | } 10 | if ((re = avformat_find_stream_info(handle->fmt, NULL)) < 0) { 11 | av_log(NULL, AV_LOG_FATAL, "Failed to find streams in \"%s\": %s (%i)\n", url, av_err2str(re), re); 12 | return re; 13 | } 14 | // handle->fmt->flags |= AVFMT_FLAG_FAST_SEEK; // 允许快速定位 15 | return FFMPEG_CORE_ERR_OK; 16 | } 17 | 18 | int open_input2(MusicInfoHandle* handle, const char* url) { 19 | if (!handle || !url) return FFMPEG_CORE_ERR_NULLPTR; 20 | int re = 0; 21 | if ((re = avformat_open_input(&handle->fmt, url, NULL, NULL)) < 0) { 22 | av_log(NULL, AV_LOG_FATAL, "Failed to open \"%s\": %s (%i)\n", url, av_err2str(re), re); 23 | return re; 24 | } 25 | if ((re = avformat_find_stream_info(handle->fmt, NULL)) < 0) { 26 | av_log(NULL, AV_LOG_FATAL, "Failed to find streams in \"%s\": %s (%i)\n", url, av_err2str(re), re); 27 | return re; 28 | } 29 | return FFMPEG_CORE_ERR_OK; 30 | } 31 | 32 | int find_audio_stream(MusicHandle* handle) { 33 | if (!handle || !handle->fmt) return FFMPEG_CORE_ERR_NULLPTR; 34 | for (unsigned int i = 0; i < handle->fmt->nb_streams; i++) { 35 | AVStream* is = handle->fmt->streams[i]; 36 | if (is->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { 37 | // 确保有对应的解码器 38 | if (!avcodec_find_decoder(is->codecpar->codec_id)) { 39 | continue; 40 | } 41 | handle->is = is; 42 | return FFMPEG_CORE_ERR_OK; 43 | } 44 | } 45 | return FFMPEG_CORE_ERR_NO_AUDIO_OR_DECODER; 46 | } 47 | 48 | int find_audio_stream2(MusicInfoHandle* handle) { 49 | if (!handle || !handle->fmt) return FFMPEG_CORE_ERR_NULLPTR; 50 | for (unsigned int i = 0; i < handle->fmt->nb_streams; i++) { 51 | AVStream* is = handle->fmt->streams[i]; 52 | if (is->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { 53 | handle->is = is; 54 | return FFMPEG_CORE_ERR_OK; 55 | } 56 | } 57 | return FFMPEG_CORE_ERR_NO_AUDIO; 58 | } 59 | -------------------------------------------------------------------------------- /src/open.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_OPEN_H 2 | #define _MUSICPLAYER2_OPEN_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | /// 打开文件 8 | int open_input(MusicHandle* handle, const char* url); 9 | int find_audio_stream(MusicHandle* handle); 10 | int open_input2(MusicInfoHandle* handle, const char* url); 11 | int find_audio_stream2(MusicInfoHandle* handle); 12 | #if __cplusplus 13 | } 14 | #endif 15 | #endif 16 | -------------------------------------------------------------------------------- /src/output.c: -------------------------------------------------------------------------------- 1 | #include "output.h" 2 | 3 | #include "speed.h" 4 | #include "ch_layout.h" 5 | #if HAVE_WASAPI 6 | #include "position_data.h" 7 | #define ADD_POSITION_DATA(len, have_data) if (handle->is_use_wasapi && wasapi_get_object) add_data_to_position_data(handle, len, have_data); 8 | #else 9 | #define ADD_POSITION_DATA(len, have_data) 10 | #endif 11 | #include "mixing.h" 12 | 13 | #include "time_util.h" 14 | 15 | int init_output(MusicHandle* handle, const char* device) { 16 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 17 | if (!handle->sdl_initialized) { 18 | if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { 19 | return FFMPEG_CORE_ERR_SDL; 20 | } 21 | handle->sdl_initialized = 1; 22 | } 23 | SDL_AudioSpec sdl_spec; 24 | sdl_spec.freq = handle->decoder->sample_rate; 25 | sdl_spec.format = convert_to_sdl_format(handle->decoder->sample_fmt); 26 | if (!sdl_spec.format) { 27 | const char* tmp = av_get_sample_fmt_name(handle->decoder->sample_fmt); 28 | av_log(NULL, AV_LOG_FATAL, "Unknown sample format: %s (%i)\n", tmp ? tmp : "", handle->decoder->sample_fmt); 29 | return FFMPEG_CORE_ERR_UNKNOWN_SAMPLE_FMT; 30 | } 31 | sdl_spec.channels = GET_AV_CODEC_CHANNELS(handle->decoder); 32 | sdl_spec.samples = handle->decoder->sample_rate / 100; 33 | sdl_spec.callback = SDL_callback; 34 | sdl_spec.userdata = handle; 35 | memcpy(&handle->sdl_spec, &sdl_spec, sizeof(SDL_AudioSpec)); 36 | handle->device_id = SDL_OpenAudioDevice(device, 0, &sdl_spec, &handle->sdl_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); 37 | if (!handle->device_id) { 38 | av_log(NULL, AV_LOG_FATAL, "Failed to open audio device \"%s\": %s\n", "default", SDL_GetError()); 39 | return FFMPEG_CORE_ERR_SDL; 40 | } 41 | enum AVSampleFormat target_format = convert_to_sdl_supported_format(handle->decoder->sample_fmt); 42 | int re = 0; 43 | #if NEW_CHANNEL_LAYOUT 44 | if (re = get_sdl_channel_layout(handle->decoder->ch_layout.nb_channels, &handle->output_channel_layout)) { 45 | return re; 46 | } 47 | if (re = swr_alloc_set_opts2(&handle->swrac, &handle->output_channel_layout, target_format, handle->sdl_spec.freq, &handle->decoder->ch_layout, handle->decoder->sample_fmt, handle->decoder->sample_rate, 0, NULL)) { 48 | return re; 49 | } 50 | #else 51 | handle->output_channel_layout = get_sdl_channel_layout(handle->decoder->channels); 52 | handle->swrac = swr_alloc_set_opts(NULL, handle->output_channel_layout, target_format, handle->sdl_spec.freq, handle->decoder->channel_layout, handle->decoder->sample_fmt, handle->decoder->sample_rate, 0, NULL); 53 | #endif 54 | if (!handle->swrac) { 55 | av_log(NULL, AV_LOG_FATAL, "Failed to allocate resample context.\n"); 56 | return FFMPEG_CORE_ERR_OOM; 57 | } 58 | set_mixing_opts(handle); 59 | if ((re = swr_init(handle->swrac)) < 0) { 60 | return re; 61 | } 62 | if (!(handle->buffer = av_audio_fifo_alloc(target_format, GET_AV_CODEC_CHANNELS(handle->decoder), 1))) { 63 | av_log(NULL, AV_LOG_FATAL, "Failed to allocate buffer.\n"); 64 | return FFMPEG_CORE_ERR_OOM; 65 | } 66 | handle->target_format = target_format; 67 | handle->target_format_pbytes = av_get_bytes_per_sample(target_format); 68 | return FFMPEG_CORE_ERR_OK; 69 | } 70 | 71 | enum AVSampleFormat convert_to_sdl_supported_format(enum AVSampleFormat fmt) { 72 | switch (fmt) { 73 | case AV_SAMPLE_FMT_DBL: 74 | case AV_SAMPLE_FMT_FLTP: 75 | case AV_SAMPLE_FMT_DBLP: 76 | return AV_SAMPLE_FMT_FLT; 77 | case AV_SAMPLE_FMT_U8P: 78 | return AV_SAMPLE_FMT_U8; 79 | case AV_SAMPLE_FMT_S16P: 80 | return AV_SAMPLE_FMT_S16; 81 | case AV_SAMPLE_FMT_S32P: 82 | case AV_SAMPLE_FMT_S64: 83 | case AV_SAMPLE_FMT_S64P: 84 | return AV_SAMPLE_FMT_S32; 85 | default: 86 | return fmt; 87 | } 88 | } 89 | 90 | SDL_AudioFormat convert_to_sdl_format(enum AVSampleFormat fmt) { 91 | fmt = convert_to_sdl_supported_format(fmt); 92 | switch (fmt) { 93 | case AV_SAMPLE_FMT_U8: 94 | return AUDIO_U8; 95 | case AV_SAMPLE_FMT_S16: 96 | return AUDIO_S16SYS; 97 | case AV_SAMPLE_FMT_S32: 98 | return AUDIO_S32SYS; 99 | case AV_SAMPLE_FMT_FLT: 100 | return AUDIO_F32SYS; 101 | default: 102 | return 0; 103 | } 104 | } 105 | 106 | void SDL_callback(void* userdata, uint8_t* stream, int len) { 107 | MusicHandle* handle = (MusicHandle*)userdata; 108 | #if HAVE_WASAPI 109 | /// 是否获取到了Mutex3所有权 110 | char wasapi_get_object = 0; 111 | if (handle->is_use_wasapi) { 112 | DWORD tmp = WaitForSingleObject(handle->mutex3, 1); 113 | if (tmp == WAIT_OBJECT_0) { 114 | wasapi_get_object = 1; 115 | } 116 | } 117 | #endif 118 | #if _WIN32 119 | DWORD re = WaitForSingleObject(handle->mutex, handle->s->max_wait_buffer_time); 120 | #else 121 | struct timespec ts; 122 | size_t now = time_time_ns(); 123 | now += handle->s->max_wait_buffer_time * 1000000; 124 | ts.tv_sec = now / 1000000000; 125 | ts.tv_nsec = now % 1000000000; 126 | int re = pthread_mutex_timedlock(&handle->mutex, &ts); 127 | #endif 128 | if (re != WAIT_OBJECT_0) { 129 | // 无法获取Mutex所有权,填充空白数据 130 | memset(stream, 0, len); 131 | ADD_POSITION_DATA(len, 0) 132 | #if HAVE_WASAPI 133 | if (wasapi_get_object) ReleaseMutex(handle->mutex3); 134 | #endif 135 | return; 136 | } 137 | int samples_need = len / handle->target_format_pbytes / handle->sdl_spec.channels; 138 | int buffer_size = handle->sdl_spec.freq / 5; 139 | if (av_audio_fifo_size(handle->buffer) == 0) { 140 | // 缓冲区没有数据,填充空白数据 141 | memset(stream, 0, len); 142 | ADD_POSITION_DATA(len, 0) 143 | } else if (!handle->graph) { 144 | int writed = av_audio_fifo_read(handle->buffer, (void**)&stream, samples_need); 145 | if (writed > 0) { 146 | // 增大缓冲区开始时间 147 | AVRational base = { 1, handle->sdl_spec.freq }; 148 | handle->pts += av_rescale_q_rnd(writed, base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 149 | } 150 | if (writed < 0) { 151 | // 读取发生错误,填充空白数据 152 | memset(stream, 0, len); 153 | ADD_POSITION_DATA(len, 0) 154 | } else if (writed < samples_need) { 155 | size_t len = ((size_t)samples_need - writed) * handle->target_format_pbytes, alen = (size_t)writed * handle->target_format_pbytes; 156 | // 不足的区域用空白数据填充 157 | memset(stream + alen, 0, len); 158 | ADD_POSITION_DATA(alen, 1) 159 | ADD_POSITION_DATA(len, 0) 160 | } else { 161 | ADD_POSITION_DATA(len, 1) 162 | } 163 | } else if (handle->is_easy_filters) { 164 | AVFrame* in = av_frame_alloc(), * out = av_frame_alloc(); 165 | int writed = 0; 166 | int samples_need_in = 0; 167 | if (!in || !out) { 168 | memset(stream, 0, len); 169 | ADD_POSITION_DATA(len, 0) 170 | goto end; 171 | } 172 | samples_need_in = samples_need * get_speed(handle->s->speed) / 1000; 173 | #if NEW_CHANNEL_LAYOUT 174 | if (av_channel_layout_copy(&in->ch_layout, &handle->output_channel_layout) < 0) { 175 | memset(stream, 0, len); 176 | ADD_POSITION_DATA(len, 0) 177 | goto end; 178 | } 179 | #else 180 | in->channels = handle->sdl_spec.channels; 181 | in->channel_layout = handle->output_channel_layout; 182 | #endif 183 | in->format = handle->target_format; 184 | in->sample_rate = handle->sdl_spec.freq; 185 | in->nb_samples = samples_need_in; 186 | if (av_frame_get_buffer(in, 0) < 0) { 187 | memset(stream, 0, len); 188 | ADD_POSITION_DATA(len, 0) 189 | goto end; 190 | } 191 | // 从缓冲区读取数据 192 | writed = av_audio_fifo_read(handle->buffer, (void**)in->data, samples_need_in); 193 | if (writed > 0) { 194 | // 增大缓冲区开始时间 195 | AVRational base = { 1, handle->sdl_spec.freq }; 196 | handle->pts += av_rescale_q_rnd(writed, base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 197 | } 198 | if (writed < 0) { 199 | memset(stream, 0, len); 200 | ADD_POSITION_DATA(len, 0) 201 | goto end; 202 | } 203 | in->nb_samples = writed; 204 | // 喂给 filters 数据 205 | if (av_buffersrc_add_frame(handle->filter_inp, in) < 0) { 206 | memset(stream, 0, len); 207 | ADD_POSITION_DATA(len, 0) 208 | goto end; 209 | } 210 | // 从 filters 拿回数据 211 | if (av_buffersink_get_frame(handle->filter_out, out) < 0) { 212 | memset(stream, 0, len); 213 | ADD_POSITION_DATA(len, 0) 214 | goto end; 215 | } 216 | if (out->nb_samples >= samples_need) { 217 | memcpy(stream, out->data[0], len); 218 | ADD_POSITION_DATA(len, 1) 219 | } else { 220 | size_t le = (size_t)out->nb_samples * handle->target_format_pbytes * handle->sdl_spec.channels; 221 | memcpy(stream, out->data[0], le); 222 | ADD_POSITION_DATA(le, 1) 223 | memset(stream, 0, len - le); 224 | ADD_POSITION_DATA(len - le, 0) 225 | } 226 | end: 227 | if (in) av_frame_free(&in); 228 | if (out) av_frame_free(&out); 229 | } else if (!handle->is_wait_filters || av_audio_fifo_size(handle->filters_buffer) > buffer_size) { 230 | handle->is_wait_filters = 0; 231 | int writed = av_audio_fifo_read(handle->filters_buffer, (void**)&stream, samples_need); 232 | if (writed > 0) { 233 | // 增大缓冲区开始时间 234 | AVRational base = { 1, handle->sdl_spec.freq }, base2 = { get_speed(handle->s->speed), 1000 }, tar = { 1, 1 }; 235 | int samples_in = av_rescale_q_rnd(writed, base2, tar, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 236 | handle->filters_buffer_offset -= samples_in; 237 | handle->pts += av_rescale_q_rnd(samples_in, base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 238 | av_audio_fifo_drain(handle->buffer, samples_in); 239 | } 240 | if (writed < 0) { 241 | // 读取发生错误,填充空白数据 242 | memset(stream, 0, len); 243 | ADD_POSITION_DATA(len, 0) 244 | } else if (writed < samples_need) { 245 | size_t alen = (size_t)writed * handle->target_format_pbytes, len = ((size_t)samples_need - writed) * handle->target_format_pbytes; 246 | // 不足的区域用空白数据填充 247 | memset(stream + alen, 0, len); 248 | ADD_POSITION_DATA(alen, 1) 249 | ADD_POSITION_DATA(len, 0) 250 | } else { 251 | ADD_POSITION_DATA(len, 1) 252 | } 253 | } else { 254 | memset(stream, 0, len); 255 | ADD_POSITION_DATA(len, 0) 256 | } 257 | ReleaseMutex(handle->mutex); 258 | #if HAVE_WASAPI 259 | if (wasapi_get_object) { 260 | ReleaseMutex(handle->mutex3); 261 | } 262 | #endif 263 | } 264 | 265 | #if NEW_CHANNEL_LAYOUT 266 | int get_sdl_channel_layout(int channels, AVChannelLayout* channel_layout) { 267 | if (!channel_layout) return FFMPEG_CORE_ERR_NULLPTR; 268 | switch (channels) { 269 | case 2: 270 | return av_channel_layout_from_string(channel_layout, "FL+FR"); 271 | case 3: 272 | return av_channel_layout_from_string(channel_layout, "FL+FR+LFE"); 273 | case 4: 274 | return av_channel_layout_from_string(channel_layout, "FL+FR+BL+BR"); 275 | case 5: 276 | return av_channel_layout_from_string(channel_layout, "FL+FR+FC+BL+BR"); 277 | case 6: 278 | return av_channel_layout_from_string(channel_layout, "FL+FR+FC+LFE+SL+SR"); 279 | case 7: 280 | return av_channel_layout_from_string(channel_layout, "FL+FR+FC+LFE+BC+SL+SR"); 281 | case 8: 282 | return av_channel_layout_from_string(channel_layout, "FL+FR+FC+LFE+BL+BR+SL+SR"); 283 | default: 284 | av_channel_layout_default(channel_layout, channels); 285 | return FFMPEG_CORE_ERR_OK; 286 | } 287 | } 288 | #endif 289 | 290 | #if OLD_CHANNEL_LAYOUT 291 | uint64_t get_sdl_channel_layout(int channels) { 292 | switch (channels) { 293 | case 2: 294 | return av_get_channel_layout("FL+FR"); 295 | case 3: 296 | return av_get_channel_layout("FL+FR+LFE"); 297 | case 4: 298 | return av_get_channel_layout("FL+FR+BL+BR"); 299 | case 5: 300 | return av_get_channel_layout("FL+FR+FC+BL+BR"); 301 | case 6: 302 | return av_get_channel_layout("FL+FR+FC+LFE+SL+SR"); 303 | case 7: 304 | return av_get_channel_layout("FL+FR+FC+LFE+BC+SL+SR"); 305 | case 8: 306 | return av_get_channel_layout("FL+FR+FC+LFE+BL+BR+SL+SR"); 307 | default: 308 | return av_get_default_channel_layout(channels); 309 | } 310 | } 311 | #endif 312 | -------------------------------------------------------------------------------- /src/output.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_OUTPUT_H 2 | #define _MUSICPLAYER2_OUTPUT_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | int init_output(MusicHandle* handle, const char* device); 8 | enum AVSampleFormat convert_to_sdl_supported_format(enum AVSampleFormat fmt); 9 | void SDL_callback(void* userdata, uint8_t* stream, int len); 10 | SDL_AudioFormat convert_to_sdl_format(enum AVSampleFormat fmt); 11 | #if NEW_CHANNEL_LAYOUT 12 | /** 13 | * @brief 获取与SDL输出匹配的输出布局 14 | * @param channels 声道数 15 | * @param channel_layout 布局 16 | * @return 错误代码 17 | */ 18 | int get_sdl_channel_layout(int channels, AVChannelLayout* channel_layout); 19 | #else 20 | /** 21 | * @brief 获取与SDL输出匹配的输出布局 22 | * @param channels 声道数 23 | * @return 布局 24 | */ 25 | uint64_t get_sdl_channel_layout(int channels); 26 | #endif 27 | #if __cplusplus 28 | } 29 | #endif 30 | #endif 31 | -------------------------------------------------------------------------------- /src/position_data.c: -------------------------------------------------------------------------------- 1 | #include "position_data.h" 2 | 3 | #include "wasapi.h" 4 | 5 | int64_t cal_true_buffer_time(PositionDataList* list, int64_t buffered) { 6 | if (!list) return 0; 7 | PositionDataList* t = position_data_list_tail(list); 8 | if (!t) return 0; 9 | int64_t result = 0; 10 | if (t && t->have_data) { 11 | result += min(buffered, t->length); 12 | } 13 | buffered -= t->length; 14 | if (buffered <= 0) return result; 15 | while (t->prev) { 16 | t = t->prev; 17 | if (t->have_data) { 18 | result += min(buffered, t->length); 19 | } 20 | buffered -= t->length; 21 | if (buffered <= 0) return result; 22 | } 23 | return result; 24 | } 25 | 26 | int add_data_to_position_data(MusicHandle* handle, int size, char have_data) { 27 | if (!handle) return FFMPEG_CORE_ERR_NULLPTR; 28 | if (!handle->is_use_wasapi || size == 0) return FFMPEG_CORE_ERR_OK; 29 | AVRational base = { 1, handle->sdl_spec.freq }; 30 | int64_t len = av_rescale_q_rnd(size / handle->target_format_pbytes / handle->sdl_spec.channels, base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 31 | if (!position_data_list_append(&handle->position_data, len, have_data)) { 32 | return FFMPEG_CORE_ERR_OOM; 33 | } 34 | int64_t clen = 0; 35 | PositionDataList* cur = position_data_list_tail(handle->position_data); 36 | if (cur) { 37 | clen += cur->length; 38 | } 39 | if (clen > handle->wasapi->frame_pts) { 40 | position_data_remove_before(cur); 41 | handle->position_data = cur; 42 | return FFMPEG_CORE_ERR_OK; 43 | } 44 | while (cur && cur->prev) { 45 | cur = cur->prev; 46 | clen += cur->length; 47 | if (clen > handle->wasapi->frame_pts) { 48 | position_data_remove_before(cur); 49 | handle->position_data = cur; 50 | return FFMPEG_CORE_ERR_OK; 51 | } 52 | } 53 | return FFMPEG_CORE_ERR_OK; 54 | } 55 | 56 | int64_t cal_true_pts(MusicHandle* handle) { 57 | if (!handle) return -1; 58 | if (!handle->is_use_wasapi) return handle->pts; 59 | DWORD re = WaitForSingleObject(handle->mutex3, 10); 60 | if (re == WAIT_OBJECT_0) { 61 | uint32_t frame_count; 62 | int64_t re = 0; 63 | AVRational base = { 1, handle->sdl_spec.freq }; 64 | // 独占模式读取padding可能会导致GetBuffer抛出致命性错误 65 | // 采用处理线程缓存的padding来估算 66 | if (handle->is_exclusive && handle->wasapi) { 67 | re = cal_true_buffer_time(handle->position_data, av_rescale_q_rnd(handle->wasapi->last_padding, base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); 68 | } else if (!handle->is_exclusive && handle->wasapi && handle->wasapi->client && SUCCEEDED(handle->wasapi->client->lpVtbl->GetCurrentPadding(handle->wasapi->client, &frame_count))) { 69 | re = cal_true_buffer_time(handle->position_data, av_rescale_q_rnd(frame_count, base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); 70 | } 71 | ReleaseMutex(handle->mutex3); 72 | return handle->pts - re; 73 | } 74 | return handle->pts; 75 | } 76 | -------------------------------------------------------------------------------- /src/position_data.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPALYER2_POSITION_DATA_H 2 | #define _MUSICPALYER2_POSITION_DATA_H 3 | #if __cplusplus 4 | #include "linked_list/position_data_linked_list.h" 5 | extern "C" { 6 | #endif 7 | #if !__cplusplus 8 | #include "linked_list/position_data_linked_list.h" 9 | #endif 10 | /** 11 | * @brief 计算真实的WASAPI缓冲区内的缓冲时间 12 | * @param list 数据来源 13 | * @param buffered 缓冲时间 14 | * @return 真实缓冲时间 15 | */ 16 | int64_t cal_true_buffer_time(PositionDataList* list, int64_t buffered); 17 | /** 18 | * @brief 将新数据加到列表 19 | * @param handle Handle 20 | * @param size 新数据大小 21 | * @param have_data 是否包含真实数据 22 | * @return 错误代码 23 | */ 24 | int add_data_to_position_data(MusicHandle* handle, int size, char have_data); 25 | /** 26 | * @brief 计算准确的当前播放时间 27 | * @param handle Handle 28 | * @return 当前播放时间(未使用WASAPI时不进行校准) 29 | */ 30 | int64_t cal_true_pts(MusicHandle* handle); 31 | #if __cplusplus 32 | } 33 | #endif 34 | #endif 35 | -------------------------------------------------------------------------------- /src/reverb.c: -------------------------------------------------------------------------------- 1 | #include "reverb.h" 2 | 3 | #include 4 | #include "linked_list/float_linked_list.h" 5 | 6 | #define APPEND_DATA(list, data) if (!float_linked_list_append(&list, data)) { \ 7 | re = FFMPEG_CORE_ERR_OOM; \ 8 | goto end; \ 9 | } 10 | 11 | #define SET_FLOAT_OPT(key, data) snprintf(buf, sizeof(buf), "%f", data); \ 12 | if ((re = av_dict_set(&opts, key, buf, 0)) < 0) { \ 13 | goto end; \ 14 | } \ 15 | re = 0; 16 | 17 | #define SET_FLOAT_LIST(key, list) tmp = float_linked_list_join(list, '|'); \ 18 | if (tmp) { \ 19 | if ((re = av_dict_set(&opts, key, tmp, 0)) < 0) { \ 20 | free(tmp); \ 21 | goto end; \ 22 | } \ 23 | free(tmp); \ 24 | re = 0; \ 25 | } else { \ 26 | re = FFMPEG_CORE_ERR_OOM; \ 27 | goto end; \ 28 | } 29 | 30 | int create_reverb_filter(AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, float delay, float mix, int type) { 31 | if (!graph || !src || !list) return FFMPEG_CORE_ERR_NULLPTR; 32 | const AVFilter* aecho = avfilter_get_by_name("aecho"); 33 | float in_gain = 1.0f; 34 | float out_gain = 1.0f; 35 | float_linked_list* delays = NULL, * decays = NULL; 36 | AVFilterContext* context = NULL; 37 | AVDictionary* opts = NULL; 38 | char buf[32]; 39 | char* tmp = NULL; 40 | int re = FFMPEG_CORE_ERR_OK; 41 | switch (type) { 42 | case REVERB_TYPE_ROOM: 43 | { 44 | float d = sqrt(2) / 2; 45 | APPEND_DATA(delays, delay * d) 46 | APPEND_DATA(delays, d) 47 | APPEND_DATA(decays, mix) 48 | APPEND_DATA(decays, mix * d) 49 | out_gain = 0.99f / (1.0f + mix + mix * d); 50 | } 51 | break; 52 | default: 53 | av_log(NULL, AV_LOG_FATAL, "Unknown reverb type: %i.\n", type); 54 | re = AVERROR(EINVAL); 55 | goto end; 56 | } 57 | if (!delays || !decays) { 58 | re = AVERROR(EINVAL); 59 | goto end; 60 | } 61 | if (!(context = avfilter_graph_alloc_filter(graph, aecho, "aecho"))) { 62 | av_log(NULL, AV_LOG_ERROR, "Failed to alloc new filter aecho.\n"); 63 | re = FFMPEG_CORE_ERR_OOM; 64 | goto end; 65 | } 66 | SET_FLOAT_OPT("in_gain", in_gain) 67 | SET_FLOAT_OPT("out_gain", out_gain) 68 | SET_FLOAT_LIST("delays", delays) 69 | SET_FLOAT_LIST("decays", decays) 70 | if ((re = avfilter_init_dict(context, &opts)) < 0) { 71 | av_log(NULL, AV_LOG_FATAL, "Failed to create aecho filter: %s (%i)\n", av_err2str(re), re); 72 | goto end; 73 | } 74 | if (!c_linked_list_append(list, (void*)context)) { 75 | av_log(NULL, AV_LOG_FATAL, "Failed to append filter \"aecho\" to list.\n"); 76 | re = FFMPEG_CORE_ERR_OOM; 77 | goto end; 78 | } 79 | if (c_linked_list_count(*list) > 1) { 80 | AVFilterContext* last = c_linked_list_tail(*list)->prev->d; 81 | if ((re = avfilter_link(last, 0, context, 0)) < 0) { 82 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", last->name, 0, context->name, 0, av_err2str(re), re); 83 | goto end; 84 | } 85 | } else { 86 | if ((re = avfilter_link(src, 0, context, 0)) < 0) { 87 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", src->name, 0, context->name, 0, av_err2str(re), re); 88 | goto end; 89 | } 90 | } 91 | end: 92 | if (opts) av_dict_free(&opts); 93 | float_linked_list_clear(&delays); 94 | float_linked_list_clear(&decays); 95 | return re; 96 | } 97 | -------------------------------------------------------------------------------- /src/reverb.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_REVERB_H 2 | #define _MUSICPLAYER2_REVERB_H 3 | #include "core.h" 4 | #if __cplusplus 5 | extern "C" { 6 | #endif 7 | /** 8 | * @brief 设置混响 9 | * @param graph Graph 10 | * @param src 输入 11 | * @param list Filters列表 12 | * @param delay 延迟时间 13 | * @param mix 混响强度 14 | * @param type 混响类型 15 | * @return 16 | */ 17 | int create_reverb_filter(AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, float delay, float mix, int type); 18 | #if __cplusplus 19 | } 20 | #endif 21 | #endif 22 | -------------------------------------------------------------------------------- /src/speed.c: -------------------------------------------------------------------------------- 1 | #include "speed.h" 2 | 3 | #include 4 | 5 | int get_speed(float speed) { 6 | return roundf(speed * 1000); 7 | } 8 | 9 | int create_speed_filter(int index, AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, int* speed) { 10 | if (!graph || !speed || !src || !list) return FFMPEG_CORE_ERR_NULLPTR; 11 | char args[64]; 12 | char name[32]; 13 | const AVFilter* atempo = avfilter_get_by_name("atempo"); 14 | int speed_now = min(max(*speed, 500), 2000); 15 | *speed = 1000 * (*speed) / speed_now; 16 | snprintf(args, sizeof(args), "tempo=%.3f", speed_now / 1000.0); 17 | snprintf(name, sizeof(name), "atempo%d", index); 18 | int re = 0; 19 | AVFilterContext* context = NULL; 20 | if ((re = avfilter_graph_create_filter(&context, atempo, name, args, NULL, graph)) < 0) { 21 | av_log(NULL, AV_LOG_FATAL, "Failed to create speed filter \"%s\": %s (%i)\n", name, av_err2str(re), re); 22 | return re; 23 | } 24 | if (!c_linked_list_append(list, (void*)context)) { 25 | av_log(NULL, AV_LOG_FATAL, "Failed to append filter \"%s\" to list.\n", context->name); 26 | return FFMPEG_CORE_ERR_OOM; 27 | } 28 | if (c_linked_list_count(*list) > 1) { 29 | AVFilterContext* last = c_linked_list_tail(*list)->prev->d; 30 | if ((re = avfilter_link(last, 0, context, 0)) < 0) { 31 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", last->name, 0, context->name, 0, av_err2str(re), re); 32 | return re; 33 | } 34 | } else { 35 | if ((re = avfilter_link(src, 0, context, 0)) < 0) { 36 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", src->name, 0, context->name, 0, av_err2str(re), re); 37 | return re; 38 | } 39 | } 40 | return FFMPEG_CORE_ERR_OK; 41 | } 42 | -------------------------------------------------------------------------------- /src/speed.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_SPEED_H 2 | #define _MUSICPLAYER2_SPEED_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | /// 将speed转为1000为1.0倍的数字,例如1.5倍转为1500 8 | int get_speed(float speed); 9 | /** 10 | * @brief 创建 atempo Filter 11 | * @param index Filter 序号 12 | * @param graph Graph 13 | * @param src 输入 14 | * @param list Filters列表 15 | * @param speed 指向目标速度的指针,会返回还需要设置的速度 16 | * @return 17 | */ 18 | int create_speed_filter(int index, AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, int* speed); 19 | #if __cplusplus 20 | } 21 | #endif 22 | #endif 23 | -------------------------------------------------------------------------------- /src/volume.c: -------------------------------------------------------------------------------- 1 | #include "volume.h" 2 | 3 | int get_volume_precision(enum AVSampleFormat f) { 4 | switch (f) { 5 | case AV_SAMPLE_FMT_U8: 6 | case AV_SAMPLE_FMT_S16: 7 | case AV_SAMPLE_FMT_S32: 8 | case AV_SAMPLE_FMT_U8P: 9 | case AV_SAMPLE_FMT_S16P: 10 | case AV_SAMPLE_FMT_S32P: 11 | return 0; 12 | case AV_SAMPLE_FMT_DBL: 13 | case AV_SAMPLE_FMT_DBLP: 14 | return 2; 15 | case AV_SAMPLE_FMT_FLT: 16 | case AV_SAMPLE_FMT_FLTP: 17 | default: 18 | return 1; 19 | } 20 | } 21 | 22 | int create_volume_filter(int index, AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, int volume, enum AVSampleFormat f) { 23 | if (!graph || !src || !list) return FFMPEG_CORE_ERR_NULLPTR; 24 | char args[128]; 25 | char name[32]; 26 | const AVFilter* vol = avfilter_get_by_name("volume"); 27 | snprintf(args, sizeof(args), "volume=%.2f:precision=%d", volume / 100.0, get_volume_precision(f)); 28 | snprintf(name, sizeof(name), "volume%d", index); 29 | int re = 0; 30 | AVFilterContext* context = NULL; 31 | if ((re = avfilter_graph_create_filter(&context, vol, name, args, NULL, graph)) < 0) { 32 | av_log(NULL, AV_LOG_FATAL, "Failed to create volume filter \"%s\": %s (%i)\n", name, av_err2str(re), re); 33 | return re; 34 | } 35 | if (!c_linked_list_append(list, (void*)context)) { 36 | av_log(NULL, AV_LOG_FATAL, "Failed to append filter \"%s\" to list.\n", context->name); 37 | return FFMPEG_CORE_ERR_OOM; 38 | } 39 | if (c_linked_list_count(*list) > 1) { 40 | AVFilterContext* last = c_linked_list_tail(*list)->prev->d; 41 | if ((re = avfilter_link(last, 0, context, 0)) < 0) { 42 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", last->name, 0, context->name, 0, av_err2str(re), re); 43 | return re; 44 | } 45 | } else { 46 | if ((re = avfilter_link(src, 0, context, 0)) < 0) { 47 | av_log(NULL, AV_LOG_FATAL, "Failed to link %s:%i -> %s:%i: %s (%i)\n", src->name, 0, context->name, 0, av_err2str(re), re); 48 | return re; 49 | } 50 | } 51 | return FFMPEG_CORE_ERR_OK; 52 | } 53 | -------------------------------------------------------------------------------- /src/volume.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_VOLUME_H 2 | #define _MUSICPLAYER2_VOLUME_H 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | #include "core.h" 7 | int get_volume_precision(enum AVSampleFormat f); 8 | /** 9 | * @brief 创建 volume Filter 10 | * @param index Filter 序号 11 | * @param graph Graph 12 | * @param src 输入 13 | * @param list Filters列表 14 | * @param volume 声音大小百分比 15 | * @param f 输入的格式 16 | * @return 17 | */ 18 | int create_volume_filter(int index, AVFilterGraph* graph, AVFilterContext* src, c_linked_list** list, int volume, enum AVSampleFormat f); 19 | #if __cplusplus 20 | } 21 | #endif 22 | #endif 23 | -------------------------------------------------------------------------------- /src/wasapi.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUSICPLAYER2_WASAPI_H 2 | #define _MUSICPLAYER2_WASAPI_H 3 | #include "core.h" 4 | #if __cplusplus 5 | extern "C" { 6 | #endif 7 | #include 8 | #include 9 | typedef struct WASAPIDevice { 10 | IMMDevice* device; 11 | wchar_t* name; 12 | } WASAPIDevice; 13 | typedef struct WASAPIHandle { 14 | IAudioClient* client; 15 | IAudioRenderClient* render; 16 | uint32_t frame_count; 17 | HANDLE thread; 18 | HRESULT err; 19 | HANDLE eve; 20 | /// 缓冲区长度(时间) 21 | int64_t frame_pts; 22 | /// 上一次padding(用来大致计算) 23 | uint32_t last_padding; 24 | /// 目标设备的名字 25 | wchar_t* device_name; 26 | unsigned char is_playing : 1; 27 | unsigned char have_err : 1; 28 | unsigned char stoping : 1; 29 | } WASAPIHandle; 30 | void free_WASAPIHandle(WASAPIHandle* handle); 31 | int init_WASAPI(); 32 | void uninit_WASAPI(); 33 | int create_WASAPIDevice(IMMDevice* device, WASAPIDevice** output); 34 | void free_WASAPIDevice(WASAPIDevice* device); 35 | /** 36 | * @brief 创建一个新的IMMDevice实例 37 | * @param src 源实例 38 | * @param out 新实例 39 | * @return 40 | */ 41 | int copy_IMMDevice(IMMDevice* src, IMMDevice** out); 42 | /** 43 | * @brief 获取设备 44 | * @param name 设备名称,如果为NULL返回默认设备 45 | * @param result 返回结果 46 | * @return 47 | */ 48 | int get_Device(const wchar_t* name, IMMDevice** result); 49 | /** 50 | * @brief 打开WASAPI设备 51 | * @param handle MusicHandle 52 | * @param name 设备名称 53 | * @return 54 | */ 55 | int open_WASAPI_device(MusicHandle* handle, const wchar_t* name); 56 | void close_WASAPI_device(MusicHandle* handle); 57 | #if NEW_CHANNEL_LAYOUT 58 | int32_t get_WASAPI_channel_mask(AVChannelLayout* channel_layout, int channels); 59 | #else 60 | int32_t get_WASAPI_channel_mask(int64_t channel_layout, int channels); 61 | #endif 62 | int format_is_pcm(enum AVSampleFormat fmt); 63 | int get_format_info(AVCodecContext* context, WAVEFORMATEXTENSIBLE* format); 64 | #define WASAPI_FORMAT_CHANGE_BITS 1 65 | #define WASAPI_FORMAT_CHANGE_CHANNELS 2 66 | #define WASAPI_FORMAT_CHANGE_SAMPLE_RATES 4 67 | #define WASAPI_FORMAT_CHANGE_FORMAT 8 68 | /** 69 | * @brief 检查client是否支持格式 70 | * @param client Client 71 | * @param exclusive 是否为独占模式 72 | * @param base 基础格式 73 | * @param result 支持的格式 74 | * @param change 哪些参数将被手动修改至默认值 75 | * @return 76 | */ 77 | int check_format_supported(IAudioClient* client, int exclusive, WAVEFORMATEXTENSIBLE* base, WAVEFORMATEX** result, int change); 78 | int init_wasapi_output(MusicHandle* handle, const char* device); 79 | DWORD WINAPI wasapi_loop2(LPVOID handle); 80 | DWORD WINAPI wasapi_loop(LPVOID handle); 81 | void play_WASAPI_device(MusicHandle* handle, int play); 82 | /** 83 | * @brief 获取独占模式缓冲区大小 84 | * @param min_device_preiord 最小设备时间(单位:100ns) 85 | * @param min_buffer_time 最小缓冲区时间(单位:ms) 86 | * @return 缓冲区长度(单位:100ns) 87 | */ 88 | REFERENCE_TIME get_WASAPI_buffer_time(REFERENCE_TIME min_device_preiord, int min_buffer_time); 89 | int reinit_wasapi_output(MusicHandle* handle); 90 | #if __cplusplus 91 | } 92 | #endif 93 | #endif 94 | --------------------------------------------------------------------------------