├── .github └── workflows │ ├── build.yaml │ └── publish.yaml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildscripts ├── .gitignore ├── README.md ├── build.sh ├── download.sh ├── include │ ├── depinfo.sh │ ├── download-deps.sh │ ├── download-sdk.sh │ └── path.sh ├── patch.sh ├── patches │ └── mpv │ │ ├── ao-aaudio.patch │ │ └── ffmpeg-8.patch └── scripts │ ├── dav1d.sh │ ├── ffmpeg.sh │ ├── freetype.sh │ ├── fribidi.sh │ ├── harfbuzz.sh │ ├── libass.sh │ ├── libplacebo.sh │ ├── libunibreak.sh │ ├── lua.sh │ ├── mbedtls.sh │ ├── mpv-android.sh │ ├── mpv.sh │ └── shaderc.sh ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── libmpv ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── cpp │ ├── CMakeLists.txt │ ├── event.cpp │ ├── event.h │ ├── globals.h │ ├── jni_utils.cpp │ ├── jni_utils.h │ ├── log.cpp │ ├── log.h │ ├── main.cpp │ ├── property.cpp │ └── render.cpp │ └── java │ └── dev │ └── jdtech │ └── mpv │ └── MPVLib.kt └── settings.gradle.kts /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build libmpv-android 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v5 16 | - name: Setup Java JDK 17 | uses: actions/setup-java@v5 18 | with: 19 | distribution: temurin 20 | java-version: 17 21 | - name: Setup Gradle 22 | uses: gradle/actions/setup-gradle@v5 23 | - name: Symlink SDK 24 | working-directory: ./buildscripts 25 | run: | 26 | mkdir sdk 27 | ln -s ${ANDROID_HOME} ./sdk/android-sdk-linux 28 | - name: Download dependencies 29 | working-directory: ./buildscripts 30 | run: ./download.sh 31 | - name: Apply patches 32 | working-directory: ./buildscripts 33 | run: ./patch.sh 34 | - name: Build 35 | working-directory: ./buildscripts 36 | run: ./build.sh 37 | - uses: actions/upload-artifact@v4 38 | with: 39 | name: libmpv-release.aar 40 | path: ./libmpv/build/outputs/aar/libmpv-release.aar 41 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: ubuntu-latest 12 | environment: release 13 | if: ${{ contains(github.repository_owner, 'jarnedemeulemeester') || startsWith(github.ref, 'refs/tags/v') }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v5 17 | - name: Setup Java JDK 18 | uses: actions/setup-java@v5 19 | with: 20 | distribution: temurin 21 | java-version: 17 22 | - name: Setup Gradle 23 | uses: gradle/actions/setup-gradle@v5 24 | - name: Symlink SDK 25 | working-directory: ./buildscripts 26 | run: | 27 | mkdir sdk 28 | ln -s ${ANDROID_HOME} ./sdk/android-sdk-linux 29 | - name: Download dependencies 30 | working-directory: ./buildscripts 31 | run: ./download.sh 32 | - name: Apply patches 33 | working-directory: ./buildscripts 34 | run: ./patch.sh 35 | - name: Build 36 | working-directory: ./buildscripts 37 | run: ./build.sh 38 | - name: Publish 39 | run: ./gradlew publishToMavenCentral --no-configuration-cache 40 | env: 41 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }} 42 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }} 43 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} 44 | ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }} 45 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} 46 | - name: Create release 47 | uses: softprops/action-gh-release@v2 48 | with: 49 | draft: true 50 | files: | 51 | ./libmpv/build/outputs/aar/libmpv-release.aar 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gdb_history 2 | bin 3 | gen 4 | jniLibs 5 | obj 6 | .gradle 7 | .idea 8 | .cxx 9 | **/*.iml 10 | build 11 | local.properties 12 | *~ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ilya Zhuravlev 2 | Copyright (c) 2016 sfan5 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libmpv for Android 2 | 3 | Based on [mpv-android](https://github.com/mpv-android/mpv-android) 4 | 5 | ## Building from source 6 | 7 | Take a look at [README.md](buildscripts/README.md) inside the `buildscripts` directory. 8 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) apply false 3 | alias(libs.plugins.kotlin.android) apply false 4 | alias(libs.plugins.maven.publish) apply false 5 | } 6 | 7 | allprojects { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /buildscripts/.gitignore: -------------------------------------------------------------------------------- 1 | deps 2 | sdk 3 | prefix 4 | prefix64 5 | *.tgz 6 | -------------------------------------------------------------------------------- /buildscripts/README.md: -------------------------------------------------------------------------------- 1 | # Building 2 | 3 | ## Download dependencies 4 | 5 | `download.sh` will take care of installing the Android SDK, NDK and downloading the sources. 6 | 7 | If you're running on Debian/Ubuntu or RHEL/Fedora it will also install the necessary dependencies for you. 8 | 9 | ```shell 10 | ./download.sh 11 | ``` 12 | 13 | If you already have the Android SDK installed you can symlink `android-sdk-linux` to your SDK root 14 | before running the script, it will still install the necessary SDK packages. 15 | 16 | A matching NDK version inside the SDK will be picked up automatically or downloaded/installed otherwise. 17 | 18 | ## Patching 19 | 20 | ```shell 21 | ./patch.sh 22 | ``` 23 | 24 | Run `patch.sh` to apply the custom patches to the dependencies. These patches are located in the `patches` folder. 25 | 26 | **Note** running `patch.sh` resets the dependencies to a clean state. 27 | 28 | ## Build 29 | 30 | ```shell 31 | ./build.sh 32 | ``` 33 | 34 | Run `build.sh` with `--clean` to clean the build directories before building. 35 | 36 | This builds for all architectures (`armeabi-v7a` `arm64-v8a` `x86` `x86_64`) by default. 37 | 38 | If you want to build only for a specific arch, build the native part like this: 39 | ```shell 40 | ./build.sh --arch arm64 mpv 41 | ./build.sh --arch x86_64 mpv 42 | ``` 43 | 44 | You also need to tell gradle to only build said architectures. See [Specify ABIs](https://developer.android.com/studio/projects/gradle-external-native-builds#specify-abi) for more information 45 | ```kotlin 46 | android { 47 | ... 48 | defaultConfig { 49 | ... 50 | ndk { 51 | abiFilters += listOf("x86_64", "arm64-v8a") 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | Finally you can build the AAR: 58 | ```shell 59 | ./build.sh -n 60 | ``` 61 | 62 | # Developing 63 | 64 | ## Getting logs 65 | 66 | ```shell 67 | adb logcat # get all logs, useful when drivers/vendor libs output to logcat 68 | adb logcat -s "mpv" # get only mpv logs 69 | ``` 70 | 71 | ## Rebuilding a single component 72 | 73 | If you've made changes to a single component (e.g. ffmpeg or mpv) and want a new build you can of course just run ./build.sh but it's also possible to just build a single component like this: 74 | 75 | ```shell 76 | ./build.sh -n ffmpeg 77 | # optional: add --clean to build from a clean state 78 | ``` 79 | 80 | Note this will build the component for all architectures, specify `--arch` to build for a single arch. 81 | 82 | Afterwards, build mpv-android: 83 | 84 | ```shell 85 | ./build.sh -n 86 | ``` 87 | 88 | ## Using Android Studio 89 | 90 | You can use Android Studio to develop the Java part of the codebase. Before using it, make sure to build the project at least once by following the steps in the **Build** section. 91 | 92 | You should point Android Studio to existing SDK installation at `mpv-android/buildscripts/sdk/android-sdk-linux`. Then click "Open an existing Android Studio project" and select `mpv-android`. 93 | 94 | If Android Studio complains about project sync failing (`Error:Exception thrown while executing model rule: NdkComponentModelPlugin.Rules#createNativeBuildModel`), go to "File -> Project Structure -> SDK Location" and set "Android NDK Location" to `mpv-android/buildscripts/sdk/android-ndk-rVERSION`. 95 | 96 | Note that if you build from Android Studio only the Java part will be built. If you make any changes to libraries (ffmpeg, mpv, ...) or mpv-android native code (`app/src/main/jni/*`), first rebuild native code with: 97 | 98 | ```shell 99 | ./build.sh -n 100 | ``` 101 | 102 | then build the project from Android Studio. 103 | 104 | Also, debugging native code does not work from within the studio at the moment, you will have to use gdb for that. 105 | 106 | ## Debugging native code with gdb 107 | 108 | You first need to rebuild mpv-android with gdbserver support: 109 | 110 | ```shell 111 | NDK_DEBUG=1 ./build.sh -n 112 | adb install -r ../app/build/outputs/apk/debug/app-debug.apk 113 | ``` 114 | 115 | After that, ndk-gdb can be used to debug the app: 116 | 117 | ```shell 118 | cd mpv-android/app/src/main/ 119 | ../../../buildscripts/sdk/android-ndk-r*/ndk-gdb --launch 120 | ``` 121 | 122 | # Credits, notes, etc 123 | 124 | These build scripts were created by @sfan5 and modified by @jarnedemeulemeester. 125 | 126 | -------------------------------------------------------------------------------- /buildscripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | cd "$( dirname "${BASH_SOURCE[0]}" )" 4 | . ./include/depinfo.sh 5 | 6 | cleanbuild=0 7 | nodeps=0 8 | target=mpv-android 9 | archs=(armv7l arm64 x86 x86_64) 10 | 11 | getdeps () { 12 | varname="dep_${1//-/_}[*]" 13 | echo ${!varname} 14 | } 15 | 16 | loadarch () { 17 | unset CC CXX CPATH LIBRARY_PATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH 18 | unset CFLAGS CXXFLAGS CPPFLAGS LDFLAGS 19 | 20 | local apilvl=26 21 | # ndk_triple: what the toolchain actually is 22 | # cc_triple: what Google pretends the toolchain is 23 | if [ "$1" == "armv7l" ]; then 24 | export ndk_suffix= 25 | export ndk_triple=arm-linux-androideabi 26 | cc_triple=armv7a-linux-androideabi$apilvl 27 | prefix_name=armeabi-v7a 28 | elif [ "$1" == "arm64" ]; then 29 | export ndk_suffix=-arm64 30 | export ndk_triple=aarch64-linux-android 31 | cc_triple=$ndk_triple$apilvl 32 | prefix_name=arm64-v8a 33 | elif [ "$1" == "x86" ]; then 34 | export ndk_suffix=-x86 35 | export ndk_triple=i686-linux-android 36 | cc_triple=$ndk_triple$apilvl 37 | prefix_name=x86 38 | elif [ "$1" == "x86_64" ]; then 39 | export ndk_suffix=-x64 40 | export ndk_triple=x86_64-linux-android 41 | cc_triple=$ndk_triple$apilvl 42 | prefix_name=x86_64 43 | else 44 | echo "Invalid architecture" 45 | exit 1 46 | fi 47 | export prefix_dir="$PWD/prefix/$prefix_name" 48 | export native_dir="$PWD/../libmpv/src/main/jniLibs/$prefix_name" 49 | export CC=$cc_triple-clang 50 | export CXX=$cc_triple-clang++ 51 | export LDFLAGS="-Wl,-O1,--icf=safe -Wl,-z,max-page-size=16384" 52 | export AR=llvm-ar 53 | export RANLIB=llvm-ranlib 54 | } 55 | 56 | setup_prefix () { 57 | if [ ! -d "$prefix_dir" ]; then 58 | mkdir -p "$prefix_dir" 59 | # enforce flat structure (/usr/local -> /) 60 | ln -s . "$prefix_dir/usr" 61 | ln -s . "$prefix_dir/local" 62 | fi 63 | 64 | if [ ! -d "$native_dir" ]; then 65 | mkdir -p "$native_dir" 66 | fi 67 | 68 | local cpu_family=${ndk_triple%%-*} 69 | [ "$cpu_family" == "i686" ] && cpu_family=x86 70 | 71 | # meson wants to be spoonfed this file, so create it ahead of time 72 | # also define: release build, static libs and no source downloads at runtime(!!!) 73 | cat >"$prefix_dir/crossfile.txt" <&2 '\e[1;31m%s\e[m\n' "Target $1 not found" 96 | return 1 97 | fi 98 | if [ $nodeps -eq 0 ]; then 99 | printf >&2 '\e[1;34m%s\e[m\n' "Preparing $1..." 100 | local deps=$(getdeps $1) 101 | echo >&2 "Dependencies: $deps" 102 | for dep in $deps; do 103 | build $dep 104 | done 105 | fi 106 | if [ "$1" != "mpv-android" ]; then 107 | printf >&2 '\e[1;34m%s\e[m\n' "Building $1..." 108 | pushd deps/$1 109 | BUILDSCRIPT=../../scripts/$1.sh 110 | [ $cleanbuild -eq 1 ] && $BUILDSCRIPT clean 111 | $BUILDSCRIPT build 112 | popd 113 | fi 114 | } 115 | 116 | assemble () { 117 | printf >&2 '\e[1;34m%s\e[m\n' "Assembling $1..." 118 | pushd .. 119 | BUILDSCRIPT=buildscripts/scripts/mpv-android.sh 120 | [ $cleanbuild -eq 1 ] && $BUILDSCRIPT clean 121 | $BUILDSCRIPT build 122 | popd 123 | } 124 | 125 | usage () { 126 | printf '%s\n' \ 127 | "Usage: build.sh [options] [target]" \ 128 | "Builds the specified target (default: $target)" \ 129 | "-n Do not build dependencies" \ 130 | "--clean Clean build dirs before compiling" \ 131 | "--arch Build for specified architecture (supported: armv7l, arm64, x86, x86_64)" 132 | exit 0 133 | } 134 | 135 | while [ $# -gt 0 ]; do 136 | case "$1" in 137 | --clean) 138 | cleanbuild=1 139 | ;; 140 | -n|--no-deps) 141 | nodeps=1 142 | ;; 143 | --arch) 144 | shift 145 | arch=$1 146 | ;; 147 | -h|--help) 148 | usage 149 | ;; 150 | *) 151 | target=$1 152 | ;; 153 | esac 154 | shift 155 | done 156 | 157 | if [ -z $arch ]; then 158 | for arch in ${archs[@]}; do 159 | loadarch $arch 160 | setup_prefix 161 | build $target 162 | done 163 | else 164 | loadarch $arch 165 | setup_prefix 166 | build $target 167 | fi 168 | 169 | if [ "$target" == "mpv-android" ]; then 170 | assemble 171 | [ -d ../libmpv/build/outputs/aar ] && ls -lh ../libmpv/build/outputs/aar/*.aar 172 | [ -d ../libmpv/build/libs ] && ls -lh ../libmpv/build/libs/*.jar 173 | fi 174 | 175 | exit 0 176 | -------------------------------------------------------------------------------- /buildscripts/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | ./include/download-sdk.sh 4 | ./include/download-deps.sh 5 | -------------------------------------------------------------------------------- /buildscripts/include/depinfo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | ## Dependency versions 4 | 5 | v_platform=android-36 6 | v_sdk=13114758_latest 7 | v_ndk=29.0.14206865 8 | v_sdk_build_tools=36.1.0 9 | v_cmake=4.1.2 10 | 11 | v_lua=5.2.4 12 | v_libunibreak=6.1 13 | v_libass=0.17.4 14 | v_harfbuzz=12.1.0 15 | v_fribidi=1.0.16 16 | v_freetype=2.14.1 17 | v_mbedtls=3.6.4 18 | v_libplacebo=7.351.0 19 | v_dav1d=1.5.1 20 | v_ffmpeg=8.0 21 | v_mpv=0.40.0 22 | 23 | 24 | ## Dependency tree 25 | # I would've used a dict but putting arrays in a dict is not a thing 26 | 27 | dep_mbedtls=() 28 | dep_dav1d=() 29 | dep_ffmpeg=(mbedtls dav1d) 30 | dep_freetype2=() 31 | dep_fribidi=() 32 | dep_harfbuzz=() 33 | dep_libunibreak=() 34 | dep_libass=(freetype fribidi harfbuzz libunibreak) 35 | dep_lua=() 36 | dep_libplacebo=() 37 | dep_mpv=(ffmpeg libass lua libplacebo) 38 | dep_mpv_android=(mpv) 39 | 40 | -------------------------------------------------------------------------------- /buildscripts/include/download-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ./include/depinfo.sh 4 | 5 | [ -z "$WGET" ] && WGET=wget 6 | 7 | mkdir -p deps && cd deps 8 | 9 | # mbedtls 10 | [ ! -d mbedtls ] && git clone --depth 1 --branch v$v_mbedtls --recurse-submodules https://github.com/Mbed-TLS/mbedtls.git mbedtls 11 | 12 | # dav1d 13 | [ ! -d dav1d ] && git clone --depth 1 --branch $v_dav1d https://code.videolan.org/videolan/dav1d.git dav1d 14 | 15 | # ffmpeg 16 | [ ! -d ffmpeg ] && git clone --depth 1 --branch n$v_ffmpeg https://github.com/FFmpeg/FFmpeg.git ffmpeg 17 | 18 | # freetype2 19 | [ ! -d freetype ] && git clone --depth 1 --branch VER-${v_freetype//./-} https://gitlab.freedesktop.org/freetype/freetype.git freetype 20 | 21 | # fribidi 22 | [ ! -d fribidi ] && git clone --depth 1 --branch v$v_fribidi https://github.com/fribidi/fribidi.git fribidi 23 | 24 | # harfbuzz 25 | [ ! -d harfbuzz ] && git clone --depth 1 --branch $v_harfbuzz https://github.com/harfbuzz/harfbuzz.git harfbuzz 26 | 27 | # libunibreak 28 | if [ ! -d libunibreak ]; then 29 | mkdir libunibreak 30 | $WGET https://github.com/adah1972/libunibreak/releases/download/libunibreak_${v_libunibreak//./_}/libunibreak-${v_libunibreak}.tar.gz -O - | \ 31 | tar -xz -C libunibreak --strip-components=1 32 | fi 33 | 34 | # libass 35 | [ ! -d libass ] && git clone --depth 1 --branch $v_libass https://github.com/libass/libass.git libass 36 | 37 | # lua 38 | if [ ! -d lua ]; then 39 | mkdir lua 40 | $WGET http://www.lua.org/ftp/lua-$v_lua.tar.gz -O - | \ 41 | tar -xz -C lua --strip-components=1 42 | fi 43 | 44 | [ ! -d libplacebo ] && git clone --depth 1 --branch v$v_libplacebo --recurse-submodules https://code.videolan.org/videolan/libplacebo.git libplacebo 45 | 46 | # mpv 47 | [ ! -d mpv ] && git clone --depth 1 --branch v$v_mpv https://github.com/mpv-player/mpv.git mpv 48 | 49 | cd .. 50 | -------------------------------------------------------------------------------- /buildscripts/include/download-sdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ./include/depinfo.sh 4 | 5 | . ./include/path.sh # load $os var 6 | 7 | [ -z "$TRAVIS" ] && TRAVIS=0 # skip steps not required for CI? 8 | [ -z "$WGET" ] && WGET=wget # possibility of calling wget differently 9 | 10 | if [ "$os" == "linux" ]; then 11 | if [ $TRAVIS -eq 0 ]; then 12 | hash yum &>/dev/null && { 13 | sudo yum install autoconf pkgconfig libtool ninja-build \ 14 | python3-pip python3-setuptools unzip wget; 15 | python3 -m pip install meson jsonschema jinja2; } 16 | apt-get -v &>/dev/null && { 17 | sudo apt-get update; 18 | sudo apt-get install -y autoconf pkg-config libtool ninja-build nasm \ 19 | python3-pip python3-setuptools unzip; 20 | python3 -m pip install meson jsonschema jinja2; } 21 | fi 22 | 23 | if ! javac -version &>/dev/null; then 24 | echo "Error: missing Java Development Kit." 25 | hash yum &>/dev/null && \ 26 | echo "Install it using e.g. sudo yum install java-latest-openjdk-devel" 27 | apt-get -v &>/dev/null && \ 28 | echo "Install it using e.g. sudo apt-get install default-jre-headless" 29 | exit 255 30 | fi 31 | 32 | os_ndk="linux" 33 | elif [ "$os" == "mac" ]; then 34 | if [ $TRAVIS -eq 0 ]; then 35 | if ! hash brew 2>/dev/null; then 36 | echo "Error: brew not found. You need to install Homebrew: https://brew.sh/" 37 | exit 255 38 | fi 39 | brew install \ 40 | automake autoconf libtool pkg-config \ 41 | coreutils gnu-sed wget meson ninja 42 | fi 43 | if ! javac -version &>/dev/null; then 44 | echo "Error: missing Java Development Kit. Install it manually." 45 | exit 255 46 | fi 47 | fi 48 | 49 | mkdir -p sdk && cd sdk 50 | 51 | # Android SDK 52 | if [ ! -d "android-sdk-${os}" ]; then 53 | $WGET "https://dl.google.com/android/repository/commandlinetools-${os}-${v_sdk}.zip" 54 | mkdir "android-sdk-${os}" 55 | unzip -q -d "android-sdk-${os}" "commandlinetools-${os}-${v_sdk}.zip" 56 | rm "commandlinetools-${os}-${v_sdk}.zip" 57 | fi 58 | sdkmanager () { 59 | local exe="./android-sdk-$os/cmdline-tools/latest/bin/sdkmanager" 60 | [ -x "$exe" ] || exe="./android-sdk-$os/cmdline-tools/bin/sdkmanager" 61 | "$exe" --sdk_root="${ANDROID_HOME}" "$@" 62 | } 63 | echo y | sdkmanager \ 64 | "platforms;${v_platform}" \ 65 | "build-tools;${v_sdk_build_tools}" \ 66 | "ndk;${v_ndk}" \ 67 | "cmake;${v_cmake}" 68 | 69 | # gas-preprocessor 70 | mkdir -p bin 71 | $WGET "https://github.com/FFmpeg/gas-preprocessor/raw/master/gas-preprocessor.pl" \ 72 | -O bin/gas-preprocessor.pl 73 | chmod +x bin/gas-preprocessor.pl 74 | 75 | cd .. 76 | -------------------------------------------------------------------------------- /buildscripts/include/path.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 4 | 5 | os=linux 6 | [[ "$OSTYPE" == "darwin"* ]] && os=mac 7 | export os 8 | 9 | if [ "$os" == "mac" ]; then 10 | [ -z "$cores" ] && cores=$(sysctl -n hw.ncpu) 11 | # various things rely on GNU behaviour 12 | export INSTALL=`which ginstall` 13 | export SED=gsed 14 | else 15 | [ -z "$cores" ] && cores=$(grep -c ^processor /proc/cpuinfo) 16 | fi 17 | cores=${cores:-4} 18 | 19 | # configure pkg-config paths if inside buildscripts 20 | if [ -n "$ndk_triple" ]; then 21 | export PKG_CONFIG_SYSROOT_DIR="$prefix_dir" 22 | export PKG_CONFIG_LIBDIR="$PKG_CONFIG_SYSROOT_DIR/lib/pkgconfig" 23 | unset PKG_CONFIG_PATH 24 | fi 25 | 26 | toolchain=$(echo "$DIR/sdk/android-sdk-linux/ndk/$v_ndk/toolchains/llvm/prebuilt/"*) 27 | export PATH="$toolchain/bin:$DIR/sdk/android-sdk-linux/ndk/$v_ndk:$DIR/sdk/bin:$PATH" 28 | export ANDROID_HOME="$DIR/sdk/android-sdk-$os" 29 | unset ANDROID_SDK_ROOT ANDROID_NDK_ROOT 30 | -------------------------------------------------------------------------------- /buildscripts/patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | PATCHES=(patches/*) 4 | ROOT=$(pwd) 5 | 6 | for dep_path in "${PATCHES[@]}"; do 7 | if [ -d "$dep_path" ]; then 8 | patches=($dep_path/*) 9 | dep=$(echo $dep_path |cut -d/ -f 2) 10 | cd deps/$dep 11 | echo Patching $dep 12 | git reset --hard 13 | for patch in "${patches[@]}"; do 14 | echo Applying $patch 15 | git apply "$ROOT/$patch" 16 | done 17 | cd $ROOT 18 | fi 19 | done 20 | 21 | exit 0 22 | -------------------------------------------------------------------------------- /buildscripts/patches/mpv/ao-aaudio.patch: -------------------------------------------------------------------------------- 1 | From 49f3fdcdc2dbab8c9c8c28424b6a7b88ba88f0d7 Mon Sep 17 00:00:00 2001 2 | From: Jun Bo Bi 3 | Date: Thu, 12 Dec 2024 09:33:17 -0500 4 | Subject: [PATCH 1/2] ao/audiotrack: added option to disable audiotrack 5 | 6 | --- 7 | audio/out/ao.c | 2 +- 8 | meson.build | 9 +++++++-- 9 | meson.options | 1 + 10 | 3 files changed, 9 insertions(+), 3 deletions(-) 11 | 12 | diff --git a/audio/out/ao.c b/audio/out/ao.c 13 | index ee76b702ea580..97fd9e7461938 100644 14 | --- a/audio/out/ao.c 15 | +++ b/audio/out/ao.c 16 | @@ -57,7 +57,7 @@ extern const struct ao_driver audio_out_sdl; 17 | 18 | static const struct ao_driver * const audio_out_drivers[] = { 19 | // native: 20 | -#if HAVE_ANDROID 21 | +#if HAVE_AUDIOTRACK 22 | &audio_out_audiotrack, 23 | #endif 24 | #if HAVE_AUDIOUNIT 25 | diff --git a/meson.build b/meson.build 26 | index cbf703cae556d..09d80483c4ab2 100644 27 | --- a/meson.build 28 | +++ b/meson.build 29 | @@ -467,8 +467,7 @@ endif 30 | features += {'android': host_machine.system() == 'android'} 31 | if features['android'] 32 | dependencies += cc.find_library('android') 33 | - sources += files('audio/out/ao_audiotrack.c', 34 | - 'misc/jni.c', 35 | + sources += files('misc/jni.c', 36 | 'osdep/android/strnlen.c', 37 | 'video/out/android_common.c', 38 | 'video/out/vo_mediacodec_embed.c') 39 | @@ -850,6 +849,12 @@ if features['openal'] 40 | sources += files('audio/out/ao_openal.c') 41 | endif 42 | 43 | +features += {'audiotrack': 44 | + get_option('audiotrack').require(features['android']).allowed()} 45 | +if features['audiotrack'] 46 | + sources += files('audio/out/ao_audiotrack.c') 47 | +endif 48 | + 49 | opensles = cc.find_library('OpenSLES', required: get_option('opensles')) 50 | features += {'opensles': opensles.found()} 51 | if features['opensles'] 52 | diff --git a/meson.options b/meson.options 53 | index dae0a333ef71b..d7b71efa149b4 100644 54 | --- a/meson.options 55 | +++ b/meson.options 56 | @@ -45,6 +45,7 @@ option('coreaudio', type: 'feature', value: 'auto', description: 'CoreAudio audi 57 | option('avfoundation', type: 'feature', value: 'auto', description: 'AVFoundation audio output') 58 | option('jack', type: 'feature', value: 'auto', description: 'JACK audio output') 59 | option('openal', type: 'feature', value: 'disabled', description: 'OpenAL audio output') 60 | +option('audiotrack', type: 'feature', value: 'auto', description: 'Android AudioTrack audio output') 61 | option('opensles', type: 'feature', value: 'auto', description: 'OpenSL ES audio output') 62 | option('oss-audio', type: 'feature', value: 'auto', description: 'OSSv4 audio output') 63 | option('pipewire', type: 'feature', value: 'auto', description: 'PipeWire audio output') 64 | 65 | From f90bd218b264acf7891055ba66321e5013f3c895 Mon Sep 17 00:00:00 2001 66 | From: Jun Bo Bi 67 | Date: Sun, 27 Aug 2023 04:46:35 -0400 68 | Subject: [PATCH 2/2] ao/aaudio: implement aaudio backend for android 69 | 70 | --- 71 | audio/chmap.h | 16 + 72 | audio/out/aaudio_functions26.inc | 50 +++ 73 | audio/out/aaudio_functions28.inc | 4 + 74 | audio/out/aaudio_functions32.inc | 1 + 75 | audio/out/ao.c | 4 + 76 | audio/out/ao_aaudio.c | 503 +++++++++++++++++++++++++++++++ 77 | meson.build | 7 + 78 | meson.options | 1 + 79 | 8 files changed, 586 insertions(+) 80 | create mode 100644 audio/out/aaudio_functions26.inc 81 | create mode 100644 audio/out/aaudio_functions28.inc 82 | create mode 100644 audio/out/aaudio_functions32.inc 83 | create mode 100644 audio/out/ao_aaudio.c 84 | 85 | diff --git a/audio/chmap.h b/audio/chmap.h 86 | index 58a3f71907715..6f1f538da4b5a 100644 87 | --- a/audio/chmap.h 88 | +++ b/audio/chmap.h 89 | @@ -98,6 +98,22 @@ typedef const char * const (mp_ch_layout_tuple)[2]; 90 | {7, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g)}} 91 | #define MP_CHMAP8(a, b, c, d, e, f, g, h) \ 92 | {8, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h)}} 93 | +#define MP_CHMAP9(a, b, c, d, e, f, g, h, i) \ 94 | + {9, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i)}} 95 | +#define MP_CHMAP10(a, b, c, d, e, f, g, h, i, j) \ 96 | + {10, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j)}} 97 | +#define MP_CHMAP11(a, b, c, d, e, f, g, h, i, j, k) \ 98 | + {11, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k)}}, 99 | +#define MP_CHMAP12(a, b, c, d, e, f, g, h, i, j, k, l) \ 100 | + {12, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l)}} 101 | +#define MP_CHMAP13(a, b, c, d, e, f, g, h, i, j, k, l, m) \ 102 | + {13, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m)}} 103 | +#define MP_CHMAP14(a, b, c, d, e, f, g, h, i, j, k, l, m, n) \ 104 | + {14, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m), MP_SP(n)}} 105 | +#define MP_CHMAP15(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) \ 106 | + {15, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m), MP_SP(n), MP_SP(o)}} 107 | +#define MP_CHMAP16(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ 108 | + {16, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h), MP_SP(i), MP_SP(j), MP_SP(k), MP_SP(l), MP_SP(m), MP_SP(n), MP_SP(o), MP_SP(p)}} 109 | 110 | #define MP_CHMAP_INIT_MONO {1, {MP_SPEAKER_ID_FC}} 111 | #define MP_CHMAP_INIT_STEREO MP_CHMAP2(FL, FR) 112 | diff --git a/audio/out/aaudio_functions26.inc b/audio/out/aaudio_functions26.inc 113 | new file mode 100644 114 | index 0000000000000..0452baf5f624c 115 | --- /dev/null 116 | +++ b/audio/out/aaudio_functions26.inc 117 | @@ -0,0 +1,50 @@ 118 | +/* Stream builder functions */ 119 | +AAUDIO_FUNCTION(AAudio_createStreamBuilder, aaudio_result_t, AAudioStreamBuilder **builder) 120 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setDeviceId, void, AAudioStreamBuilder *builder, int32_t deviceId) 121 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setSampleRate, void, AAudioStreamBuilder *builder, int32_t sampleRate) 122 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setChannelCount, void, AAudioStreamBuilder *builder, int32_t channelCount) 123 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setSamplesPerFrame, void, AAudioStreamBuilder *builder, int32_t samplesPerFrame) 124 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setFormat, void, AAudioStreamBuilder *builder, aaudio_format_t format) 125 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setSharingMode, void, AAudioStreamBuilder *builder, aaudio_sharing_mode_t sharingMode) 126 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setDirection, void, AAudioStreamBuilder *builder, aaudio_direction_t direction) 127 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setBufferCapacityInFrames, void, AAudioStreamBuilder *builder, int32_t numFrames) 128 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setPerformanceMode, void, AAudioStreamBuilder *builder, aaudio_performance_mode_t mode) 129 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setDataCallback, void, AAudioStreamBuilder *builder, AAudioStream_dataCallback callback, void *userData) 130 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setFramesPerDataCallback, void, AAudioStreamBuilder *builder, int32_t numFrames) 131 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setErrorCallback, void, AAudioStreamBuilder *builder, AAudioStream_errorCallback callback, void *userData) 132 | +AAUDIO_FUNCTION(AAudioStreamBuilder_openStream, aaudio_result_t, AAudioStreamBuilder *builder, AAudioStream **stream) 133 | +AAUDIO_FUNCTION(AAudioStreamBuilder_delete, aaudio_result_t, AAudioStreamBuilder *builder) 134 | + 135 | +/* Stream control functions */ 136 | +AAUDIO_FUNCTION(AAudioStream_close, aaudio_result_t, AAudioStream *stream) 137 | +AAUDIO_FUNCTION(AAudioStream_requestStart, aaudio_result_t, AAudioStream *stream) 138 | +AAUDIO_FUNCTION(AAudioStream_requestPause, aaudio_result_t, AAudioStream *stream) 139 | +AAUDIO_FUNCTION(AAudioStream_requestFlush, aaudio_result_t, AAudioStream *stream) 140 | +AAUDIO_FUNCTION(AAudioStream_requestStop, aaudio_result_t, AAudioStream *stream) 141 | + 142 | +/* Stream query functions */ 143 | +AAUDIO_FUNCTION(AAudioStream_getState, aaudio_stream_state_t, AAudioStream *stream) 144 | +AAUDIO_FUNCTION(AAudioStream_waitForStateChange, aaudio_result_t, AAudioStream *stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds) 145 | +AAUDIO_FUNCTION(AAudioStream_read, aaudio_result_t, AAudioStream *stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) 146 | +AAUDIO_FUNCTION(AAudioStream_write, aaudio_result_t, AAudioStream *stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) 147 | +AAUDIO_FUNCTION(AAudioStream_setBufferSizeInFrames, aaudio_result_t, AAudioStream *stream, int32_t numFrames) 148 | +AAUDIO_FUNCTION(AAudioStream_getBufferSizeInFrames, int32_t, AAudioStream *stream) 149 | +AAUDIO_FUNCTION(AAudioStream_getFramesPerBurst, int32_t, AAudioStream *stream) 150 | +AAUDIO_FUNCTION(AAudioStream_getBufferCapacityInFrames, int32_t, AAudioStream *stream) 151 | +AAUDIO_FUNCTION(AAudioStream_getFramesPerDataCallback, int32_t, AAudioStream *stream) 152 | +AAUDIO_FUNCTION(AAudioStream_getXRunCount, int32_t, AAudioStream *stream) 153 | +AAUDIO_FUNCTION(AAudioStream_getSampleRate, int32_t, AAudioStream *stream) 154 | +AAUDIO_FUNCTION(AAudioStream_getChannelCount, int32_t, AAudioStream *stream) 155 | +AAUDIO_FUNCTION(AAudioStream_getSamplesPerFrame, int32_t, AAudioStream *stream) 156 | +AAUDIO_FUNCTION(AAudioStream_getDeviceId, int32_t, AAudioStream *stream) 157 | +AAUDIO_FUNCTION(AAudioStream_getFormat, aaudio_format_t, AAudioStream *stream) 158 | +AAUDIO_FUNCTION(AAudioStream_getSharingMode, aaudio_sharing_mode_t, AAudioStream *stream) 159 | +AAUDIO_FUNCTION(AAudioStream_getPerformanceMode, aaudio_performance_mode_t, AAudioStream *stream) 160 | +AAUDIO_FUNCTION(AAudioStream_getDirection, aaudio_direction_t, AAudioStream *stream) 161 | +AAUDIO_FUNCTION(AAudioStream_getFramesWritten, int64_t, AAudioStream *stream) 162 | +AAUDIO_FUNCTION(AAudioStream_getFramesRead, int64_t, AAudioStream *stream) 163 | +AAUDIO_FUNCTION(AAudioStream_getTimestamp, aaudio_result_t, AAudioStream *stream, clockid_t clockid, int64_t* framePosition, int64_t* timeNanoseconds) 164 | + 165 | +/* Utility functions */ 166 | +AAUDIO_FUNCTION(AAudio_convertResultToText, const char *, aaudio_result_t returnCode) 167 | +AAUDIO_FUNCTION(AAudio_convertStreamStateToText, const char*, aaudio_stream_state_t state) 168 | diff --git a/audio/out/aaudio_functions28.inc b/audio/out/aaudio_functions28.inc 169 | new file mode 100644 170 | index 0000000000000..0bfdb6f67069d 171 | --- /dev/null 172 | +++ b/audio/out/aaudio_functions28.inc 173 | @@ -0,0 +1,4 @@ 174 | +/* Stream builder functions - API 28 */ 175 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setUsage, void, AAudioStreamBuilder *builder, aaudio_usage_t usage) 176 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setContentType, void, AAudioStreamBuilder *builder, aaudio_content_type_t contentType) 177 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setSessionId, void, AAudioStreamBuilder* builder, aaudio_session_id_t sessionId) 178 | diff --git a/audio/out/aaudio_functions32.inc b/audio/out/aaudio_functions32.inc 179 | new file mode 100644 180 | index 0000000000000..46dd9c77b2414 181 | --- /dev/null 182 | +++ b/audio/out/aaudio_functions32.inc 183 | @@ -0,0 +1 @@ 184 | +AAUDIO_FUNCTION(AAudioStreamBuilder_setChannelMask, void, AAudioStreamBuilder *builder, aaudio_channel_mask_t channelMask) 185 | diff --git a/audio/out/ao.c b/audio/out/ao.c 186 | index 97fd9e7461938..a35f0cfcf4c15 100644 187 | --- a/audio/out/ao.c 188 | +++ b/audio/out/ao.c 189 | @@ -54,12 +54,16 @@ extern const struct ao_driver audio_out_wasapi; 190 | extern const struct ao_driver audio_out_pcm; 191 | extern const struct ao_driver audio_out_lavc; 192 | extern const struct ao_driver audio_out_sdl; 193 | +extern const struct ao_driver audio_out_aaudio; 194 | 195 | static const struct ao_driver * const audio_out_drivers[] = { 196 | // native: 197 | #if HAVE_AUDIOTRACK 198 | &audio_out_audiotrack, 199 | #endif 200 | +#if HAVE_AAUDIO 201 | + &audio_out_aaudio, 202 | +#endif 203 | #if HAVE_AUDIOUNIT 204 | &audio_out_audiounit, 205 | #endif 206 | diff --git a/audio/out/ao_aaudio.c b/audio/out/ao_aaudio.c 207 | new file mode 100644 208 | index 0000000000000..5b1b6c6082f27 209 | --- /dev/null 210 | +++ b/audio/out/ao_aaudio.c 211 | @@ -0,0 +1,503 @@ 212 | +/* 213 | + * AAudio audio output driver 214 | + * 215 | + * Copyright (C) 2024 Jun Bo Bi 216 | + * 217 | + * This file is part of mpv. 218 | + * 219 | + * mpv is free software; you can redistribute it and/or 220 | + * modify it under the terms of the GNU Lesser General Public 221 | + * License as published by the Free Software Foundation; either 222 | + * version 2.1 of the License, or (at your option) any later version. 223 | + * 224 | + * mpv is distributed in the hope that it will be useful, 225 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of 226 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 227 | + * GNU Lesser General Public License for more details. 228 | + * 229 | + * You should have received a copy of the GNU Lesser General Public 230 | + * License along with mpv. If not, see . 231 | + */ 232 | + 233 | +#include 234 | +#include 235 | + 236 | +#include 237 | +#include 238 | + 239 | +#include "ao.h" 240 | +#include "audio/format.h" 241 | +#include "common/common.h" 242 | +#include "common/msg.h" 243 | +#include "internal.h" 244 | +#include "options/m_option.h" 245 | +#include "osdep/timer.h" 246 | + 247 | +struct priv { 248 | + AAudioStream *stream; 249 | + 250 | + int32_t device_id; 251 | + aaudio_session_id_t session_id; 252 | + int32_t buffer_capacity; 253 | + aaudio_performance_mode_t performance_mode; 254 | + 255 | + int64_t presented; 256 | + int64_t discarded; 257 | + 258 | + int device_api; 259 | + void *lib_handle; 260 | + 261 | +#define AAUDIO_FUNCTION(name, ret, ...) ret (*name)(__VA_ARGS__); 262 | +#include "aaudio_functions26.inc" 263 | +#include "aaudio_functions28.inc" 264 | +#include "aaudio_functions32.inc" 265 | +#undef AAUDIO_FUNCTION 266 | +}; 267 | + 268 | +struct function_map { 269 | + const char *symbol; 270 | + int offset; 271 | +}; 272 | + 273 | +#define AAUDIO_FUNCTION(name, ret, ...) {#name, offsetof(struct priv, name)}, 274 | +static const struct function_map lib_functions26[] = { 275 | +#include "aaudio_functions26.inc" 276 | +}; 277 | + 278 | +static const struct function_map lib_functions28[] = { 279 | +#include "aaudio_functions28.inc" 280 | +}; 281 | + 282 | +static const struct function_map lib_functions32[] = { 283 | +#include "aaudio_functions32.inc" 284 | +}; 285 | +#undef AAUDIO_FUNCTION 286 | + 287 | +// clang-format off 288 | +static const struct { 289 | + int api_level; 290 | + int length; 291 | + const struct function_map *functions; 292 | +} lib_functions[] = { 293 | + {26, MP_ARRAY_SIZE(lib_functions26), lib_functions26}, 294 | + {28, MP_ARRAY_SIZE(lib_functions28), lib_functions28}, 295 | + {32, MP_ARRAY_SIZE(lib_functions32), lib_functions32} 296 | +}; 297 | + 298 | +/* 299 | + * There is no documentation in AAudio for the order of positions for AAudio. 300 | + * It's assumed to work the same way as AudioTrack (even the order of the bits 301 | + * for the position mask is the same for both) 302 | + * See https://developer.android.com/reference/android/media/AudioFormat#channelPositionMask 303 | + */ 304 | +static const struct mp_chmap aaudio_default_chmaps[] = { 305 | + {0}, /* empty */ 306 | + MP_CHMAP_INIT_MONO, /* mono */ 307 | + MP_CHMAP_INIT_STEREO, /* stereo */ 308 | + MP_CHMAP3(FL, FR, FC), /* 3.0 */ 309 | + MP_CHMAP4(FL, FR, BL, BR), /* quad */ 310 | + MP_CHMAP5(FL, FR, FC, BL, BR), /* 5.0 */ 311 | + MP_CHMAP6(FL, FR, FC, LFE, BL, BR), /* 5.1 */ 312 | + MP_CHMAP7(FL, FR, FC, LFE, BL, BR, BC), /* 6.1 */ 313 | + MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), /* 7.1 */ 314 | + {0}, 315 | + MP_CHMAP10(FL, FR, FC, LFE, BL, BR, SL, SR, TSL, TSR), /* 7.1.2 */ 316 | + {0}, 317 | + MP_CHMAP12(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR), /* 7.1.4 */ 318 | + {0}, 319 | + MP_CHMAP14(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, WL, WR), /* 9.1.4 */ 320 | + {0}, 321 | + MP_CHMAP16(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, TSL, TSR, WL, WR) /* 9.1.6 */ 322 | +}; 323 | + 324 | +static const struct mp_chmap aaudio_chmaps[] = { 325 | + {0}, /* empty */ 326 | + /* 327 | + * This should be `{1, {MP_SP(FL)}}` according to spec 328 | + * but `mp_chmap_sel` doesn't like it 329 | + */ 330 | + MP_CHMAP_INIT_MONO, /* mono */ 331 | + MP_CHMAP_INIT_STEREO, /* stereo */ 332 | + MP_CHMAP3(FL, FR, LFE), /* 2.1 */ 333 | + MP_CHMAP3(FL, FR, FC), /* 3.0 */ 334 | + MP_CHMAP3(FL, FR, BC), /* 3.0 (back) */ 335 | + MP_CHMAP4(FL, FR, FC, LFE), /* 3.1 */ 336 | + MP_CHMAP4(FL, FR, TSL, TSR), /* 2.0.2 */ 337 | + MP_CHMAP5(FL, FR, LFE, TSL, TSR), /* 2.1.2 */ 338 | + MP_CHMAP5(FL, FR, FC, TSL, TSR), /* 3.0.2 */ 339 | + MP_CHMAP6(FL, FR, FC, LFE, TSL, TSR), /* 3.1.2 */ 340 | + MP_CHMAP4(FL, FR, BL, BR), /* quad */ 341 | + MP_CHMAP4(FL, FR, SL, SR), /* quad (side) */ 342 | + MP_CHMAP4(FL, FR, FC, BC), /* quad (center) */ 343 | + MP_CHMAP5(FL, FR, FC, BL, BR), /* 5.0 */ 344 | + MP_CHMAP6(FL, FR, FC, LFE, BL, BR), /* 5.1 */ 345 | + MP_CHMAP6(FL, FR, FC, LFE, SL, SR), /* 5.1 (side) */ 346 | + MP_CHMAP7(FL, FR, FC, LFE, BL, BR, BC), /* 6.1 */ 347 | + MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), /* 7.1 */ 348 | + MP_CHMAP8(FL, FR, FC, LFE, BL, BR, TSL, TSR), /* 5.1.2 */ 349 | + MP_CHMAP10(FL, FR, FC, LFE, BL, BR, TFL, TFR, TBL, TBR), /* 5.1.4 */ 350 | + MP_CHMAP10(FL, FR, FC, LFE, BL, BR, SL, SR, TSL, TSR), /* 7.1.2 */ 351 | + MP_CHMAP12(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR), /* 7.1.4 */ 352 | + MP_CHMAP14(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, WL, WR), /* 9.1.4 */ 353 | + MP_CHMAP16(FL, FR, FC, LFE, BL, BR, SL, SR, TFL, TFR, TBL, TBR, TSL, TSR, WL, WR) /* 9.1.6 */ 354 | +}; 355 | + 356 | +static const aaudio_channel_mask_t aaudio_masks[] = { 357 | + AAUDIO_CHANNEL_INVALID, 358 | + AAUDIO_CHANNEL_MONO, 359 | + AAUDIO_CHANNEL_STEREO, 360 | + AAUDIO_CHANNEL_2POINT1, 361 | + AAUDIO_CHANNEL_TRI, 362 | + AAUDIO_CHANNEL_TRI_BACK, 363 | + AAUDIO_CHANNEL_3POINT1, 364 | + AAUDIO_CHANNEL_2POINT0POINT2, 365 | + AAUDIO_CHANNEL_2POINT1POINT2, 366 | + AAUDIO_CHANNEL_3POINT0POINT2, 367 | + AAUDIO_CHANNEL_3POINT1POINT2, 368 | + AAUDIO_CHANNEL_QUAD, 369 | + AAUDIO_CHANNEL_QUAD_SIDE, 370 | + AAUDIO_CHANNEL_SURROUND, 371 | + AAUDIO_CHANNEL_PENTA, 372 | + AAUDIO_CHANNEL_5POINT1, 373 | + AAUDIO_CHANNEL_5POINT1_SIDE, 374 | + AAUDIO_CHANNEL_6POINT1, 375 | + AAUDIO_CHANNEL_7POINT1, 376 | + AAUDIO_CHANNEL_5POINT1POINT2, 377 | + AAUDIO_CHANNEL_5POINT1POINT4, 378 | + AAUDIO_CHANNEL_7POINT1POINT2, 379 | + AAUDIO_CHANNEL_7POINT1POINT4, 380 | + AAUDIO_CHANNEL_9POINT1POINT4, 381 | + AAUDIO_CHANNEL_9POINT1POINT6 382 | +}; 383 | + 384 | +/* 385 | + * The values corresponds to indexes of `aaudio_chmaps` 386 | + * 387 | + * Different devices may support different number of max channels 388 | + * depending on what they set `FCC_LIMIT` to 389 | + * See https://cs.android.com/android/platform/superproject/+/android-latest-release:system/media/audio/include/system/audio.h;l=234 390 | + */ 391 | +static const uint8_t aaudio_max_chnums[] = { 392 | + MP_ARRAY_SIZE(aaudio_chmaps), 393 | + 22, /* FCC_12 */ 394 | + 19, /* FCC_8 */ 395 | + 2, /* FCC_2 */ 396 | + 1 /* FCC_1 */ 397 | +}; 398 | +// clang-format on 399 | + 400 | +static bool load_lib_functions(struct ao *ao) 401 | +{ 402 | + struct priv *p = ao->priv; 403 | + 404 | + p->device_api = android_get_device_api_level(); 405 | + p->lib_handle = dlopen("libaaudio.so", RTLD_NOW | RTLD_GLOBAL); 406 | + if (!p->lib_handle) 407 | + return false; 408 | + 409 | + for (int i = 0; i < MP_ARRAY_SIZE(lib_functions); i++) { 410 | + if (p->device_api < lib_functions[i].api_level) 411 | + break; 412 | + 413 | + for (int j = 0; j < lib_functions[i].length; j++) { 414 | + const char *sym = lib_functions[i].functions[j].symbol; 415 | + void *fun = dlsym(p->lib_handle, sym); 416 | + if (!fun) 417 | + fun = dlsym(RTLD_DEFAULT, sym); 418 | + if (!fun) { 419 | + MP_WARN(ao, "Could not resolve symbol %s\n", sym); 420 | + return false; 421 | + } 422 | + *(void **)((uint8_t *)p + lib_functions[i].functions[j].offset) = fun; 423 | + } 424 | + } 425 | + return true; 426 | +} 427 | +// clang-format off 428 | + 429 | +static void error_callback(AAudioStream *stream, void *context, aaudio_result_t error) 430 | +{ 431 | + struct ao *ao = context; 432 | + struct priv *p = ao->priv; 433 | + 434 | + MP_ERR(ao, "%s, trying to reload...\n", p->AAudio_convertResultToText(error)); 435 | + ao_request_reload(ao); 436 | +} 437 | + 438 | +static aaudio_data_callback_result_t data_callback(AAudioStream *stream, void *context, 439 | + void *data, int32_t nframes) 440 | +{ 441 | + struct ao *ao = context; 442 | + struct priv *p = ao->priv; 443 | + 444 | + aaudio_result_t result; 445 | + int64_t presented, present_time; 446 | + int64_t written = p->AAudioStream_getFramesWritten(stream); 447 | + 448 | + if ((result = p->AAudioStream_getTimestamp(stream, CLOCK_MONOTONIC, &presented, &present_time)) < 0) { 449 | + MP_TRACE(ao, "AAudioStream_getTimestamp() returned %s\n", 450 | + p->AAudio_convertResultToText(result)); 451 | + presented = p->presented; 452 | + } else { 453 | + p->presented = presented; 454 | + } 455 | + 456 | + int64_t end_time = mp_time_ns(); 457 | + end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate; 458 | + end_time += MP_TIME_S_TO_NS(written - (presented + p->discarded)) / ao->samplerate; 459 | + 460 | + ao_read_data(ao, &data, nframes, end_time, NULL, true, true); 461 | + 462 | + return AAUDIO_CALLBACK_RESULT_CONTINUE; 463 | +} 464 | + 465 | +static void uninit(struct ao *ao) 466 | +{ 467 | + struct priv *p = ao->priv; 468 | + 469 | + if (p->stream) { 470 | + p->AAudioStream_close(p->stream); 471 | + p->stream = NULL; 472 | + } 473 | + 474 | + if (p->lib_handle) { 475 | + dlclose(p->lib_handle); 476 | + p->lib_handle = NULL; 477 | + } 478 | +} 479 | + 480 | +static int init(struct ao *ao) 481 | +{ 482 | + if (!load_lib_functions(ao)) 483 | + return -1; 484 | + 485 | + struct priv *p = ao->priv; 486 | + 487 | + aaudio_result_t result; 488 | + AAudioStreamBuilder *builder; 489 | + 490 | + if ((result = p->AAudio_createStreamBuilder(&builder)) < 0) { 491 | + MP_ERR(ao, "AAudio_createStreamBuilder() returned %s\n", 492 | + p->AAudio_convertResultToText(result)); 493 | + return -1; 494 | + } 495 | + 496 | + aaudio_format_t format; 497 | + 498 | + if (p->device_api >= 34 && af_fmt_is_spdif(ao->format)) { 499 | + format = AAUDIO_FORMAT_IEC61937; 500 | + } else if (af_fmt_is_float(ao->format)) { 501 | + ao->format = AF_FORMAT_FLOAT; 502 | + format = AAUDIO_FORMAT_PCM_FLOAT; 503 | + } else if (af_fmt_is_int(ao->format)) { 504 | + if (af_fmt_to_bytes(ao->format) > 2 && p->device_api >= 31) { 505 | + ao->format = AF_FORMAT_S32; 506 | + format = AAUDIO_FORMAT_PCM_I32; 507 | + } else { 508 | + ao->format = AF_FORMAT_S16; 509 | + format = AAUDIO_FORMAT_PCM_I16; 510 | + } 511 | + } else { 512 | + ao->format = AF_FORMAT_S16; 513 | + format = AAUDIO_FORMAT_PCM_I16; 514 | + } 515 | + 516 | + p->AAudioStreamBuilder_setDeviceId(builder, p->device_id); 517 | + p->AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT); 518 | + p->AAudioStreamBuilder_setSharingMode(builder, 519 | + (ao->init_flags & AO_INIT_EXCLUSIVE) 520 | + ? AAUDIO_SHARING_MODE_EXCLUSIVE 521 | + : AAUDIO_SHARING_MODE_SHARED); 522 | + p->AAudioStreamBuilder_setFormat(builder, format); 523 | + p->AAudioStreamBuilder_setSampleRate(builder, ao->samplerate); 524 | + p->AAudioStreamBuilder_setErrorCallback(builder, error_callback, ao); 525 | + p->AAudioStreamBuilder_setBufferCapacityInFrames(builder, 526 | + p->buffer_capacity); 527 | + p->AAudioStreamBuilder_setPerformanceMode(builder, p->performance_mode); 528 | + p->AAudioStreamBuilder_setDataCallback(builder, data_callback, ao); 529 | + 530 | + if (p->device_api >= 28) { 531 | + p->AAudioStreamBuilder_setContentType(builder, 532 | + (ao->init_flags & 533 | + AO_INIT_MEDIA_ROLE_MUSIC) 534 | + ? AAUDIO_CONTENT_TYPE_MUSIC 535 | + : AAUDIO_CONTENT_TYPE_MOVIE); 536 | + p->AAudioStreamBuilder_setUsage(builder, AAUDIO_USAGE_MEDIA); 537 | + p->AAudioStreamBuilder_setSessionId(builder, p->session_id); 538 | + } 539 | + 540 | + if (p->device_api >= 32) { 541 | + uint8_t i = 0; 542 | + struct mp_chmap channels = ao->channels; 543 | + 544 | + do { 545 | + struct mp_chmap_sel sel = {0, .tmp = p}; 546 | + aaudio_channel_mask_t channel_mask = AAUDIO_CHANNEL_INVALID; 547 | + 548 | + for (int j = 1; j < aaudio_max_chnums[i]; j++) 549 | + mp_chmap_sel_add_map(&sel, &aaudio_chmaps[j]); 550 | + 551 | + if (!ao_chmap_sel_adjust(ao, &sel, &channels)) { 552 | + MP_ERR(ao, "Failed to find channel map\n"); 553 | + goto err; 554 | + } 555 | + 556 | + for (uint8_t j = 0; j < aaudio_max_chnums[i]; j++) { 557 | + if (mp_chmap_equals(&aaudio_chmaps[j], &channels)) { 558 | + channel_mask = aaudio_masks[j]; 559 | + break; 560 | + } 561 | + } 562 | + assert(channel_mask != AAUDIO_CHANNEL_INVALID); 563 | + 564 | + p->AAudioStreamBuilder_setChannelMask(builder, channel_mask); 565 | + } while (i++ < MP_ARRAY_SIZE(aaudio_max_chnums) && 566 | + (result = p->AAudioStreamBuilder_openStream(builder, &p->stream)) == 567 | + AAUDIO_ERROR_OUT_OF_RANGE); 568 | + 569 | + ao->channels = channels; 570 | + } else { 571 | + result = p->AAudioStreamBuilder_openStream(builder, &p->stream); 572 | + } 573 | + 574 | + if (result < 0) { 575 | + MP_ERR(ao, "AAudioStreamBuilder_openStream() returned %s\n", 576 | + p->AAudio_convertResultToText(result)); 577 | + goto err; 578 | + } 579 | + 580 | + if (p->device_api < 32) { 581 | + int32_t channel_count = p->AAudioStream_getChannelCount(p->stream); 582 | + 583 | + if (channel_count >= MP_ARRAY_SIZE(aaudio_default_chmaps) || 584 | + (ao->channels = aaudio_default_chmaps[channel_count]).num == 0) { 585 | + MP_ERR(ao, "Unknown layout for channel count: %" PRId32 "\n", 586 | + channel_count); 587 | + goto err; 588 | + } 589 | + } 590 | + 591 | + ao->device_buffer = p->AAudioStream_getBufferCapacityInFrames(p->stream); 592 | + return 1; 593 | + 594 | +err: 595 | + p->AAudioStreamBuilder_delete(builder); 596 | + return -1; 597 | +} 598 | + 599 | +static void start(struct ao *ao) 600 | +{ 601 | + struct priv *p = ao->priv; 602 | + 603 | + aaudio_result_t result; 604 | + p->discarded = p->AAudioStream_getFramesWritten(p->stream) - p->presented; 605 | + 606 | + if ((result = p->AAudioStream_requestStart(p->stream)) < 0) { 607 | + MP_ERR(ao, "AAudioStream_requestStart() returned %s\n", 608 | + p->AAudio_convertResultToText(result)); 609 | + return ao_request_reload(ao); 610 | + } 611 | +} 612 | + 613 | +static bool set_pause(struct ao *ao, bool paused) 614 | +{ 615 | + struct priv *p = ao->priv; 616 | + 617 | + aaudio_result_t result = paused 618 | + ? p->AAudioStream_requestPause(p->stream) 619 | + : p->AAudioStream_requestStart(p->stream); 620 | + 621 | + if (result < 0) { 622 | + MP_ERR(ao, "AAudioStream_request%s() returned %s\n", 623 | + paused ? "Pause" : "Start", 624 | + p->AAudio_convertResultToText(result)); 625 | + ao_request_reload(ao); 626 | + return false; 627 | + } 628 | + 629 | + return true; 630 | +} 631 | + 632 | +static void reset(struct ao *ao) 633 | +{ 634 | + struct priv *p = ao->priv; 635 | + 636 | + aaudio_result_t result; 637 | + aaudio_stream_state_t state = p->AAudioStream_getState(p->stream); 638 | + 639 | + switch (state) { 640 | + case AAUDIO_STREAM_STATE_STARTING: 641 | + case AAUDIO_STREAM_STATE_STOPPING: 642 | + case AAUDIO_STREAM_STATE_PAUSING: 643 | + case AAUDIO_STREAM_STATE_FLUSHING: 644 | + if ((result = p->AAudioStream_waitForStateChange(p->stream, 645 | + state, &state, INT64_MAX)) < 0) { 646 | + MP_ERR(ao, "AAudioStream_waitForStateChange() returned %s\n", 647 | + p->AAudio_convertResultToText(result)); 648 | + return ao_request_reload(ao); 649 | + } 650 | + } 651 | + 652 | + if (state != AAUDIO_STREAM_STATE_PAUSED) { 653 | + if ((result = p->AAudioStream_requestPause(p->stream)) < 0) { 654 | + MP_ERR(ao, "AAudioStream_requestPause() %s\n", 655 | + p->AAudio_convertResultToText(result)); 656 | + return ao_request_reload(ao); 657 | + } 658 | + 659 | + if ((result = p->AAudioStream_waitForStateChange(p->stream, 660 | + AAUDIO_STREAM_STATE_PAUSING, &state, INT64_MAX)) < 0) { 661 | + MP_ERR(ao, "AAudioStream_waitForStateChange() returned %s\n", 662 | + p->AAudio_convertResultToText(result)); 663 | + return ao_request_reload(ao); 664 | + } 665 | + } 666 | + 667 | + if ((result = p->AAudioStream_requestFlush(p->stream)) < 0) { 668 | + MP_ERR(ao, "AAudioStream_requestFlush() returned %s\n", 669 | + p->AAudio_convertResultToText(result)); 670 | + return ao_request_reload(ao); 671 | + } 672 | +} 673 | + 674 | +// clang-format off 675 | +#define OPT_BASE_STRUCT struct priv 676 | + 677 | +const struct ao_driver audio_out_aaudio = { 678 | + .description = "AAudio audio output", 679 | + .name = "aaudio", 680 | + .init = init, 681 | + .uninit = uninit, 682 | + .start = start, 683 | + .reset = reset, 684 | + .set_pause = set_pause, 685 | + 686 | + .priv_size = sizeof(struct priv), 687 | + .priv_defaults = &(const struct priv) { 688 | + .device_id = AAUDIO_UNSPECIFIED, 689 | + .session_id = AAUDIO_SESSION_ID_NONE, 690 | + .buffer_capacity = AAUDIO_UNSPECIFIED, 691 | + .performance_mode = AAUDIO_PERFORMANCE_MODE_NONE, 692 | + .presented = 0, 693 | + .discarded = 0, 694 | + .stream = NULL, 695 | + .lib_handle = NULL 696 | + }, 697 | + .options_prefix = "aaudio", 698 | + .options = 699 | + (const struct m_option[]){ 700 | + {"device-id", OPT_CHOICE(device_id, 701 | + {"auto", AAUDIO_UNSPECIFIED}), 702 | + M_RANGE(FLT_MIN, FLT_MAX)}, 703 | + {"session-id", OPT_CHOICE(session_id, 704 | + {"none", AAUDIO_SESSION_ID_NONE}), 705 | + M_RANGE(FLT_MIN, FLT_MAX)}, 706 | + {"buffer-capacity", OPT_CHOICE(buffer_capacity, 707 | + {"auto", AAUDIO_UNSPECIFIED}), 708 | + M_RANGE(1, FLT_MAX)}, 709 | + {"performance-mode", OPT_CHOICE(performance_mode, 710 | + {"none", AAUDIO_PERFORMANCE_MODE_NONE}, 711 | + {"low-latency", AAUDIO_PERFORMANCE_MODE_LOW_LATENCY}, 712 | + {"power-saving", AAUDIO_PERFORMANCE_MODE_POWER_SAVING})}, 713 | + {0}}, 714 | +}; 715 | diff --git a/meson.build b/meson.build 716 | index 09d80483c4ab2..92b9ce15f49f1 100644 717 | --- a/meson.build 718 | +++ b/meson.build 719 | @@ -855,6 +855,13 @@ if features['audiotrack'] 720 | sources += files('audio/out/ao_audiotrack.c') 721 | endif 722 | 723 | +aaudio_opt = get_option('aaudio').require(features['android']) 724 | +features += {'aaudio': cc.has_header_symbol('aaudio/AAudio.h', 725 | + 'AAudioStream', required: aaudio_opt)} 726 | +if features['aaudio'] 727 | + sources += files('audio/out/ao_aaudio.c') 728 | +endif 729 | + 730 | opensles = cc.find_library('OpenSLES', required: get_option('opensles')) 731 | features += {'opensles': opensles.found()} 732 | if features['opensles'] 733 | diff --git a/meson.options b/meson.options 734 | index d7b71efa149b4..a8a3836a5ce32 100644 735 | --- a/meson.options 736 | +++ b/meson.options 737 | @@ -46,6 +46,7 @@ option('avfoundation', type: 'feature', value: 'auto', description: 'AVFoundatio 738 | option('jack', type: 'feature', value: 'auto', description: 'JACK audio output') 739 | option('openal', type: 'feature', value: 'disabled', description: 'OpenAL audio output') 740 | option('audiotrack', type: 'feature', value: 'auto', description: 'Android AudioTrack audio output') 741 | +option('aaudio', type: 'feature', value: 'auto', description: 'Android AAudio audio output') 742 | option('opensles', type: 'feature', value: 'auto', description: 'OpenSL ES audio output') 743 | option('oss-audio', type: 'feature', value: 'auto', description: 'OSSv4 audio output') 744 | option('pipewire', type: 'feature', value: 'auto', description: 'PipeWire audio output') 745 | -------------------------------------------------------------------------------- /buildscripts/patches/mpv/ffmpeg-8.patch: -------------------------------------------------------------------------------- 1 | From 26b29fba02a2782f68e2906f837d21201fc6f1b9 Mon Sep 17 00:00:00 2001 2 | From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= 3 | Date: Fri, 28 Mar 2025 19:12:01 +0100 4 | Subject: [PATCH] demux_mkv: fix compilation after deprecated definitions 5 | removal 6 | 7 | See: https://github.com/FFmpeg/FFmpeg/commit/822432769868da325ba03774df1084aa78b9a5a0 8 | --- 9 | demux/demux_mkv.c | 6 +++--- 10 | 1 file changed, 3 insertions(+), 3 deletions(-) 11 | 12 | diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c 13 | index 135edcc23d82b..cc7ce3e98f4f6 100644 14 | --- a/demux/demux_mkv.c 15 | +++ b/demux/demux_mkv.c 16 | @@ -2200,16 +2200,16 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track) 17 | // [0x30..0x37] are component tags utilized for 18 | // non-mobile captioning service ("profile A"). 19 | if (component_tag >= 0x30 && component_tag <= 0x37) 20 | - lav->profile = FF_PROFILE_ARIB_PROFILE_A; 21 | + lav->profile = AV_PROFILE_ARIB_PROFILE_A; 22 | break; 23 | case 0x0012: 24 | // component tag 0x87 signifies a mobile/partial reception 25 | // (1seg) captioning service ("profile C"). 26 | if (component_tag == 0x87) 27 | - lav->profile = FF_PROFILE_ARIB_PROFILE_C; 28 | + lav->profile = AV_PROFILE_ARIB_PROFILE_C; 29 | break; 30 | } 31 | - if (lav->profile == FF_PROFILE_UNKNOWN) 32 | + if (lav->profile == AV_PROFILE_UNKNOWN) 33 | MP_WARN(demuxer, "ARIB caption profile %02x / %04x not supported.\n", 34 | component_tag, data_component_id); 35 | } 36 | -------------------------------------------------------------------------------- /buildscripts/scripts/dav1d.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | build=_build$ndk_suffix 7 | 8 | if [ "$1" == "build" ]; then 9 | true 10 | elif [ "$1" == "clean" ]; then 11 | rm -rf $build 12 | exit 0 13 | else 14 | exit 255 15 | fi 16 | 17 | unset CC CXX # meson wants these unset 18 | 19 | meson setup $build --cross-file "$prefix_dir"/crossfile.txt \ 20 | -Denable_tests=false \ 21 | -Db_lto=true \ 22 | -Dstack_alignment=16 23 | 24 | ninja -C $build -j$cores 25 | DESTDIR="$prefix_dir" ninja -C $build install 26 | -------------------------------------------------------------------------------- /buildscripts/scripts/ffmpeg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | if [ "$1" == "build" ]; then 7 | true 8 | elif [ "$1" == "clean" ]; then 9 | rm -rf _build$ndk_suffix 10 | exit 0 11 | else 12 | exit 255 13 | fi 14 | 15 | mkdir -p _build$ndk_suffix 16 | cd _build$ndk_suffix 17 | 18 | cpu=armv7-a 19 | [[ "$ndk_triple" == "aarch64"* ]] && cpu=armv8-a 20 | [[ "$ndk_triple" == "x86_64"* ]] && cpu=generic 21 | [[ "$ndk_triple" == "i686"* ]] && cpu="i686 --disable-asm" 22 | 23 | cpuflags= 24 | [[ "$ndk_triple" == "arm"* ]] && cpuflags="$cpuflags -mfpu=neon -mcpu=cortex-a8" 25 | 26 | ../configure \ 27 | --target-os=android --enable-cross-compile --cross-prefix=$ndk_triple- --cc=$CC \ 28 | --arch=${ndk_triple%%-*} --cpu=$cpu --pkg-config=pkg-config --nm=llvm-nm \ 29 | --extra-cflags="-I$prefix_dir/include $cpuflags" --extra-ldflags="-L$prefix_dir/lib" \ 30 | --enable-{jni,mediacodec,mbedtls,libdav1d} --disable-vulkan \ 31 | --disable-static --enable-shared --enable-{gpl,version3} \ 32 | --disable-{stripping,doc,programs} \ 33 | --disable-{muxers,encoders,devices,filters} \ 34 | --disable-v4l2-m2m 35 | 36 | make -j$cores 37 | make DESTDIR="$prefix_dir" install 38 | 39 | ln -sf "$prefix_dir"/lib/libswresample.so "$native_dir" 40 | ln -sf "$prefix_dir"/lib/libpostproc.so "$native_dir" 41 | ln -sf "$prefix_dir"/lib/libavutil.so "$native_dir" 42 | ln -sf "$prefix_dir"/lib/libavcodec.so "$native_dir" 43 | ln -sf "$prefix_dir"/lib/libavformat.so "$native_dir" 44 | ln -sf "$prefix_dir"/lib/libswscale.so "$native_dir" 45 | ln -sf "$prefix_dir"/lib/libavfilter.so "$native_dir" 46 | ln -sf "$prefix_dir"/lib/libavdevice.so "$native_dir" 47 | -------------------------------------------------------------------------------- /buildscripts/scripts/freetype.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | build=_build$ndk_suffix 7 | 8 | if [ "$1" == "build" ]; then 9 | true 10 | elif [ "$1" == "clean" ]; then 11 | rm -rf $build 12 | exit 0 13 | else 14 | exit 255 15 | fi 16 | 17 | unset CC CXX # meson wants these unset 18 | 19 | meson setup $build --cross-file "$prefix_dir"/crossfile.txt 20 | 21 | ninja -C $build -j$cores 22 | DESTDIR="$prefix_dir" ninja -C $build install 23 | -------------------------------------------------------------------------------- /buildscripts/scripts/fribidi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | build=_build$ndk_suffix 7 | 8 | if [ "$1" == "build" ]; then 9 | true 10 | elif [ "$1" == "clean" ]; then 11 | rm -rf $build 12 | exit 0 13 | else 14 | exit 255 15 | fi 16 | 17 | unset CC CXX # meson wants these unset 18 | 19 | meson setup $build --cross-file "$prefix_dir"/crossfile.txt \ 20 | -D{tests,docs}=false 21 | 22 | ninja -C $build -j$cores 23 | DESTDIR="$prefix_dir" ninja -C $build install 24 | -------------------------------------------------------------------------------- /buildscripts/scripts/harfbuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | build=_build$ndk_suffix 7 | 8 | if [ "$1" == "build" ]; then 9 | true 10 | elif [ "$1" == "clean" ]; then 11 | rm -rf $build 12 | exit 0 13 | else 14 | exit 255 15 | fi 16 | 17 | unset CC CXX # meson wants these unset 18 | 19 | meson setup $build --cross-file "$prefix_dir"/crossfile.txt \ 20 | -Dtests=disabled -Ddocs=disabled 21 | 22 | ninja -C $build -j$cores 23 | DESTDIR="$prefix_dir" ninja -C $build install 24 | -------------------------------------------------------------------------------- /buildscripts/scripts/libass.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | if [ "$1" == "build" ]; then 7 | true 8 | elif [ "$1" == "clean" ]; then 9 | rm -rf _build$ndk_suffix 10 | exit 0 11 | else 12 | exit 255 13 | fi 14 | 15 | [ -f configure ] || ./autogen.sh 16 | 17 | mkdir -p _build$ndk_suffix 18 | cd _build$ndk_suffix 19 | 20 | ../configure \ 21 | --host=$ndk_triple --with-pic \ 22 | --enable-static --disable-shared \ 23 | --enable-libunibreak --disable-require-system-font-provider 24 | 25 | make -j$cores 26 | make DESTDIR="$prefix_dir" install 27 | -------------------------------------------------------------------------------- /buildscripts/scripts/libplacebo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | build=_build$ndk_suffix 7 | 8 | if [ "$1" == "build" ]; then 9 | true 10 | elif [ "$1" == "clean" ]; then 11 | rm -rf $build 12 | exit 0 13 | else 14 | exit 255 15 | fi 16 | 17 | unset CC CXX 18 | meson setup $build --cross-file "$prefix_dir"/crossfile.txt \ 19 | -Dvulkan=disabled -Ddemos=false 20 | 21 | ninja -C $build -j$cores 22 | DESTDIR="$prefix_dir" ninja -C $build install 23 | 24 | # add missing library for static linking 25 | # this isn't "-lstdc++" due to a meson bug: https://github.com/mesonbuild/meson/issues/11300 26 | ${SED:-sed} '/^Libs:/ s|$| -lc++|' "$prefix_dir/lib/pkgconfig/libplacebo.pc" -i 27 | -------------------------------------------------------------------------------- /buildscripts/scripts/libunibreak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | build=_build$ndk_suffix 7 | 8 | if [ "$1" == "build" ]; then 9 | true 10 | elif [ "$1" == "clean" ]; then 11 | rm -rf $build 12 | exit 0 13 | else 14 | exit 255 15 | fi 16 | 17 | mkdir -p $build 18 | cd $build 19 | 20 | ../configure \ 21 | --host=$ndk_triple --with-pic \ 22 | --enable-static --disable-shared 23 | 24 | make -j$cores 25 | make DESTDIR="$prefix_dir" install 26 | -------------------------------------------------------------------------------- /buildscripts/scripts/lua.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | if [ "$1" == "build" ]; then 7 | true 8 | elif [ "$1" == "clean" ]; then 9 | make clean 10 | exit 0 11 | else 12 | exit 255 13 | fi 14 | 15 | # Building seperately from source tree is not supported, this means we are forced to always clean 16 | $0 clean 17 | 18 | # LUA_T= and LUAC_T= to disable building lua & luac 19 | # -Dgetlocaledecpoint()=('.') fixes bionic missing decimal_point in localeconv 20 | make CC="$CC" AR="$AR rc" RANLIB="$RANLIB" \ 21 | MYCFLAGS="-fPIC -Dgetlocaledecpoint\(\)=\(\'.\'\)" \ 22 | PLAT=linux LUA_T= LUAC_T= -j$cores 23 | 24 | # TO_BIN=/dev/null disables installing lua & luac 25 | make INSTALL=${INSTALL:-install} INSTALL_TOP="$prefix_dir" TO_BIN=/dev/null install 26 | 27 | # make pc only generates a partial pkg-config file because ???? 28 | mkdir -p $prefix_dir/lib/pkgconfig 29 | make pc >$prefix_dir/lib/pkgconfig/lua.pc 30 | cat >>$prefix_dir/lib/pkgconfig/lua.pc <<'EOF' 31 | Name: Lua 32 | Description: 33 | Version: ${version} 34 | Libs: -L${libdir} -llua 35 | Cflags: -I${includedir} 36 | EOF 37 | -------------------------------------------------------------------------------- /buildscripts/scripts/mbedtls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | if [ "$1" == "build" ]; then 7 | true 8 | elif [ "$1" == "clean" ]; then 9 | make clean 10 | exit 0 11 | else 12 | exit 255 13 | fi 14 | 15 | $0 clean # separate building not supported, always clean 16 | 17 | make -j$cores no_test 18 | make DESTDIR="$prefix_dir" install 19 | -------------------------------------------------------------------------------- /buildscripts/scripts/mpv-android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | BUILD="$DIR/.." 5 | MPV_ANDROID="$DIR/../.." 6 | 7 | . $BUILD/include/path.sh 8 | . $BUILD/include/depinfo.sh 9 | 10 | if [ "$1" == "build" ]; then 11 | true 12 | elif [ "$1" == "clean" ]; then 13 | rm -rf $MPV_ANDROID/{libmpv,.}/build $MPV_ANDROID/libmpv/src/main/{libs,obj} 14 | exit 0 15 | else 16 | exit 255 17 | fi 18 | 19 | nativeprefix () { 20 | if [ -f $BUILD/prefix/$1/lib/libmpv.so ]; then 21 | echo $BUILD/prefix/$1 22 | else 23 | echo >&2 "Warning: libmpv.so not found in native prefix for $1, support will be omitted" 24 | fi 25 | } 26 | 27 | ./gradlew assembleRelease 28 | -------------------------------------------------------------------------------- /buildscripts/scripts/mpv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | build=_build$ndk_suffix 7 | 8 | if [ "$1" == "build" ]; then 9 | true 10 | elif [ "$1" == "clean" ]; then 11 | rm -rf $build 12 | exit 0 13 | else 14 | exit 255 15 | fi 16 | 17 | unset CC CXX # meson wants these unset 18 | 19 | meson setup $build --cross-file "$prefix_dir"/crossfile.txt \ 20 | --default-library shared \ 21 | -Diconv=disabled -Dlua=enabled \ 22 | -Dlibmpv=true -Dcplayer=false \ 23 | -Dmanpage-build=disabled 24 | 25 | ninja -C $build -j$cores 26 | DESTDIR="$prefix_dir" ninja -C $build install 27 | 28 | ln -sf "$prefix_dir"/lib/libmpv.so "$native_dir" 29 | -------------------------------------------------------------------------------- /buildscripts/scripts/shaderc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . ../../include/depinfo.sh 4 | . ../../include/path.sh 5 | 6 | if [ "$1" == "build" ]; then 7 | true 8 | elif [ "$1" == "clean" ]; then 9 | rm -rf local include libs 10 | exit 0 11 | else 12 | exit 255 13 | fi 14 | 15 | builddir=$PWD 16 | 17 | abi=armeabi-v7a 18 | [[ "$ndk_triple" == "aarch64"* ]] && abi=arm64-v8a 19 | [[ "$ndk_triple" == "x86_64"* ]] && abi=x86_64 20 | [[ "$ndk_triple" == "i686"* ]] && abi=x86 21 | 22 | # build using the NDK's scripts, but keep object files in our build dir 23 | cd "$(dirname "$(which ndk-build)")/sources/third_party/shaderc" 24 | ndk-build -j$cores \ 25 | NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \ 26 | APP_PLATFORM=android-26 APP_STL=c++_shared APP_ABI=$abi \ 27 | NDK_APP_OUT="$builddir" NDK_APP_LIBS_OUT="$builddir/libs" \ 28 | libshaderc_combined 29 | 30 | cd "$builddir" 31 | cp -r include/* "$prefix_dir/include" 32 | cp libs/*/$abi/libshaderc.a "$prefix_dir/lib/libshaderc_combined.a" 33 | 34 | # create a pkgconfig file 35 | # 'libc++' instead of 'libstdc++': workaround for meson linking bug 36 | mkdir -p "$prefix_dir"/lib/pkgconfig 37 | cat >"$prefix_dir"/lib/pkgconfig/shaderc_combined.pc <<"END" 38 | Name: shaderc_combined 39 | Description: 40 | Version: 2022.3-unknown 41 | Libs: -L/usr/lib -lshaderc_combined 42 | Cflags: -I/usr/include 43 | END -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | android-plugin = "8.13.0" 3 | kotlin = "2.2.20" 4 | maven-publish = "0.34.0" 5 | 6 | [plugins] 7 | android-library = { id = "com.android.library", version.ref = "android-plugin" } 8 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 9 | maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarnedemeulemeester/libmpv-android/8c4778b5aad441bb0449a7f9b3d6d827fd3d6a2a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" -------------------------------------------------------------------------------- /libmpv/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.vanniktech.maven.publish.AndroidSingleVariantLibrary 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.kotlin.android) 6 | alias(libs.plugins.maven.publish) 7 | } 8 | 9 | android { 10 | namespace = "dev.jdtech.mpv" 11 | compileSdk = 36 12 | buildToolsVersion = "36.1.0" 13 | ndkVersion = "29.0.14206865" 14 | 15 | defaultConfig { 16 | minSdk = 26 17 | consumerProguardFiles("proguard-rules.pro") 18 | externalNativeBuild { 19 | cmake { 20 | arguments += listOf( 21 | "-DANDROID_STL=c++_shared", 22 | ) 23 | cFlags += "-Werror" 24 | cppFlags += "-std=c++11" 25 | } 26 | } 27 | } 28 | 29 | externalNativeBuild { 30 | cmake { 31 | path = file("src/main/cpp/CMakeLists.txt") 32 | version = "4.1.2" 33 | } 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_17 38 | targetCompatibility = JavaVersion.VERSION_17 39 | } 40 | } 41 | 42 | mavenPublishing { 43 | publishToMavenCentral(automaticRelease = true) 44 | signAllPublications() 45 | 46 | configure( 47 | platform = AndroidSingleVariantLibrary( 48 | variant = "release", 49 | sourcesJar = true, 50 | publishJavadocJar = false, 51 | ) 52 | ) 53 | 54 | coordinates( 55 | groupId = "dev.jdtech.mpv", 56 | artifactId = "libmpv", 57 | version = "0.5.1" 58 | ) 59 | 60 | pom { 61 | name = "libmpv-android" 62 | description = "libmpv for Android" 63 | inceptionYear = "2023" 64 | url = "https://github.com/jarnedemeulemeester/libmpv-android" 65 | licenses { 66 | license { 67 | name = "MIT license" 68 | url = "https://github.com/jarnedemeulemeester/libmpv-android/blob/main/LICENSE" 69 | } 70 | } 71 | developers { 72 | developer { 73 | id = "jarnedemeulemeester" 74 | name = "Jarne Demeulemeester" 75 | email = "jarnedemeulemeester@gmail.com" 76 | } 77 | } 78 | scm { 79 | url = "https://github.com/jarnedemeulemeester/libmpv-android.git" 80 | connection = "scm:git@github.com:jarnedemeulemeester/libmpv-android.git" 81 | developerConnection = "scm:git@github.com:jarnedemeulemeester/libmpv-android.git" 82 | } 83 | issueManagement { 84 | system = "GitHub" 85 | url = "https://github.com/jarnedemeulemeester/libmpv-android/issues" 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /libmpv/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class dev.jdtech.mpv.MPVLib { *; } 2 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | 3 | project("libmpv") 4 | 5 | add_library( 6 | player 7 | SHARED 8 | main.cpp render.cpp log.cpp jni_utils.cpp property.cpp event.cpp 9 | ) 10 | 11 | add_library( 12 | mpv 13 | SHARED 14 | IMPORTED 15 | ) 16 | 17 | set_target_properties( 18 | mpv 19 | PROPERTIES IMPORTED_LOCATION 20 | ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libmpv.so 21 | ) 22 | 23 | add_library( 24 | avcodec 25 | SHARED 26 | IMPORTED 27 | ) 28 | 29 | set_target_properties( 30 | avcodec 31 | PROPERTIES IMPORTED_LOCATION 32 | ${CMAKE_CURRENT_SOURCE_DIR}/../../../../buildscripts/prefix/${ANDROID_ABI}/lib/libavcodec.so 33 | ) 34 | 35 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../../../buildscripts/prefix/${ANDROID_ABI}/include ) 36 | 37 | target_link_libraries( player mpv avcodec log ) -------------------------------------------------------------------------------- /libmpv/src/main/cpp/event.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "globals.h" 6 | #include "jni_utils.h" 7 | #include "log.h" 8 | 9 | static void sendPropertyUpdateToJava(JNIEnv *env, mpv_event_property *prop) { 10 | jstring jprop = env->NewStringUTF(prop->name); 11 | jstring jvalue = NULL; 12 | switch (prop->format) { 13 | case MPV_FORMAT_NONE: 14 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_S, jprop); 15 | break; 16 | case MPV_FORMAT_FLAG: 17 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sb, jprop, *(int*)prop->data); 18 | break; 19 | case MPV_FORMAT_INT64: 20 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sl, jprop, *(int64_t*)prop->data); 21 | break; 22 | case MPV_FORMAT_DOUBLE: 23 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sd, jprop, *(double*)prop->data); 24 | break; 25 | case MPV_FORMAT_STRING: 26 | jvalue = env->NewStringUTF(*(const char**)prop->data); 27 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_SS, jprop, jvalue); 28 | break; 29 | default: 30 | ALOGV("sendPropertyUpdateToJava: Unknown property update format received in callback: %d!", prop->format); 31 | break; 32 | } 33 | if (jprop) 34 | env->DeleteLocalRef(jprop); 35 | if (jvalue) 36 | env->DeleteLocalRef(jvalue); 37 | } 38 | 39 | static void sendEventToJava(JNIEnv *env, int event) { 40 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_event, event); 41 | } 42 | 43 | static inline bool invalid_utf8(unsigned char c) { 44 | return c == 0xc0 || c == 0xc1 || c >= 0xf5; 45 | } 46 | 47 | static void sendLogMessageToJava(JNIEnv *env, mpv_event_log_message *msg) { 48 | // filter the most obvious cases of invalid utf-8, since Java would choke on it 49 | const auto invalid_utf8 = [] (unsigned char c) { 50 | return c == 0xc0 || c == 0xc1 || c >= 0xf5; 51 | }; 52 | for (int i = 0; msg->text[i]; i++) { 53 | if (invalid_utf8(static_cast(msg->text[i]))) 54 | return; 55 | } 56 | 57 | jstring jprefix = env->NewStringUTF(msg->prefix); 58 | jstring jtext = env->NewStringUTF(msg->text); 59 | 60 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_logMessage_SiS, 61 | jprefix, (jint) msg->log_level, jtext); 62 | 63 | if (jprefix) 64 | env->DeleteLocalRef(jprefix); 65 | if (jtext) 66 | env->DeleteLocalRef(jtext); 67 | } 68 | 69 | void *event_thread(void *arg) { 70 | JNIEnv *env = NULL; 71 | acquire_jni_env(g_vm, &env); 72 | if (!env) 73 | die("failed to acquire java env"); 74 | 75 | while (true) { 76 | mpv_event *mp_event; 77 | mpv_event_property *mp_property; 78 | mpv_event_log_message *msg; 79 | 80 | mp_event = mpv_wait_event(g_mpv, -1.0); 81 | 82 | if (g_event_thread_request_exit) 83 | break; 84 | 85 | if (mp_event->event_id == MPV_EVENT_NONE) 86 | continue; 87 | 88 | switch (mp_event->event_id) { 89 | case MPV_EVENT_LOG_MESSAGE: 90 | msg = (mpv_event_log_message*)mp_event->data; 91 | ALOGV("[%s:%s] %s", msg->prefix, msg->level, msg->text); 92 | sendLogMessageToJava(env, msg); 93 | break; 94 | case MPV_EVENT_PROPERTY_CHANGE: 95 | mp_property = (mpv_event_property*)mp_event->data; 96 | sendPropertyUpdateToJava(env, mp_property); 97 | break; 98 | default: 99 | ALOGV("event: %s\n", mpv_event_name(mp_event->event_id)); 100 | sendEventToJava(env, mp_event->event_id); 101 | break; 102 | } 103 | } 104 | 105 | g_vm->DetachCurrentThread(); 106 | 107 | return NULL; 108 | } 109 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void *event_thread(void *arg); 4 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/globals.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern JavaVM *g_vm; 6 | extern mpv_handle *g_mpv; 7 | extern std::atomic g_event_thread_request_exit; 8 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/jni_utils.cpp: -------------------------------------------------------------------------------- 1 | #define UTIL_EXTERN 2 | #include "jni_utils.h" 3 | 4 | #include 5 | #include 6 | 7 | bool acquire_jni_env(JavaVM *vm, JNIEnv **env) 8 | { 9 | int ret = vm->GetEnv((void**) env, JNI_VERSION_1_6); 10 | if (ret == JNI_EDETACHED) 11 | return vm->AttachCurrentThread(env, NULL) == 0; 12 | else 13 | return ret == JNI_OK; 14 | } 15 | 16 | // Apparently it's considered slow to FindClass and GetMethodID every time we need them, 17 | // so let's have a nice cache here 18 | void init_methods_cache(JNIEnv *env) { 19 | static bool methods_initialized = false; 20 | if (methods_initialized) 21 | return; 22 | 23 | #define FIND_CLASS(name) reinterpret_cast(env->NewGlobalRef(env->FindClass(name))) 24 | java_Integer = FIND_CLASS("java/lang/Integer"); 25 | java_Integer_init = env->GetMethodID(java_Integer, "", "(I)V"); 26 | java_Double = FIND_CLASS("java/lang/Double"); 27 | java_Double_init = env->GetMethodID(java_Double, "", "(D)V"); 28 | java_Boolean = FIND_CLASS("java/lang/Boolean"); 29 | java_Boolean_init = env->GetMethodID(java_Boolean, "", "(Z)V"); 30 | 31 | mpv_MPVLib = FIND_CLASS("dev/jdtech/mpv/MPVLib"); 32 | mpv_MPVLib_eventProperty_S = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;)V"); // eventProperty(String) 33 | mpv_MPVLib_eventProperty_Sb = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Z)V"); // eventProperty(String, boolean) 34 | mpv_MPVLib_eventProperty_Sl = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;J)V"); // eventProperty(String, long) 35 | mpv_MPVLib_eventProperty_Sd = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;D)V"); // eventProperty(String, double) 36 | mpv_MPVLib_eventProperty_SS = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Ljava/lang/String;)V"); // eventProperty(String, String) 37 | mpv_MPVLib_event = env->GetStaticMethodID(mpv_MPVLib, "event", "(I)V"); // event(int) 38 | mpv_MPVLib_logMessage_SiS = env->GetStaticMethodID(mpv_MPVLib, "logMessage", "(Ljava/lang/String;ILjava/lang/String;)V"); // logMessage(String, int, String) 39 | #undef FIND_CLASS 40 | 41 | methods_initialized = true; 42 | } 43 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/jni_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define jni_func_name(name) Java_dev_jdtech_mpv_MPVLib_##name 6 | #define jni_func(return_type, name, ...) JNIEXPORT return_type JNICALL jni_func_name(name) (JNIEnv *env, jobject obj, ##__VA_ARGS__) 7 | 8 | bool acquire_jni_env(JavaVM *vm, JNIEnv **env); 9 | void init_methods_cache(JNIEnv *env); 10 | 11 | #ifndef UTIL_EXTERN 12 | #define UTIL_EXTERN extern 13 | #endif 14 | 15 | UTIL_EXTERN jclass java_Integer, java_Double, java_Boolean; 16 | UTIL_EXTERN jmethodID java_Integer_init, java_Double_init, java_Boolean_init; 17 | 18 | UTIL_EXTERN jclass mpv_MPVLib; 19 | UTIL_EXTERN jmethodID mpv_MPVLib_eventProperty_S, 20 | mpv_MPVLib_eventProperty_Sb, 21 | mpv_MPVLib_eventProperty_Sl, 22 | mpv_MPVLib_eventProperty_Sd, 23 | mpv_MPVLib_eventProperty_SS, 24 | mpv_MPVLib_event, 25 | mpv_MPVLib_logMessage_SiS; 26 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | 5 | void die(const char *msg) 6 | { 7 | ALOGE("%s", msg); 8 | exit(1); 9 | } 10 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define DEBUG 1 6 | 7 | #define LOG_TAG "mpv" 8 | #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 9 | #if DEBUG 10 | #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 11 | #else 12 | #define ALOGV(...) (void)0 13 | #endif 14 | 15 | __attribute__((noreturn)) void die(const char *msg); 16 | 17 | #define CHECK_MPV_INIT() do { \ 18 | if (__builtin_expect(!g_mpv, 0)) \ 19 | die("libmpv is not initialized"); \ 20 | } while (0) 21 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | extern "C" { 13 | #include 14 | } 15 | 16 | #include "log.h" 17 | #include "jni_utils.h" 18 | #include "event.h" 19 | 20 | #define ARRAYLEN(a) (sizeof(a)/sizeof(a[0])) 21 | 22 | extern "C" { 23 | jni_func(void, create, jobject appctx); 24 | jni_func(void, init); 25 | jni_func(void, destroy); 26 | 27 | jni_func(void, command, jobjectArray jarray); 28 | }; 29 | 30 | JavaVM *g_vm; 31 | mpv_handle *g_mpv; 32 | std::atomic g_event_thread_request_exit(false); 33 | 34 | static pthread_t event_thread_id; 35 | 36 | static void prepare_environment(JNIEnv *env, jobject appctx) { 37 | setlocale(LC_NUMERIC, "C"); 38 | 39 | if (!env->GetJavaVM(&g_vm) && g_vm) 40 | av_jni_set_java_vm(g_vm, NULL); 41 | 42 | jobject global_appctx = env->NewGlobalRef(appctx); 43 | if (global_appctx) 44 | av_jni_set_android_app_ctx(global_appctx, NULL); 45 | 46 | init_methods_cache(env); 47 | } 48 | 49 | jni_func(void, create, jobject appctx) { 50 | prepare_environment(env, appctx); 51 | 52 | if (g_mpv) 53 | die("mpv is already initialized"); 54 | 55 | g_mpv = mpv_create(); 56 | if (!g_mpv) 57 | die("context init failed"); 58 | 59 | mpv_request_log_messages(g_mpv, "v"); 60 | } 61 | 62 | jni_func(void, init) { 63 | if (!g_mpv) 64 | die("mpv is not created"); 65 | 66 | if (mpv_initialize(g_mpv) < 0) 67 | die("mpv init failed"); 68 | 69 | g_event_thread_request_exit = false; 70 | if (pthread_create(&event_thread_id, NULL, event_thread, NULL) != 0) 71 | die("thread create failed"); 72 | pthread_setname_np(event_thread_id, "event_thread"); 73 | } 74 | 75 | jni_func(void, destroy) { 76 | if (!g_mpv) { 77 | ALOGV("mpv destroy called but it's already destroyed"); 78 | return; 79 | } 80 | 81 | // poke event thread and wait for it to exit 82 | g_event_thread_request_exit = true; 83 | mpv_wakeup(g_mpv); 84 | pthread_join(event_thread_id, NULL); 85 | 86 | mpv_terminate_destroy(g_mpv); 87 | g_mpv = NULL; 88 | } 89 | 90 | jni_func(void, command, jobjectArray jarray) { 91 | CHECK_MPV_INIT(); 92 | 93 | const char *arguments[128] = { 0 }; 94 | int len = env->GetArrayLength(jarray); 95 | if (len >= ARRAYLEN(arguments)) 96 | die("too many command arguments"); 97 | 98 | for (int i = 0; i < len; ++i) 99 | arguments[i] = env->GetStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), NULL); 100 | 101 | mpv_command(g_mpv, arguments); 102 | 103 | for (int i = 0; i < len; ++i) 104 | env->ReleaseStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), arguments[i]); 105 | } 106 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/property.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "jni_utils.h" 7 | #include "log.h" 8 | #include "globals.h" 9 | 10 | extern "C" { 11 | jni_func(jint, setOptionString, jstring option, jstring value); 12 | 13 | jni_func(jobject, getPropertyInt, jstring property); 14 | jni_func(void, setPropertyInt, jstring property, jstring value); 15 | jni_func(jobject, getPropertyDouble, jstring property); 16 | jni_func(void, setPropertyDouble, jstring property, jdouble value); 17 | jni_func(jobject, getPropertyBoolean, jstring property); 18 | jni_func(void, setPropertyBoolean, jstring property, jboolean value); 19 | jni_func(jstring, getPropertyString, jstring jproperty); 20 | jni_func(void, setPropertyString, jstring jproperty, jstring jvalue); 21 | 22 | jni_func(void, observeProperty, jstring property, jint format); 23 | } 24 | 25 | jni_func(jint, setOptionString, jstring joption, jstring jvalue) { 26 | CHECK_MPV_INIT(); 27 | 28 | const char *option = env->GetStringUTFChars(joption, NULL); 29 | const char *value = env->GetStringUTFChars(jvalue, NULL); 30 | 31 | int result = mpv_set_option_string(g_mpv, option, value); 32 | 33 | env->ReleaseStringUTFChars(joption, option); 34 | env->ReleaseStringUTFChars(jvalue, value); 35 | 36 | return result; 37 | } 38 | 39 | static int common_get_property(JNIEnv *env, jstring jproperty, mpv_format format, void *output) { 40 | CHECK_MPV_INIT(); 41 | 42 | const char *prop = env->GetStringUTFChars(jproperty, NULL); 43 | int result = mpv_get_property(g_mpv, prop, format, output); 44 | if (result < 0) 45 | ALOGE("mpv_get_property(%s) format %d returned error %s", prop, format, mpv_error_string(result)); 46 | env->ReleaseStringUTFChars(jproperty, prop); 47 | 48 | return result; 49 | } 50 | 51 | static int common_set_property(JNIEnv *env, jstring jproperty, mpv_format format, void *value) { 52 | CHECK_MPV_INIT(); 53 | 54 | const char *prop = env->GetStringUTFChars(jproperty, NULL); 55 | int result = mpv_set_property(g_mpv, prop, format, value); 56 | if (result < 0) 57 | ALOGE("mpv_set_property(%s, %p) format %d returned error %s", prop, value, format, mpv_error_string(result)); 58 | env->ReleaseStringUTFChars(jproperty, prop); 59 | 60 | return result; 61 | } 62 | 63 | jni_func(jobject, getPropertyInt, jstring jproperty) { 64 | int64_t value = 0; 65 | if (common_get_property(env, jproperty, MPV_FORMAT_INT64, &value) < 0) 66 | return NULL; 67 | return env->NewObject(java_Integer, java_Integer_init, (jint)value); 68 | } 69 | 70 | jni_func(jobject, getPropertyDouble, jstring jproperty) { 71 | double value = 0; 72 | if (common_get_property(env, jproperty, MPV_FORMAT_DOUBLE, &value) < 0) 73 | return NULL; 74 | return env->NewObject(java_Double, java_Double_init, (jdouble)value); 75 | } 76 | 77 | jni_func(jobject, getPropertyBoolean, jstring jproperty) { 78 | int value = 0; 79 | if (common_get_property(env, jproperty, MPV_FORMAT_FLAG, &value) < 0) 80 | return NULL; 81 | return env->NewObject(java_Boolean, java_Boolean_init, (jboolean)value); 82 | } 83 | 84 | jni_func(jstring, getPropertyString, jstring jproperty) { 85 | char *value; 86 | if (common_get_property(env, jproperty, MPV_FORMAT_STRING, &value) < 0) 87 | return NULL; 88 | jstring jvalue = env->NewStringUTF(value); 89 | mpv_free(value); 90 | return jvalue; 91 | } 92 | 93 | jni_func(void, setPropertyInt, jstring jproperty, jint jvalue) { 94 | int64_t value = static_cast(jvalue); 95 | common_set_property(env, jproperty, MPV_FORMAT_INT64, &value); 96 | } 97 | 98 | jni_func(void, setPropertyDouble, jstring jproperty, jdouble jvalue) { 99 | double value = static_cast(jvalue); 100 | common_set_property(env, jproperty, MPV_FORMAT_DOUBLE, &value); 101 | } 102 | 103 | jni_func(void, setPropertyBoolean, jstring jproperty, jboolean jvalue) { 104 | int value = jvalue == JNI_TRUE ? 1 : 0; 105 | common_set_property(env, jproperty, MPV_FORMAT_FLAG, &value); 106 | } 107 | 108 | jni_func(void, setPropertyString, jstring jproperty, jstring jvalue) { 109 | const char *value = env->GetStringUTFChars(jvalue, NULL); 110 | common_set_property(env, jproperty, MPV_FORMAT_STRING, &value); 111 | env->ReleaseStringUTFChars(jvalue, value); 112 | } 113 | 114 | jni_func(void, observeProperty, jstring property, jint format) { 115 | CHECK_MPV_INIT(); 116 | const char *prop = env->GetStringUTFChars(property, NULL); 117 | int result = mpv_observe_property(g_mpv, 0, prop, (mpv_format)format); 118 | if (result < 0) 119 | ALOGE("mpv_observe_property(%s) format %d returned error %s", prop, format, mpv_error_string(result)); 120 | env->ReleaseStringUTFChars(property, prop); 121 | } 122 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/render.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "jni_utils.h" 6 | #include "log.h" 7 | #include "globals.h" 8 | 9 | extern "C" { 10 | jni_func(void, attachSurface, jobject surface_); 11 | jni_func(void, detachSurface); 12 | }; 13 | 14 | static jobject surface; 15 | 16 | jni_func(void, attachSurface, jobject surface_) { 17 | CHECK_MPV_INIT(); 18 | 19 | surface = env->NewGlobalRef(surface_); 20 | if (!surface) 21 | die("invalid surface provided"); 22 | int64_t wid = reinterpret_cast(surface); 23 | int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid); 24 | if (result < 0) 25 | ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result)); 26 | } 27 | 28 | jni_func(void, detachSurface) { 29 | CHECK_MPV_INIT(); 30 | 31 | int64_t wid = 0; 32 | int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid); 33 | if (result < 0) 34 | ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result)); 35 | 36 | env->DeleteGlobalRef(surface); 37 | surface = NULL; 38 | } 39 | -------------------------------------------------------------------------------- /libmpv/src/main/java/dev/jdtech/mpv/MPVLib.kt: -------------------------------------------------------------------------------- 1 | package dev.jdtech.mpv 2 | 3 | import android.content.Context 4 | import android.view.Surface 5 | 6 | // Wrapper for native library 7 | 8 | @Suppress("unused") 9 | object MPVLib { 10 | init { 11 | val libs = arrayOf("mpv", "player") 12 | for (lib in libs) { 13 | System.loadLibrary(lib) 14 | } 15 | } 16 | 17 | external fun create(appctx: Context) 18 | external fun init() 19 | external fun destroy() 20 | external fun attachSurface(surface: Surface) 21 | external fun detachSurface() 22 | 23 | external fun command(cmd: Array) 24 | 25 | external fun setOptionString(name: String, value: String): Int 26 | 27 | external fun getPropertyInt(property: String): Int? 28 | external fun setPropertyInt(property: String, value: Int) 29 | external fun getPropertyDouble(property: String): Double? 30 | external fun setPropertyDouble(property: String, value: Double) 31 | external fun getPropertyBoolean(property: String): Boolean? 32 | external fun setPropertyBoolean(property: String, value: Boolean) 33 | external fun getPropertyString(property: String): String? 34 | external fun setPropertyString(property: String, value: String) 35 | 36 | external fun observeProperty(property: String, format: Int) 37 | 38 | private val observers = mutableListOf() 39 | 40 | @JvmStatic 41 | fun addObserver(o: EventObserver) { 42 | synchronized(observers) { 43 | observers.add(o) 44 | } 45 | } 46 | 47 | @JvmStatic 48 | fun removeObserver(o: EventObserver) { 49 | synchronized(observers) { 50 | observers.remove(o) 51 | } 52 | } 53 | 54 | @JvmStatic 55 | fun eventProperty(property: String, value: Long) { 56 | synchronized(observers) { 57 | for (o in observers) 58 | o.eventProperty(property, value) 59 | } 60 | } 61 | 62 | @JvmStatic 63 | fun eventProperty(property: String, value: Double) { 64 | synchronized(observers) { 65 | for (o in observers) 66 | o.eventProperty(property, value) 67 | } 68 | } 69 | 70 | @JvmStatic 71 | fun eventProperty(property: String, value: Boolean) { 72 | synchronized(observers) { 73 | for (o in observers) 74 | o.eventProperty(property, value) 75 | } 76 | } 77 | 78 | @JvmStatic 79 | fun eventProperty(property: String, value: String) { 80 | synchronized(observers) { 81 | for (o in observers) 82 | o.eventProperty(property, value) 83 | } 84 | } 85 | 86 | @JvmStatic 87 | fun eventProperty(property: String) { 88 | synchronized(observers) { 89 | for (o in observers) 90 | o.eventProperty(property) 91 | } 92 | } 93 | 94 | @JvmStatic 95 | fun event(eventId: Int) { 96 | synchronized(observers) { 97 | for (o in observers) 98 | o.event(eventId) 99 | } 100 | } 101 | 102 | private val log_observers = mutableListOf() 103 | 104 | @JvmStatic 105 | fun addLogObserver(o: LogObserver) { 106 | synchronized(log_observers) { 107 | log_observers.add(o) 108 | } 109 | } 110 | 111 | @JvmStatic 112 | fun removeLogObserver(o: LogObserver) { 113 | synchronized(log_observers) { 114 | log_observers.remove(o) 115 | } 116 | } 117 | 118 | @JvmStatic 119 | fun logMessage(prefix: String, level: Int, text: String) { 120 | synchronized(log_observers) { 121 | for (o in log_observers) 122 | o.logMessage(prefix, level, text) 123 | } 124 | } 125 | 126 | interface EventObserver { 127 | fun eventProperty(property: String) 128 | fun eventProperty(property: String, value: Long) 129 | fun eventProperty(property: String, value: Double) 130 | fun eventProperty(property: String, value: Boolean) 131 | fun eventProperty(property: String, value: String) 132 | fun event(eventId: Int) 133 | } 134 | 135 | interface LogObserver { 136 | fun logMessage(prefix: String, level: Int, text: String) 137 | } 138 | 139 | object MpvFormat { 140 | const val MPV_FORMAT_NONE: Int = 0 141 | const val MPV_FORMAT_STRING: Int = 1 142 | const val MPV_FORMAT_OSD_STRING: Int = 2 143 | const val MPV_FORMAT_FLAG: Int = 3 144 | const val MPV_FORMAT_INT64: Int = 4 145 | const val MPV_FORMAT_DOUBLE: Int = 5 146 | const val MPV_FORMAT_NODE: Int = 6 147 | const val MPV_FORMAT_NODE_ARRAY: Int = 7 148 | const val MPV_FORMAT_NODE_MAP: Int = 8 149 | const val MPV_FORMAT_BYTE_ARRAY: Int = 9 150 | } 151 | 152 | object MpvEvent { 153 | const val MPV_EVENT_NONE: Int = 0 154 | const val MPV_EVENT_SHUTDOWN: Int = 1 155 | const val MPV_EVENT_LOG_MESSAGE: Int = 2 156 | const val MPV_EVENT_GET_PROPERTY_REPLY: Int = 3 157 | const val MPV_EVENT_SET_PROPERTY_REPLY: Int = 4 158 | const val MPV_EVENT_COMMAND_REPLY: Int = 5 159 | const val MPV_EVENT_START_FILE: Int = 6 160 | const val MPV_EVENT_END_FILE: Int = 7 161 | const val MPV_EVENT_FILE_LOADED: Int = 8 162 | @Deprecated("") 163 | const val MPV_EVENT_IDLE: Int = 11 164 | @Deprecated("") 165 | const val MPV_EVENT_TICK: Int = 14 166 | const val MPV_EVENT_CLIENT_MESSAGE: Int = 16 167 | const val MPV_EVENT_VIDEO_RECONFIG: Int = 17 168 | const val MPV_EVENT_AUDIO_RECONFIG: Int = 18 169 | const val MPV_EVENT_SEEK: Int = 20 170 | const val MPV_EVENT_PLAYBACK_RESTART: Int = 21 171 | const val MPV_EVENT_PROPERTY_CHANGE: Int = 22 172 | const val MPV_EVENT_QUEUE_OVERFLOW: Int = 24 173 | const val MPV_EVENT_HOOK: Int = 25 174 | } 175 | 176 | object MpvLogLevel { 177 | const val MPV_LOG_LEVEL_NONE: Int = 0 178 | const val MPV_LOG_LEVEL_FATAL: Int = 10 179 | const val MPV_LOG_LEVEL_ERROR: Int = 20 180 | const val MPV_LOG_LEVEL_WARN: Int = 30 181 | const val MPV_LOG_LEVEL_INFO: Int = 40 182 | const val MPV_LOG_LEVEL_V: Int = 50 183 | const val MPV_LOG_LEVEL_DEBUG: Int = 60 184 | const val MPV_LOG_LEVEL_TRACE: Int = 70 185 | } 186 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include(":libmpv") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | mavenCentral() 7 | google() 8 | } 9 | } --------------------------------------------------------------------------------