Compare commits

...

88 Commits

Author SHA1 Message Date
Taner Şener 7a9c62f556 Update FUNDING.yml 2021-10-07 09:42:47 +01:00
Taner Şener df463626f1 Update README.md 2021-04-16 17:58:12 +01:00
Taner Şener 94607015d9 Update README.md 2021-04-16 17:55:12 +01:00
Taner Şener 7710133dcf Add the link of medium article to the README 2021-04-16 17:54:43 +01:00
Taner Sener b0c3d5bb27 announce the retirement 2021-04-13 20:19:53 +01:00
Taner Sener 0dbbc5e11f Add mavenCentral() links 2021-02-07 20:51:34 +00:00
Taner Sener 195a809cca fix android async example 2020-11-03 22:24:10 +00:00
Taner Sener 0aae541e81 support iOS 14, fixes #553 2020-09-26 15:31:46 +01:00
Pete dda06154f1 Update arch-common.sh (#534)
* Update arch-common.sh

x265 download on Bitbucket no longer works. Use GitHub link instead
2020-08-24 13:50:51 +01:00
Taner Sener 725aef963c update the packaging for android arm-v7a and arm-v7a-neon architectures and remove custom abi rules for releases, fixes #511 2020-08-03 19:34:53 +01:00
Taner Sener 94adca685e fix proguard rules, fixes #508 2020-08-03 19:34:45 +01:00
Taner Şener 4f6e484d5b merge v4.4 to master (#503)
* cleanup openh264 build

* apply 'use build host and target host in build scripts' efe1c59a

* remove custom common_includes

The includes are handled by llvm via --sysroot parameter.

* remove unnecessary parameters for make command

using the names of compiler tools same way as they are used (without full path) for ffmpeg build

* let AndroidStudio manage project root

* import cpufeatures from NDK

* Enable stale action

* Use JSON parsing in getMediaInformation, fixes #417

* Remove internal log line limit on Android, fixes #418

* add support for vo-amrwbenc, fixes #381

* Enable all gpl libraries when both --full and --enable-gpl flags are present, fixes #394

* let the project be opened and managed in Android Studio

* bring in cpu_features as a submodule

* add cpu_features script

and some helper fuctions to android-common

* fix lint errors

* use cpu_features script

it's a regular library, only it's enabled by default

* import cpu_features into Android.mk

also clean up the Android.mk files a bit
also, import libc++_shared correctly, so that it is packed in AAR

* fix Android.lts.mk

* with NDK r20 and higher, openh264.a does not depend on libc++

* Revert "with NDK r20 and higher, openh264.a does not depend on libc++"

This reverts commit dc751016

* Revert a (small) portion of commit e77f8971

On lower API, openh264 depends on libc++_shared. Here, for simplicity, we force this dependency for all API levels.

* LTS: fix the static constructor blocks to handle the changed name of the cpu features library

Also, add ffmpeg dependencies for lower APIs (how did it work before?)
Also, just try to load c++_shared if it is present, not trying to guess whether the external libraries require it (android.sh will still make the decision to add c++_shared to dependencies list based on the list of external libraries).

* switch the submodule to fork

* rename the submodule library

* scripts changed for renamed libary and move cpu-features to slot 47

create_cpufeatures_package_config moved back to android-common.sh
android_ndk_cmake to handle generic Android-NDK toolchain invocation of cmake

* update the Android.mk files to reflect cpu-features rename

also, remove unnecessary dependencies

* fix for LTS

* not needed anymore

* revert the commit 740fd2e9

load of c++_shared determined by the list of enabled external libraries

* Using static lib for Google cpu-features

* let openh264 work with external cpu-features

compatible with NDK deperecated implementation and with ndk_compat from https://github.com/google/cpu_features

* set packages path

* make android-cpu-features.sh executable

* automatically update submodules from android.sh

* disable publishing to bintray

* bump version

* list cpu-features after builtin libraries

* fix javadoc warnings

* disable stripping of debug symbols for ffmpeg, fixes #447

* do not detach main thread on error cases, fixes #446

* release unreleased utf strings in setNativeEnvironmentVariable(), fixes #443

* implement toString() method for Statistics and LogMessage classes, fixes #438

* do not install lldb for android in travis builds

* fix travis builds

* remove unused test resources

* switch src/cpu-features remote to google/cpu_features

* switch expat source code with expat submodule

* update submodules in top level build scripts

* switch openh264 source code with openh264 submodule

* implement bash functions to download external libraries

* implement ignoreSignal api method, fixes #258, #214

* use the latest android ndk

* disable avfoundation device for ios lts releases, fixes #462

* update ios test-app versions

* implement an api method to set environment variables, fixes #466

* update release scripts to use the latest ndk version

* update stale.yml

* implement ffmpeg async api

* add executionId to log and statistic callbacks

* fix ndk version in travis

* fix test commands in android test app

* fix android env variables  in travis

* switch to personal fork for cpu-features, use in-repo sources for expat and openh264

* introduce an api method to list ongoing executions

* use a thread safe implementation to list ongoing executions

* add startTime to FFmpegExecution class

* test openh264 in ios/tvos test applications

* use new x265 version v3.4

* use new x264 version v20200630-stable

* use new chromaprint version v1.5.0

* use new wavpack version v5.3.0

* use new kvazaar version v2.0.0

* use new libvorbis version v1.3.7

* use new nettle version v3.6

* use new libjpeg-turbo version v2.0.5

* use new ffmpeg version v4.4-dev-416-g1998d1d6af

* remove coreimage references on ios/tvos, fixes #480

* use new freetype version 2.10.2

* enable redownloading of ffmpeg source code

* freeze v4.4

* disable building of audiotoolbox output device on ios/tvos

* do not process log lines with zero length on ios/tvos

* update libsamplerate build workarounds

* do not print parsing exceptions for not found keys on android

* synchronize updating lastCommandOutput on ios/tvos, fixes #494

* introduce execution specific log levels, fixes #495

* fix x265 compilation errors on ios/tvos

* fix android lts neon packaging

* fix processing of ios/tvos flags

* fix mobile-ffmpeg videotoolbox flags for tvos

* release v4.4.LTS

* use v4.4.LTS in test applications

* fix ios/tvos system library indexes in ffmpeg build scripts

* update ios test-app settings

* fix android release packaging script

* Update README

Co-authored-by: alexcohn <sasha.cohn@gmail.com>
2020-07-29 21:28:04 +01:00
Taner Sener 8bc3165083 Update stale.yml 2020-05-09 01:22:59 +01:00
Taner Sener 6d3cdcf35f Enable stale action 2020-05-08 14:27:59 +01:00
Taner Sener fc032eb014 Merge pull request #408 from tanersener/development
Development
2020-04-30 01:02:30 +01:00
Taner Sener d09b4378e7 add git check in top level build scripts 2020-04-30 01:01:25 +01:00
Taner Sener 4ad203d8ef update ios assets for dependencies 2020-04-18 00:42:28 +01:00
Taner Sener 72cd70e60b update android.sh desription 2020-04-17 00:44:00 +01:00
Taner Sener 904e9def33 Merge pull request #385 from tanersener/development
merge project page changes
2020-04-17 00:15:17 +01:00
Taner Sener 6cf3de77fb update project page 2020-04-17 00:13:56 +01:00
Taner Sener 2169d5af99 merge v4.3.2 2020-04-16 23:51:07 +01:00
Taner Sener 90a30b68e2 Merge branch 'master' into development 2020-04-16 23:45:55 +01:00
Taner Sener 5b3816b121 update README 2020-04-16 23:20:57 +01:00
Taner Sener c373dc32e0 update build numbers for ios/tvos test apps 2020-04-16 12:33:51 +01:00
Taner Sener c679de1af7 fix ios/tvos main release scripts 2020-04-16 12:33:51 +01:00
Taner Sener bdcee571bb use v4.3.2 in android test-app 2020-04-15 23:15:07 +01:00
Taner Sener 00233e707d fix gnutls build errors 2020-04-15 00:34:56 +01:00
Taner Sener c658b6af61 patch avfoundation.m for ios 2020-04-14 23:12:48 +01:00
Taner Sener 740a290a71 freeze v4.3.2 2020-04-14 21:44:22 +01:00
Taner Sener 9627fe43c3 set a fixed NDK version in build.gradle 2020-04-14 20:35:38 +01:00
Taner Sener df6dd51b7d use new ffmpeg version v4.3-dev-2955-g19a16330f4 2020-04-13 22:04:51 +01:00
Taner Sener 6b8f83b9ff use new x264 version v20200409-stable 2020-04-13 21:51:15 +01:00
Taner Sener 50c91e58be use new x265 version v3.3 2020-04-13 21:44:11 +01:00
Taner Sener 14ffa7ee35 use new gmp version v6.2.0 2020-04-13 21:39:32 +01:00
Taner Sener 23948da2de use new gnutls version v3.6.13 2020-04-13 21:31:59 +01:00
Taner Sener 56621fbffb use new openh264 version v2.1.0 2020-04-13 21:26:43 +01:00
Taner Sener 3c398cb8a0 use new snappy version v1.1.8 2020-04-13 21:17:30 +01:00
Taner Sener dac231cad3 use new fribidi version v1.0.9 2020-04-13 21:11:36 +01:00
Taner Sener 080906ea0d depend on smart-exception-java on android test-app 2020-04-13 19:38:26 +01:00
Taner Sener 13fed8c537 specify ndk version in travis.yml 2020-04-13 16:08:54 +01:00
Taner Sener 105126acef decrease travis build duration 2020-04-13 15:22:25 +01:00
Taner Sener 99dc774b34 set issue tracker url in bintray 2020-04-12 18:44:49 +01:00
Taner Sener 554e714b37 update deprecated gradle properties 2020-04-11 01:08:56 +01:00
Taner Sener 10a464220f update how hscale ios patch is applied for issue #370 2020-04-06 18:56:32 +01:00
Taner Sener 080bb47c28 revert ios patches before building android for openh264 2020-04-02 12:33:04 +01:00
Taner Sener c2b44c40eb enable video4linux2 devices on Android to support external usb cameras 2020-04-02 00:32:46 +01:00
Taner Sener 95be9fac95 update gradle plugin version 2020-04-01 23:40:30 +01:00
Taner Sener b1ca8f4841 disable hscale workaround for android, fixes #370 2020-04-01 23:32:27 +01:00
Alex Cohn 2dbfad6915 remove unnecessary parameters for make command
using the names of compiler tools same way as they are used (without full path) for ffmpeg build
2020-04-01 23:23:26 +01:00
Alex Cohn 5a7fba3574 remove custom common_includes
The includes are handled by llvm via --sysroot parameter.
2020-04-01 23:23:26 +01:00
alexcohn b84faa479d apply 'use build host and target host in build scripts' efe1c59a 2020-04-01 23:23:26 +01:00
alexcohn bdcd943c94 cleanup openh264 build 2020-04-01 23:23:26 +01:00
Taner Sener 915a8e1e2b update gradle plugin version 2020-03-29 23:50:36 +01:00
Taner Sener e29aa828d6 enable libwebp_anim encoder, fixes #366 2020-03-29 23:50:23 +01:00
Taner Sener 99a6f18f5f add create xcframework bundles option to ios.sh, fixes #351 2020-03-29 18:56:24 +01:00
Taner Sener 8af793fccb fix parsing of unicode parameters on ios/tvos, fixes #360 2020-03-09 20:30:25 +00:00
Taner Sener e5f9f72038 update rubber band build flags 2020-03-02 23:23:26 +00:00
Taner Sener 0baff685d7 support rubber band external library 2020-03-01 20:05:10 +00:00
Taner Sener 6d5cb88d0a use system uuid library for tvos 2020-02-29 00:41:17 +00:00
Taner Sener 6fc1ed06c6 refactor system package function names in build scripts 2020-02-28 23:17:20 +00:00
Taner Sener 4c86999458 refactor ARCH_OPTIONS in build scripts 2020-02-28 23:11:48 +00:00
Taner Sener 95261c1ade update libaom .gitignore 2020-02-28 22:03:22 +00:00
Taner Sener ca0bb9fd0f fix libaom linking error on mac catalyst 2020-02-28 20:07:21 +00:00
Taner Sener 12f52514b8 fix sdl linking error on mac catalyst 2020-02-28 07:47:33 +00:00
Taner Sener 64488a818a disable asm for external libraries on mac catalyst 2020-02-25 22:52:55 +00:00
Taner Sener c7e7cc20ef fix mac catalyst build errors 2020-02-25 19:55:22 +00:00
Taner Sener efe1c59a20 use build host and target host in build scripts 2020-02-24 21:24:58 +00:00
Taner Sener 4605e778c5 update mac catalyst arch name 2020-02-24 00:29:20 +00:00
Taner Sener ec851d33a3 disable asm for ffmpeg on x86-64h 2020-02-23 19:21:24 +00:00
Taner Sener b5565bd359 disable asm for x265 on x86-64h 2020-02-23 18:37:29 +00:00
Taner Sener 9229cf50c4 use system uuid library for ios 2020-02-23 14:12:43 +00:00
Taner Sener 7cf14d21e4 add mac catalyst support 2020-02-23 09:22:16 +00:00
Taner Sener 091f96ae84 fix travis builds 2020-02-16 18:17:59 +00:00
Taner Sener a13a05725a fix travis ios builds 2020-02-16 18:17:59 +00:00
Taner Sener 0981ff571b update SIMD_OPTIONS as ARCH_OPTIONS 2020-02-16 18:17:59 +00:00
Taner Sener 4e92d550fd fix travis builds 2020-02-16 15:38:02 +00:00
Taner Sener 1157681271 fix travis ios builds 2020-02-15 23:18:23 +00:00
Taner Sener 78e5411df9 update SIMD_OPTIONS as ARCH_OPTIONS 2020-02-15 22:20:51 +00:00
Taner Sener 917c22e209 Merge pull request #329 from tanersener/development
merge v4.3.1 fixes
2020-01-26 12:22:52 +00:00
Taner Sener c708cbdedd merge development changes (#318)
* fix cancel operation

* update wiki images

* cleanup Android project

Co-authored-by: hannesa2 <hannes.achleitner@googlemail.com>
2020-01-16 21:11:50 +00:00
Taner Sener e34f08c539 fix cancel operation 2020-01-16 00:59:41 +00:00
Taner Sener 00978be936 merge v4.3.1 release 2020-01-14 00:30:27 +00:00
Taner Sener 18a8f6478f fix tvos pod name in README, fixes #276 2019-11-25 20:30:28 +00:00
Taner Sener 6e5af90e9c remove old patreon backers 2019-11-16 16:11:38 +00:00
jess b26f87d292 Activating Open Collective (#239)
* Added financial contributors to the README

* Update README.md

increase level and add numbers
2019-11-16 16:08:36 +00:00
Taner Sener a10996cf7e merge v4.3 fixes (#263)
* generate ios documentation

* update project page

* improve getNativeLastCommandOutput checks
2019-10-28 09:24:45 +00:00
Taner Sener 31d413d980 merge documentation updates (#262)
* generate ios documentation

* update project page
2019-10-27 15:48:12 +00:00
Taner Sener 92eb33e0b5 Merge pull request #261 from tanersener/development
merge v4.3
2019-10-27 15:14:40 +00:00
14721 changed files with 733126 additions and 1868932 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
open_collective: mobile-ffmpeg
patreon: tanersener
custom: ['https://buymeacoff.ee/tanersener','https://paypal.me/teodosiyminchev']
open_collective: mobile-ffmpeg
custom: ['https://buymeacoff.ee/tanersener']
+19
View File
@@ -0,0 +1,19 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'
+73 -35
View File
@@ -2,36 +2,32 @@ branches:
only:
- master
- development
sudo: false
git:
quiet: true
addons:
apt:
packages:
- autoconf
- automake
- libtool
- pkg-config
- curl
- git
- cmake
- gcc
- gperf
- texinfo
- yasm
- nasm
- bison
- autogen
- patch
homebrew:
packages:
- nasm
update: true
matrix:
depth: false
jobs:
include:
- name: "Android Main Build"
language: android
os: linux
dist : trusty
addons:
apt:
packages:
- autoconf
- automake
- libtool
- pkg-config
- curl
- git
- cmake
- gcc
- gperf
- texinfo
- yasm
- bison
- autogen
- patch
android:
components:
- tools
@@ -42,24 +38,42 @@ matrix:
- extra-google-m2repository
- extra-android-m2repository
install:
- echo y | sdkmanager "ndk-bundle"
- echo y | sdkmanager "ndk;21.3.6528147"
- echo y | sdkmanager "cmake;3.10.2.4988404"
- echo y | sdkmanager "lldb;3.1"
before_install:
- touch $HOME/.android/repositories.cfg
before_script:
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk-bundle
- export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
- rm -f ./build.log
- wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.gz;tar zxvf nasm-2.14.02.tar.gz;cd nasm-2.14.02;./configure;make;sudo make install;cd ..
after_success:
- grep -e INFO ./build.log | grep build
after_failure:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./android.sh --no-output-redirection
- bash ./android.sh --no-output-redirection -d
- name: "Android LTS Build"
language: android
os: linux
dist : trusty
addons:
apt:
packages:
- autoconf
- automake
- libtool
- pkg-config
- curl
- git
- cmake
- gcc
- gperf
- texinfo
- yasm
- bison
- autogen
- patch
android:
components:
- tools
@@ -71,24 +85,30 @@ matrix:
- extra-google-m2repository
- extra-android-m2repository
install:
- echo y | sdkmanager "ndk-bundle"
- echo y | sdkmanager "ndk;21.3.6528147"
- echo y | sdkmanager "cmake;3.10.2.4988404"
- echo y | sdkmanager "lldb;3.1"
before_install:
- touch $HOME/.android/repositories.cfg
before_script:
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk-bundle
- export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
- rm -f ./build.log
- wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.gz;tar zxvf nasm-2.14.02.tar.gz;cd nasm-2.14.02;./configure;make;sudo make install;cd ..
after_success:
- grep -e INFO ./build.log | grep build
after_failure:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./android.sh --lts --no-output-redirection
- bash ./android.sh --lts --no-output-redirection -d
- name: "iOS Main Build"
language: objective-c
os: osx
osx_image: xcode10.2
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -97,10 +117,16 @@ matrix:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./ios.sh --no-output-redirection
- bash ./ios.sh --no-output-redirection --disable-arm64e
- name: "iOS LTS Build"
language: objective-c
osx_image: xcode7.3
os: osx
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -109,10 +135,16 @@ matrix:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./ios.sh --lts --no-output-redirection
- bash ./ios.sh --lts --no-output-redirection --disable-armv7 --disable-armv7s --disable-i386
- name: "tvOS Main Build"
language: objective-c
os: osx
osx_image: xcode10.2
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -124,7 +156,13 @@ matrix:
- bash ./tvos.sh --no-output-redirection
- name: "tvOS LTS Build"
language: objective-c
os: osx
osx_image: xcode7.3
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -133,4 +171,4 @@ matrix:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./tvos.sh --lts --no-output-redirection
- bash ./tvos.sh --lts --no-output-redirection
+203 -94
View File
@@ -1,29 +1,31 @@
# MobileFFmpeg [![Financial Contributors on Open Collective](https://opencollective.com/mobile-ffmpeg/all/badge.svg?label=financial+contributors)](https://opencollective.com/mobile-ffmpeg) ![GitHub release](https://img.shields.io/badge/release-v4.3.1-blue.svg) ![Bintray](https://img.shields.io/badge/bintray-v4.3.1-blue.svg) ![CocoaPods](https://img.shields.io/badge/pod-v4.3.1-blue.svg) [![Build Status](https://travis-ci.org/tanersener/mobile-ffmpeg.svg?branch=master)](https://travis-ci.org/tanersener/mobile-ffmpeg)
# MobileFFmpeg [![Financial Contributors on Open Collective](https://opencollective.com/mobile-ffmpeg/all/badge.svg?label=financial+contributors)](https://opencollective.com/mobile-ffmpeg) ![GitHub release](https://img.shields.io/badge/release-v4.4-blue.svg) ![Maven Central](https://img.shields.io/maven-central/v/com.arthenica/mobile-ffmpeg-min) ![CocoaPods](https://img.shields.io/badge/pod-v4.4-blue.svg) [![Build Status](https://travis-ci.org/tanersener/mobile-ffmpeg.svg?branch=master)](https://travis-ci.org/tanersener/mobile-ffmpeg)
FFmpeg for Android, iOS and tvOS
FFmpeg for Android, iOS and tvOS.
Not maintained anymore as explained in [Whats next for MobileFFmpeg?](https://tanersener.medium.com/whats-next-for-mobileffmpeg-44d2fac6f09b). Superseded by [FFmpegKit](https://github.com/tanersener/ffmpeg-kit).
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/master/docs/assets/mobile-ffmpeg-logo-v7.png" width="320">
### 1. Features
- Includes both `FFmpeg` and `FFprobe`
- Use binaries available at `Github`/`JCenter`/`CocoaPods` or build your own version with external libraries you need
- Use binaries available at `Github`/`Maven Central`/`CocoaPods` or build your own version with external libraries you need
- Supports
- Android, iOS and tvOS
- FFmpeg `v3.4.x`, `v4.0.x`, `v4.1`, `v4.2` and `v4.3-dev` releases
- 28 external libraries
- FFmpeg `v3.4.x`, `v4.0.x`, `v4.1`, `v4.2` , `v4.3` and `v4.4-dev` releases
- 29 external libraries
`chromaprint`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `openh264`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `twolame`, `wavpack`
`chromaprint`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `openh264`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `twolame`, `vo-amrwbenc`, `wavpack`
- 4 external libraries with GPL license
- 5 external libraries with GPL license
`vid.stab`, `x264`, `x265`, `xvidcore`
`rubberband`, `vid.stab`, `x264`, `x265`, `xvidcore`
- Concurrent execution
- Exposes both FFmpeg library and MobileFFmpeg wrapper library capabilities
- Includes cross-compile instructions for 44 open-source libraries
- Includes cross-compile instructions for 47 open-source libraries
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `leptonica`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `openh264`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `tiff`, `twolame`, `vid.stab`, `wavpack`, `x264`, `x265`, `xvidcore`
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `leptonica`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libsamplerate`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `openh264`, `opus`, `rubberband`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `tiff`, `twolame`, `vid.stab`, `vo-amrwbenc`, `wavpack`, `x264`, `x265`, `xvidcore`
- Licensed under LGPL 3.0, can be customized to support GPL v3.0
@@ -36,26 +38,29 @@ FFmpeg for Android, iOS and tvOS
- Supports `API Level 16+`
#### 1.2 iOS
- Builds `armv7`, `armv7s`, `arm64`, `arm64e`, `i386` and `x86_64` architectures
- Supports `bzip2`, `zlib`, `iconv` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` system frameworks
- Builds `armv7`, `armv7s`, `arm64`, `arm64e`, `i386`, `x86_64` and `x86_64` (Mac Catalyst) architectures
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox`, `AVFoundation` system frameworks
- Objective-C API
- Camera access
- `ARC` enabled library
- Built with `-fembed-bitcode` flag
- Creates static framework and static universal (fat) library (.a)
- Creates static frameworks, static xcframeworks and static universal (fat) libraries (.a)
- Supports `iOS SDK 9.3` or later
#### 1.3 tvOS
- Builds `arm64` and `x86_64` architectures
- Supports `bzip2`, `zlib`, `iconv` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox` system frameworks
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox` system frameworks
- Objective-C API
- `ARC` enabled library
- Built with `-fembed-bitcode` flag
- Creates static framework and static universal (fat) library (.a)
- Creates static frameworks and static universal (fat) libraries (.a)
- Supports `tvOS SDK 9.2` or later
### 2. Using
Published binaries are available at [Github](https://github.com/tanersener/mobile-ffmpeg/releases), [JCenter](https://bintray.com/bintray/jcenter) and [CocoaPods](https://cocoapods.org).
Prebuilt binaries are available at [Github](https://github.com/tanersener/mobile-ffmpeg/releases), [Maven Central](https://repo1.maven.org/maven2) and [CocoaPods](https://cocoapods.org).
#### 2.1 Packages
There are eight different `mobile-ffmpeg` packages. Below you can see which system libraries and external libraries are enabled in each of them.
@@ -65,14 +70,14 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
<thead>
<tr>
<th align="center"></th>
<th align="center">min</th>
<th align="center">min-gpl</th>
<th align="center">https</th>
<th align="center">https-gpl</th>
<th align="center">audio</th>
<th align="center">video</th>
<th align="center">full</th>
<th align="center">full-gpl</th>
<th align="center"><sup>min</sup></th>
<th align="center"><sup>min-gpl</sup></th>
<th align="center"><sup>https</sup></th>
<th align="center"><sup>https-gpl</sup></th>
<th align="center"><sup>audio</sup></th>
<th align="center"><sup>video</sup></th>
<th align="center"><sup>full</sup></th>
<th align="center"><sup>full-gpl</sup></th>
</tr>
</thead>
<tbody>
@@ -82,10 +87,10 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
<td align="center"><sup>vid.stab</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
<td align="center"><sup>gmp</sup><br><sup>gnutls</sup></td>
<td align="center"><sup>gmp</sup><br><sup>gnutls</sup><br><sup>vid.stab</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
<td align="center"><sup>lame</sup><br><sup>libilbc</sup><br><sup>libvorbis</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>lame</sup><br><sup>libilbc</sup><br><sup>libvorbis</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vo-amrwbenc</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>kvazaar</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libtheora</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>snappy</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vid.stab</sup><br><sup>wavpack</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vo-amrwbenc</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vid.stab</sup><br><sup>vo-amrwbenc</sup><br><sup>wavpack</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
</tr>
<tr>
<td align="center"><sup>android system libraries</sup></td>
@@ -93,11 +98,11 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
</tr>
<tr>
<td align="center"><sup>ios system libraries</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>AVFoundation</sup><br><sup>CoreImage</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>AVFoundation</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
</tr>
<tr>
<td align="center"><sup>tvos system libraries</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>CoreImage</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
</tr>
</tbody>
</table>
@@ -108,7 +113,7 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
- `chromaprint`, `vid.stab` and `x265` are supported since `v2.1`
- `sdl`, `tesseract`, `twolame` external libraries; `zlib`, `MediaCodec` Android system libraries; `bzip2`, `zlib` iOS system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` iOS system frameworks are supported since `v3.0`
- `sdl`, `tesseract`, `twolame` external libraries; `zlib`, `MediaCodec` Android system libraries; `bzip2`, `zlib` iOS system libraries and `AudioToolbox`, `VideoToolbox`, `AVFoundation` iOS system frameworks are supported since `v3.0`
- Since `v4.2`, `chromaprint`, `sdl` and `tesseract` libraries are not included in binary releases. You can still build them and include in your releases
@@ -116,15 +121,26 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
- Since `v4.3.1`, `iOS` and `tvOS` releases started to use `iconv` system library instead of `iconv` external library
#### 2.1 Android
1. Add MobileFFmpeg dependency to your `build.gradle` in `mobile-ffmpeg-<package name>` format
- `vo-amrwbenc` is supported since `v4.4`
#### 2.2 Android
- For versions `4.4` and `4.4.LTS`, add `mavenCentral()` to your `build.gradle` and make sure that it is listed
before `jcenter()`
- For `4.3.2` and older releases, add `jcenter()`
```
dependencies {
implementation 'com.arthenica:mobile-ffmpeg-full:4.3.1'
repositories {
mavenCentral()
}
```
2. Execute FFmpeg commands.
1. Add MobileFFmpeg dependency to your `build.gradle` in `mobile-ffmpeg-<package name>` pattern.
```
dependencies {
implementation 'com.arthenica:mobile-ffmpeg-full:4.4'
}
```
2. Execute synchronous FFmpeg commands.
```
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFmpeg;
@@ -141,7 +157,27 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
```
3. Execute FFprobe commands.
3. Execute asynchronous FFmpeg commands.
```
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFmpeg;
long executionId = FFmpeg.executeAsync("-i file1.mp4 -c:v mpeg4 file2.mp4", new ExecuteCallback() {
@Override
public void apply(final long executionId, final int returnCode) {
if (returnCode == RETURN_CODE_SUCCESS) {
Log.i(Config.TAG, "Async command execution completed successfully.");
} else if (returnCode == RETURN_CODE_CANCEL) {
Log.i(Config.TAG, "Async command execution cancelled by user.");
} else {
Log.i(Config.TAG, String.format("Async command execution failed with returnCode=%d.", returnCode));
}
}
});
```
4. Execute FFprobe commands.
```
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFprobe;
@@ -156,7 +192,7 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
```
4. Check execution output later.
5. Check execution output later.
```
int rc = Config.getLastReturnCode();
@@ -170,26 +206,26 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
```
5. Stop an ongoing FFmpeg operation.
```
FFmpeg.cancel();
```
6. Stop ongoing FFmpeg operations.
- Stop all executions
```
FFmpeg.cancel();
```
- Stop a specific execution
```
FFmpeg.cancel(executionId);
```
6. Get media information for a file.
7. Get media information for a file.
```
MediaInformation info = FFprobe.getMediaInformation("<file path or uri>");
```
7. Record video using Android camera.
8. Record video using Android camera.
```
FFmpeg.execute("-f android_camera -i 0:0 -r 30 -pixel_format bgr0 -t 00:00:05 <record file path>");
```
8. List enabled external libraries.
```
List<String> externalLibraries = Config.getExternalLibraries();
```
9. Enable log callback.
```
Config.enableLogCallback(new LogCallback() {
@@ -207,31 +243,44 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
});
```
11. Ignore the handling of a signal.
```
Config.ignoreSignal(Signal.SIGXCPU);
```
11. Set log level.
12. List ongoing executions.
```
final List<FFmpegExecution> ffmpegExecutions = FFmpeg.listExecutions();
for (int i = 0; i < ffmpegExecutions.size(); i++) {
FFmpegExecution execution = ffmpegExecutions.get(i);
Log.d(TAG, String.format("Execution %d = id:%d, startTime:%s, command:%s.", i, execution.getExecutionId(), execution.getStartTime(), execution.getCommand()));
}
```
13. Set default log level.
```
Config.setLogLevel(Level.AV_LOG_FATAL);
```
12. Register custom fonts directory.
14. Register custom fonts directory.
```
Config.setFontDirectory(this, "<folder with fonts>", Collections.EMPTY_MAP);
```
#### 2.2 iOS / tvOS
1. Add MobileFFmpeg dependency to your `Podfile` in `mobile-ffmpeg-<package name>` format
#### 2.3 iOS / tvOS
1. Add MobileFFmpeg dependency to your `Podfile` in `mobile-ffmpeg-<package name>` pattern.
- iOS
```
pod 'mobile-ffmpeg-full', '~> 4.3.1'
pod 'mobile-ffmpeg-full', '~> 4.4'
```
- tvOS
```
pod 'mobile-ffmpeg-tvos-full', '~> 4.3.1'
pod 'mobile-ffmpeg-tvos-full', '~> 4.4'
```
2. Execute FFmpeg commands.
2. Execute synchronous FFmpeg commands.
```
#import <mobileffmpeg/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFmpeg.h>
@@ -246,8 +295,26 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
NSLog(@"Command execution failed with rc=%d and output=%@.\n", rc, [MobileFFmpegConfig getLastCommandOutput]);
}
```
3. Execute FFprobe commands.
3. Execute asynchronous FFmpeg commands.
```
#import <mobileffmpeg/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFmpeg.h>
long executionId = [MobileFFmpeg executeAsync:@"-i file1.mp4 -c:v mpeg4 file2.mp4" withCallback:self];
- (void)executeCallback:(long)executionId :(int)returnCode {
if (rc == RETURN_CODE_SUCCESS) {
NSLog(@"Async command execution completed successfully.\n");
} else if (rc == RETURN_CODE_CANCEL) {
NSLog(@"Async command execution cancelled by user.\n");
} else {
NSLog(@"Async command execution failed with rc=%d.\n", rc);
}
}
```
4. Execute FFprobe commands.
```
#import <mobileffmpeg/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFprobe.h>
@@ -263,7 +330,7 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
```
4. Check execution output later.
5. Check execution output later.
```
int rc = [MobileFFmpegConfig getLastReturnCode];
NSString *output = [MobileFFmpegConfig getLastCommandOutput];
@@ -277,32 +344,33 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
```
5. Stop an ongoing FFmpeg operation.
```
[MobileFFmpeg cancel];
```
6. Stop ongoing FFmpeg operations.
- Stop all executions
```
[MobileFFmpeg cancel];
6. Get media information for a file.
```
- Stop a specific execution
```
[MobileFFmpeg cancel:executionId];
```
7. Get media information for a file.
```
MediaInformation *mediaInformation = [MobileFFprobe getMediaInformation:@"<file path or uri>"];
```
7. Record video and audio using iOS camera. This operation is not supported on `tvOS` since `AVFoundation` is not available on `tvOS`.
8. Record video and audio using iOS camera. This operation is not supported on `tvOS` since `AVFoundation` is not available on `tvOS`.
```
[MobileFFmpeg execute: @"-f avfoundation -r 30 -video_size 1280x720 -pixel_format bgr0 -i 0:0 -vcodec h264_videotoolbox -vsync 2 -f h264 -t 00:00:05 %@", recordFilePath];
```
8. List enabled external libraries.
```
NSArray *externalLibraries = [MobileFFmpegConfig getExternalLibraries];
```
9. Enable log callback.
```
[MobileFFmpegConfig setLogDelegate:self];
- (void)logCallback: (int)level :(NSString*)message {
- (void)logCallback:(long)executionId :(int)level :(NSString*)message {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", message);
});
@@ -320,33 +388,47 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
```
11. Set log level.
11. Ignore the handling of a signal.
```
[MobileFFmpegConfig ignoreSignal:SIGXCPU];
```
12. List ongoing executions.
```
NSArray* ffmpegExecutions = [MobileFFmpeg listExecutions];
for (int i = 0; i < [ffmpegExecutions count]; i++) {
FFmpegExecution* execution = [ffmpegExecutions objectAtIndex:i];
NSLog(@"Execution %d = id: %ld, startTime: %@, command: %@.\n", i, [execution getExecutionId], [execution getStartTime], [execution getCommand]);
}
```
13. Set default log level.
```
[MobileFFmpegConfig setLogLevel:AV_LOG_FATAL];
```
12. Register custom fonts directory.
14. Register custom fonts directory.
```
[MobileFFmpegConfig setFontDirectory:@"<folder with fonts>" with:nil];
```
#### 2.3 Manual Installation
##### 2.3.1 Android
#### 2.4 Manual Installation
##### 2.4.1 Android
You can import `MobileFFmpeg` aar packages in `Android Studio` using the `File` -> `New` -> `New Module` -> `Import .JAR/.AAR Package` menu.
##### 2.3.2 iOS / tvOS
##### 2.4.2 iOS / tvOS
iOS and tvOS frameworks can be installed manually using the [Importing Frameworks](https://github.com/tanersener/mobile-ffmpeg/wiki/Importing-Frameworks) guide.
If you want to use universal binaries please refer to [Using Universal Binaries](https://github.com/tanersener/mobile-ffmpeg/wiki/Using-Universal-Binaries) guide.
#### 2.4 Test Application
#### 2.5 Test Application
You can see how MobileFFmpeg is used inside an application by running test applications provided.
There is an `Android` test application under the `android/test-app` folder, an `iOS` test application under the
`ios/test-app` folder and a `tvOS` test application under the `tvos/test-app` folder.
All applications are identical and supports command execution, video encoding, accessing https, encoding audio,
burning subtitles, video stabilization and pipe operations.
burning subtitles, video stabilisation, pipe operations and concurrent command execution.
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/master/docs/assets/android_test_app.gif" width="240">
@@ -362,6 +444,9 @@ Exact version number is obtained using `git describe --tags`.
| MobileFFmpeg Version | FFmpeg Version | Release Date |
| :----: | :----: |:----: |
| [4.4](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.4) | 4.4-dev-416 | Jul 25, 2020 |
| [4.4.LTS](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.4.LTS) | 4.4-dev-416 | Jul 24, 2020 |
| [4.3.2](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3.2) | 4.3-dev-2955 | Apr 15, 2020 |
| [4.3.1](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3.1) | 4.3-dev-1944 | Jan 25, 2020 |
| [4.3.1.LTS](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3.1.LTS) | 4.3-dev-1944 | Jan 25, 2020 |
| [4.3](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3) | 4.3-dev-1181 | Oct 27, 2019 |
@@ -397,10 +482,15 @@ This table shows the differences between two variants.
| Android Architectures | arm-v7a-neon<br/>arm64-v8a<br/>x86<br/>x86-64 | arm-v7a<br/>arm-v7a-neon<br/>arm64-v8a<br/>x86<br/>x86-64 |
| Xcode Support | 10.1 | 7.3.1 |
| iOS SDK | 12.1 | 9.3 |
| iOS Architectures | arm64<br/>arm64e<br/>x86-64 | armv7<br/>arm64<br/>i386<br/>x86-64 |
| iOS AVFoundation | Yes | - |
| iOS Architectures | arm64<br/>arm64e<sup>1</sup><br/>x86-64<br/>x86-64-mac-catalyst<sup>2</sup> | armv7<br/>arm64<br/>i386<br/>x86-64 |
| tvOS SDK | 10.2 | 9.2 |
| tvOS Architectures | arm64<br/>x86-64 | arm64<br/>x86-64 |
<sup>1</sup> - Included until `v4.3.2`
<sup>2</sup> - Included since `v4.3.2`
### 5. Building
Build scripts from `master` and `development` branches are tested periodically. See the latest status from the table below.
@@ -424,7 +514,7 @@ Please visit [Android Prerequisites](https://github.com/tanersener/mobile-ffmpeg
2. Android builds require these additional packages.
- **Android SDK 4.1 Jelly Bean (API Level 16)** or later
- **Android NDK r20** or later with LLDB and CMake
- **Android NDK r21** or later with LLDB and CMake
3. iOS builds need these extra packages and tools.
- **Xcode 7.3.1** or later
@@ -478,14 +568,16 @@ All libraries created by the top level build scripts (`android.sh`, `ios.sh` and
the `prebuilt` directory.
- `Android` archive (.aar file) is located under the `android-aar` folder
- `iOS` frameworks are located under the `ios-framework`folder
- `iOS` universal binaries are located under the `ios-universal`folder
- `tvOS` frameworks are located under the `tvos-framework`folder
- `tvOS` universal binaries are located under the `tvos-universal`folder
- `iOS` frameworks are located under the `ios-framework` folder
- `iOS` xcframeworks are located under the `ios-xcframework` folder
- `iOS` universal binaries are located under the `ios-universal` folder
- `tvOS` frameworks are located under the `tvos-framework` folder
- `tvOS` universal binaries are located under the `tvos-universal` folder
#### 5.4 GPL Support
It is possible to enable GPL licensed libraries `x264`, `xvidcore` since `v1.1` and `vid.stab`, `x265` since `v2.1`
from the top level build scripts. Their source code is not included in the repository and downloaded when enabled.
It is possible to enable GPL licensed libraries `x264`, `xvidcore` since `v1.1`; `vid.stab`, `x265` since `v2.1` and
`rubberband` since `v4.3.2` from the top level build scripts. Their source code is not included in the repository and
downloaded when enabled.
#### 5.5 External Libraries
`build` directory includes build scripts of all external libraries. Two scripts exist for each external library,
@@ -531,30 +623,47 @@ Support this project with your organization. Your logo will show up here with a
### 8. License
This project is licensed under the LGPL v3.0. However, if source code is built using optional `--enable-gpl` flag or
prebuilt binaries with `-gpl` postfix are used then MobileFFmpeg is subject to the GPL v3.0 license.
`MobileFFmpeg` is licensed under the LGPL v3.0. However, if source code is built using the optional `--enable-gpl` flag
or prebuilt binaries with `-gpl` postfix are used, then MobileFFmpeg is subject to the GPL v3.0 license.
Source code of FFmpeg and external libraries is included in compliance with their individual licenses.
The source code of all external libraries included is in compliance with their individual licenses.
`openh264` source code included in this repository is licensed under the 2-clause BSD License but this license does
not cover the `MPEG LA` licensing fees. If you build `mobile-ffmpeg` with `openh264` and distribute that library, then
you are subject to pay `MPEG LA` licensing fees. Refer to [OpenH264 FAQ](https://www.openh264.org/faq.html) page for
the details. Please note that `mobile-ffmpeg` does not publish a binary with `openh264` inside.
`strip-frameworks.sh` script included and distributed (until v4.x) is published under the [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
`strip-frameworks.sh` script included and distributed (until v4.x) is published under the
[Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
In test applications; embedded fonts are licensed under the [SIL Open Font License](https://opensource.org/licenses/OFL-1.1), other digital assets are published in the public domain.
In test applications; embedded fonts are licensed under the
[SIL Open Font License](https://opensource.org/licenses/OFL-1.1), other digital assets are published in the public
domain.
Please visit [License](https://github.com/tanersener/mobile-ffmpeg/wiki/License) page for the details.
### 9. Contributing
### 9. Patents
If you have any recommendations or ideas to improve it, please feel free to submit issues or pull requests. Any help is appreciated.
It is not clearly explained in their documentation but it is believed that `FFmpeg`, `kvazaar`, `x264` and `x265`
include algorithms which are subject to software patents. If you live in a country where software algorithms are
patentable then you'll probably need to pay royalty fees to patent holders. We are not lawyers though, so we recommend
that you seek legal advice first. See [FFmpeg Patent Mini-FAQ](https://ffmpeg.org/legal.html).
### 10. See Also
`openh264` clearly states that it uses patented algorithms. Therefore, if you build mobile-ffmpeg with openh264 and
distribute that library, then you are subject to pay MPEG LA licensing fees. Refer to
[OpenH264 FAQ](https://www.openh264.org/faq.html) page for the details.
### 10. Contributing
Feel free to submit issues or pull requests.
Please note that `master` branch includes only the latest released source code. Changes planned for the next release
are implemented under the `development` branch. Therefore, if you want to create a pull request, please open it against
the `development`.
### 11. See Also
- [libav gas-preprocessor](https://github.com/libav/gas-preprocessor/raw/master/gas-preprocessor.pl)
- [FFmpeg API Documentation](https://ffmpeg.org/doxygen/4.0/index.html)
- [FFmpeg Wiki](https://trac.ffmpeg.org/wiki/WikiStart)
- [FFmpeg License and Legal Considerations](https://ffmpeg.org/legal.html)
- [FFmpeg External Library Licenses](https://www.ffmpeg.org/doxygen/4.0/md_LICENSE.html)
+625 -581
View File
File diff suppressed because it is too large Load Diff
+9 -6
View File
@@ -1,10 +1,13 @@
*.iml
.gradle
.gradle/
local.properties
.DS_Store
build
/build/
/app/build/
/test-app/build/
/app/.cxx/
captures
.externalNativeBuild
.idea
obj
libs
.externalNativeBuild/
.idea/
/obj/
/libs/
+137 -69
View File
@@ -1,4 +1,4 @@
# Doxyfile 1.8.14
# Doxyfile 1.8.18
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -17,10 +17,10 @@
# Project related configuration options
#---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the config file
# that follow. The default is UTF-8 which is also the encoding used for all text
# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
# built into libc) for the transcoding. See
# This tag specifies the encoding used for all characters in the configuration
# file that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8.
@@ -38,7 +38,7 @@ PROJECT_NAME = "MobileFFmpeg Android API"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 4.3.1
PROJECT_NUMBER = 4.4
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all generated output in the proper direction.
# Possible values are: None, LTR, RTL and Context.
# The default value is: None.
OUTPUT_TEXT_DIRECTION = None
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
@@ -189,6 +197,16 @@ SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
# such as
# /***************
# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
# Javadoc-style will behave just like regular comments and it will not be
# interpreted by doxygen.
# The default value is: NO.
JAVADOC_BANNER = NO
# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
# line (until the first dot) of a Qt-style comment as the brief description. If
# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
@@ -238,15 +256,13 @@ TAB_SIZE = 4
# "Side Effects:". You can put \n's in the value part of an alias to insert
# newlines (in the resulting output). You can put ^^ in the value part of an
# alias to insert a newline as if a physical newline was in the original file.
# When you need a literal { or } or , in the value part of an alias you have to
# escape them by means of a backslash (\), this can lead to conflicts with the
# commands \{ and \} for these it is advised to use the version @{ and @} or use
# a double escape (\\{ and \\})
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@@ -275,17 +291,26 @@ OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
# sources only. Doxygen will then generate output that is more tailored for that
# language. For instance, namespaces will be presented as modules, types will be
# separated into more groups, etc.
# The default value is: NO.
OPTIMIZE_OUTPUT_SLICE = NO
# Doxygen selects the parser to use depending on the extension of the files it
# parses. With this tag you can assign which parser to use for a given
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
# Fortran. In the later case the parser tries to guess whether the code is fixed
# or free formatted code, this is the default for Fortran type files), VHDL. For
# instance to make doxygen treat .inc files as Fortran files (default is PHP),
# and .f files as C (default is Fortran), use: inc=Fortran f=C.
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
@@ -296,7 +321,7 @@ EXTENSION_MAPPING =
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
# documentation. See http://daringfireball.net/projects/markdown/ for details.
# documentation. See https://daringfireball.net/projects/markdown/ for details.
# The output of markdown processing is further processed by doxygen, so you can
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues.
@@ -308,7 +333,7 @@ MARKDOWN_SUPPORT = YES
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings.
# Minimum value: 0, maximum value: 99, default value: 0.
# Minimum value: 0, maximum value: 99, default value: 5.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
TOC_INCLUDE_HEADINGS = 0
@@ -444,6 +469,12 @@ EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
# methods of a class will be included in the documentation.
# The default value is: NO.
EXTRACT_PRIV_VIRTUAL = NO
# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation.
# The default value is: NO.
@@ -498,8 +529,8 @@ HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# (class|struct|union) declarations. If set to NO, these declarations will be
# included in the documentation.
# declarations. If set to NO, these declarations will be included in the
# documentation.
# The default value is: NO.
HIDE_FRIEND_COMPOUNDS = NO
@@ -522,7 +553,7 @@ INTERNAL_DOCS = NO
# names in lower-case letters. If set to YES, upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
# (including Cygwin) ands Mac users are advised to set this option to NO.
# The default value is: system dependent.
CASE_SENSE_NAMES = NO
@@ -754,7 +785,8 @@ WARN_IF_DOC_ERROR = YES
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong or incomplete
# parameter documentation, but not about the absence of documentation.
# parameter documentation, but not about the absence of documentation. If
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.
WARN_NO_PARAMDOC = NO
@@ -813,8 +845,10 @@ INPUT_ENCODING = UTF-8
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
*.cc \
@@ -889,7 +923,7 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = cmdutils.* ffmpeg.* ffmpeg_*
EXCLUDE_PATTERNS =
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
@@ -1011,7 +1045,7 @@ INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
# function all documented functions referencing it will be listed.
# entity all documented functions referencing it will be listed.
# The default value is: NO.
REFERENCED_BY_RELATION = NO
@@ -1048,7 +1082,7 @@ SOURCE_TOOLTIPS = YES
#
# To use it do the following:
# - Install the latest version of global
# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
# - Make sure the INPUT points to the root of the source tree
# - Run doxygen as normal
#
@@ -1226,9 +1260,9 @@ HTML_TIMESTAMP = YES
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via Javascript. If disabled, the navigation index will
# are dynamically created via JavaScript. If disabled, the navigation index will
# consists of multiple levels of tabs that are statically embedded in every HTML
# page. Disable this option to support browsers that do not have Javascript,
# page. Disable this option to support browsers that do not have JavaScript,
# like the Qt help browser.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1258,13 +1292,13 @@ HTML_INDEX_NUM_ENTRIES = 100
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
# generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see: https://developer.apple.com/tools/xcode/), introduced with
# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
# Makefile in the HTML output directory. Running make will produce the docset in
# that directory and running make install will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html
# for more information.
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# genXcode/_index.html for more information.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1303,7 +1337,7 @@ DOCSET_PUBLISHER_NAME = Publisher
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
# Windows.
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
@@ -1379,7 +1413,7 @@ QCH_FILE =
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace
# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES.
@@ -1387,7 +1421,8 @@ QHP_NAMESPACE = org.doxygen.Project
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
# Help Project output. For more information please see Qt Help Project / Virtual
# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
# folders).
# The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES.
@@ -1395,21 +1430,23 @@ QHP_VIRTUAL_FOLDER = doc
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
# filter to add. For more information please see Qt Help Project / Custom
# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
# custom filter to add. For more information please see Qt Help Project / Custom
# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
# project's filter section matches. Qt Help Project / Filter Attributes (see:
# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_SECT_FILTER_ATTRS =
@@ -1493,6 +1530,17 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png The default and svg Looks nicer but requires the
# pdf2svg tool.
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FORMULA_FORMAT = png
# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
@@ -1513,8 +1561,14 @@ FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
FORMULA_MACROFILE =
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# https://www.mathjax.org) which uses client side Javascript for the rendering
# https://www.mathjax.org) which uses client side JavaScript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
# installed or if you want to formulas look prettier in the HTML output. When
# enabled you may also need to install MathJax separately and configure the path
@@ -1542,7 +1596,7 @@ MATHJAX_FORMAT = HTML-CSS
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/
@@ -1584,7 +1638,7 @@ MATHJAX_CODEFILE =
SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
# implemented using a web server instead of a web client using Javascript. There
# implemented using a web server instead of a web client using JavaScript. There
# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
# setting. When disabled, doxygen will generate a PHP script for searching and
# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
@@ -1668,21 +1722,35 @@ LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked.
#
# Note that when enabling USE_PDFLATEX this option is only used for generating
# bitmaps for formulas in the HTML output, but not in the Makefile that is
# written to the output directory.
# The default file is: latex.
# Note that when not enabling USE_PDFLATEX the default is latex when enabling
# USE_PDFLATEX the default is pdflatex and when in the later case latex is
# chosen this is overwritten by pdflatex. For specific output languages the
# default can have been set differently, this depends on the implementation of
# the output language.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX.
# Note: This tag is used in the Makefile / make.bat.
# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
# (.tex).
# The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex
# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
# generate index for LaTeX. In case there is no backslash (\) as first character
# it will be automatically added in the LaTeX code.
# Note: This tag is used in the generated output file (.tex).
# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
# The default value is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_MAKEINDEX_CMD = makeindex
# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
# documents. This may be useful for small projects and may help to save some
# trees in general.
@@ -1817,6 +1885,14 @@ LATEX_BIB_STYLE = plain
LATEX_TIMESTAMP = NO
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
# LATEX_OUTPUT directory will be used.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EMOJI_DIRECTORY =
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
@@ -1856,9 +1932,9 @@ COMPACT_RTF = NO
RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's config
# file, i.e. a series of assignments. You only have to provide replacements,
# missing definitions are set to their default value.
# Load stylesheet definitions from file. Syntax is similar to doxygen's
# configuration file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
#
# See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses.
@@ -1867,8 +1943,8 @@ RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an RTF document. Syntax is
# similar to doxygen's config file. A template extensions file can be generated
# using doxygen -e rtf extensionFile.
# similar to doxygen's configuration file. A template extensions file can be
# generated using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_EXTENSIONS_FILE =
@@ -1954,6 +2030,13 @@ XML_OUTPUT = xml
XML_PROGRAMLISTING = YES
# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
# namespace members in file scope as well, matching the HTML output.
# The default value is: NO.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_NS_MEMB_FILE_SCOPE = NO
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
@@ -2155,12 +2238,6 @@ EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
# The PERL_PATH should be the absolute path and name of the perl script
# interpreter (i.e. the result of 'which perl').
# The default file (with absolute path) is: /usr/bin/perl.
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
@@ -2174,15 +2251,6 @@ PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = YES
# You can define message sequence charts within doxygen comments using the \msc
# command. Doxygen will then run the mscgen tool (see:
# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
# documentation. The MSCGEN_PATH tag allows you to specify the directory where
# the mscgen tool resides. If left empty the tool is assumed to be found in the
# default search path.
MSCGEN_PATH =
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+4 -2
View File
@@ -2,12 +2,13 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion 29
ndkVersion "21.3.6528147"
defaultConfig {
minSdkVersion 24
targetSdkVersion 29
versionCode 240431
versionName "4.3.1"
versionCode 240440
versionName "4.4"
project.archivesBaseName = "mobile-ffmpeg"
consumerProguardFiles 'proguard-rules.pro'
}
@@ -39,4 +40,5 @@ task javadoc(type: Javadoc) {
dependencies {
testImplementation "androidx.test.ext:junit:1.1.1"
testImplementation "org.json:json:20190722"
}
+2 -2
View File
@@ -7,8 +7,8 @@
-keep class com.arthenica.mobileffmpeg.Config {
native <methods>;
void log(int, byte[]);
void statistics(int, float, float, long , int, double, double);
void log(long, int, byte[]);
void statistics(long, int, float, float, long , int, double, double);
}
-keep class com.arthenica.mobileffmpeg.AbiDetect {
+1 -1
View File
@@ -105,7 +105,7 @@ __thread AVDictionary *format_opts, *codec_opts, *resample_opts;
FILE *report_file;
int report_file_level = AV_LOG_DEBUG;
__thread int hide_banner = 0;
__thread int longjmp_value = 0;
__thread volatile int longjmp_value = 0;
enum show_muxdemuxers {
SHOW_DEFAULT,
@@ -334,6 +334,7 @@ typedef struct OptionParseContext {
* Parse an options group and write results into optctx.
*
* @param optctx an app-specific options context. NULL for global options group
* @param g option group
*/
int parse_optgroup(void *optctx, OptionGroup *g);
+47 -16
View File
@@ -24,11 +24,16 @@
*/
/*
* CHANGES 06.2020
* - ignoring signals implemented
* - cancel_operation() method signature updated with id
* - cancel by execution id implemented
*
* CHANGES 01.2020
* - ffprobe support changes
*
* CHANGES 12.2019
* - Concurrent execution support
* - concurrent execution support
*
* CHANGES 08.2018
* --------------------------------------------------------
@@ -239,6 +244,16 @@ __thread int restore_tty;
static void free_input_threads(void);
#endif
extern volatile int handleSIGQUIT;
extern volatile int handleSIGINT;
extern volatile int handleSIGTERM;
extern volatile int handleSIGXCPU;
extern volatile int handleSIGPIPE;
extern __thread volatile long executionId;
extern void removeExecution(long id);
extern int cancelRequested(long id);
/* sub2video hack:
Convert subtitles to video with alpha to insert them in filter graphs.
This is a temporary solution until libavfilter gets real subtitles support.
@@ -402,12 +417,12 @@ void term_exit(void)
term_exit_sigsafe();
}
volatile int received_sigterm = 0;
volatile int received_nb_signals = 0;
static volatile int received_sigterm = 0;
static volatile int received_nb_signals = 0;
__thread atomic_int transcode_init_done = ATOMIC_VAR_INIT(0);
__thread volatile int ffmpeg_exited = 0;
__thread int main_ffmpeg_return_code = 0;
extern __thread int longjmp_value;
__thread volatile int main_ffmpeg_return_code = 0;
extern __thread volatile int longjmp_value;
static void
sigterm_handler(int sig)
@@ -476,17 +491,27 @@ void term_init(void)
tcsetattr (0, TCSANOW, &tty);
}
signal(SIGQUIT, sigterm_handler); /* Quit (POSIX). */
if (handleSIGQUIT == 1) {
signal(SIGQUIT, sigterm_handler); /* Quit (POSIX). */
}
}
#endif
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
if (handleSIGINT == 1) {
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
}
if (handleSIGTERM == 1) {
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
}
#ifdef SIGXCPU
signal(SIGXCPU, sigterm_handler);
if (handleSIGXCPU == 1) {
signal(SIGXCPU, sigterm_handler);
}
#endif
#ifdef SIGPIPE
signal(SIGPIPE, SIG_IGN); /* Broken pipe (POSIX). */
if (handleSIGPIPE == 1) {
signal(SIGPIPE, SIG_IGN); /* Broken pipe (POSIX). */
}
#endif
#if HAVE_SETCONSOLECTRLHANDLER
SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE);
@@ -701,6 +726,8 @@ static void ffmpeg_cleanup(int ret)
if (received_sigterm) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n",
(int) received_sigterm);
} else if (cancelRequested(executionId)) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received cancel signal.\n");
} else if (ret && atomic_load(&transcode_init_done)) {
av_log(NULL, AV_LOG_INFO, "Conversion failed!\n");
}
@@ -2362,7 +2389,7 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts)
if (ifilter->filter) {
/* THIS VALIDATION IS REQUIRED TO COMPLETE CANCELLATION */
if (!received_sigterm) {
if (!received_sigterm && !cancelRequested(executionId)) {
ret = av_buffersrc_close(ifilter->filter, pts, AV_BUFFERSRC_FLAG_PUSH);
}
if (ret < 0)
@@ -4829,7 +4856,7 @@ static int transcode(void)
goto fail;
#endif
while (!received_sigterm) {
while (!received_sigterm && !cancelRequested(executionId)) {
int64_t cur_time= av_gettime_relative();
/* if 'q' pressed, exits */
@@ -5029,9 +5056,13 @@ void set_report_callback(void (*callback)(int, float, float, int64_t, int, doubl
report_callback = callback;
}
void cancel_operation()
void cancel_operation(long id)
{
sigterm_handler(SIGINT);
if (id == 0) {
sigterm_handler(SIGINT);
} else {
removeExecution(id);
}
}
__thread OptionDef *ffmpeg_options = NULL;
@@ -5541,10 +5572,10 @@ int ffmpeg_execute(int argc, char **argv)
if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
exit_program(69);
exit_program(received_nb_signals ? 255 : main_ffmpeg_return_code);
exit_program((received_nb_signals || cancelRequested(executionId))? 255 : main_ffmpeg_return_code);
} else {
main_ffmpeg_return_code = longjmp_value;
main_ffmpeg_return_code = (received_nb_signals || cancelRequested(executionId)) ? 255 : longjmp_value;
}
return main_ffmpeg_return_code;
+4 -1
View File
@@ -17,6 +17,9 @@
*/
/*
* CHANGES 06.2020
* - cancel_operation() method signature updated with id
*
* CHANGES 01.2020
* - ffprobe support changes
*
@@ -685,7 +688,7 @@ int hwaccel_decode_init(AVCodecContext *avctx);
void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double));
void cancel_operation();
void cancel_operation(long id);
int opt_map(void *optctx, const char *opt, const char *arg);
int opt_map_channel(void *optctx, const char *opt, const char *arg);
+2 -2
View File
@@ -261,8 +261,8 @@ __thread AVInputFormat *iformat = NULL;
__thread struct AVHashContext *hash;
__thread int main_ffprobe_return_code = 0;
extern __thread int longjmp_value;
__thread volatile int main_ffprobe_return_code = 0;
extern __thread volatile int longjmp_value;
static const struct {
double bin_val;
+185 -112
View File
@@ -17,34 +17,6 @@
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* CHANGES 08.2019
* --------------------------------------------------------
* - lastCommandOutput methods introduced
* - AV_LOG_STDERR introduced
*
* CHANGES 04.2019
* --------------------------------------------------------
* - setNativeEnvironmentVariable method added
*
* CHANGES 02.2019
* --------------------------------------------------------
* - JavaVM registered via av_jni_set_java_vm()
* - registerNewNativeFFmpegPipe() method added
*
* CHANGES 10.2018
* --------------------------------------------------------
* - getBuildConf method added
*
* CHANGES 09.2018
* --------------------------------------------------------
* - Merged with mobileffmpeg_config
*
* CHANGES 08.2018
* --------------------------------------------------------
* - Copied methods with avutil_log_ prefix from libavutil/log.c
*/
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -59,9 +31,10 @@
/** Callback data structure */
struct CallbackData {
int type; // 1 (log callback) or 2 (statistics callback)
long executionId; // execution id
int logLevel; // log level
char *logData; // log data
AVBPrint logData; // log data
int statisticsFrameNumber; // statistics frame number
float statisticsFps; // statistics fps
@@ -74,14 +47,19 @@ struct CallbackData {
struct CallbackData *next;
};
/** Execution map variables */
const int EXECUTION_MAP_SIZE = 1000;
static volatile int executionMap[EXECUTION_MAP_SIZE];
static pthread_mutex_t executionMapMutex;
/** Redirection control variables */
pthread_mutex_t lockMutex;
pthread_mutex_t monitorMutex;
pthread_cond_t monitorCondition;
static pthread_mutex_t lockMutex;
static pthread_mutex_t monitorMutex;
static pthread_cond_t monitorCondition;
/** Last command output variables */
pthread_mutex_t logMutex;
static char *lastCommandOutput;
static pthread_mutex_t logMutex;
static AVBPrint lastCommandOutput;
pthread_t callbackThread;
int redirectionEnabled;
@@ -113,6 +91,19 @@ const char *configClassName = "com/arthenica/mobileffmpeg/Config";
/** Full name of String class */
const char *stringClassName = "java/lang/String";
/** Fields that control the handling of SIGNALs */
volatile int handleSIGQUIT = 1;
volatile int handleSIGINT = 1;
volatile int handleSIGTERM = 1;
volatile int handleSIGXCPU = 1;
volatile int handleSIGPIPE = 1;
/** Holds the id of the current execution */
__thread volatile long executionId = 0;
/** Holds the default log level */
int configuredLogLevel = AV_LOG_INFO;
/** Prototypes of native functions defined by Config class. */
JNINativeMethod configMethods[] = {
{"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection},
@@ -121,20 +112,19 @@ JNINativeMethod configMethods[] = {
{"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel},
{"getNativeFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion},
{"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeVersion},
{"nativeFFmpegExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute},
{"nativeFFmpegCancel", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel},
{"nativeFFmpegExecute", "(J[Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute},
{"nativeFFmpegCancel", "(J)V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel},
{"nativeFFprobeExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFprobeExecute},
{"registerNewNativeFFmpegPipe", "(Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_registerNewNativeFFmpegPipe},
{"getNativeBuildDate", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeBuildDate},
{"setNativeEnvironmentVariable", "(Ljava/lang/String;Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmentVariable}
{"setNativeEnvironmentVariable", "(Ljava/lang/String;Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmentVariable},
{"getNativeLastCommandOutput", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput},
{"ignoreNativeSignal", "(I)V", (void*) Java_com_arthenica_mobileffmpeg_Config_ignoreNativeSignal}
};
/** Forward declaration for function defined in fftools_ffmpeg.c */
int ffmpeg_execute(int argc, char **argv);
/** DEFINES LINE SIZE USED FOR LOGGING */
#define LOG_LINE_SIZE 1024
static const char *avutil_log_get_level_str(int level) {
switch (level) {
case AV_LOG_STDERR:
@@ -233,7 +223,16 @@ void logInit() {
pthread_mutex_init(&logMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
lastCommandOutput = NULL;
av_bprint_init(&lastCommandOutput, 0, AV_BPRINT_SIZE_UNLIMITED);
}
void executionMapLockInit() {
pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&executionMapMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
}
void mutexUnInit() {
@@ -249,6 +248,10 @@ void logUnInit() {
pthread_mutex_destroy(&logMutex);
}
void executionMapLockUnInit() {
pthread_mutex_destroy(&executionMapMutex);
}
void mutexLock() {
pthread_mutex_lock(&lockMutex);
}
@@ -257,6 +260,10 @@ void lastCommandOutputLock() {
pthread_mutex_lock(&logMutex);
}
void executionMapLock() {
pthread_mutex_lock(&executionMapMutex);
}
void mutexUnlock() {
pthread_mutex_unlock(&lockMutex);
}
@@ -265,50 +272,24 @@ void lastCommandOutputUnlock() {
pthread_mutex_unlock(&logMutex);
}
void executionMapUnlock() {
pthread_mutex_unlock(&executionMapMutex);
}
void clearLastCommandOutput() {
lastCommandOutputLock();
if (lastCommandOutput != NULL) {
av_free(lastCommandOutput);
lastCommandOutput = NULL;
}
av_bprint_clear(&lastCommandOutput);
lastCommandOutputUnlock();
}
void appendLastCommandOutput(const char *logMessage) {
size_t length = 0;
char *tempLastCommandOutput = NULL;
size_t logMessageLength = strlen(logMessage);
if (logMessageLength <= 0) {
void appendLastCommandOutput(AVBPrint *logMessage) {
if (logMessage->len <= 0) {
return;
}
lastCommandOutputLock();
if (lastCommandOutput == NULL) {
length = logMessageLength + 1;
lastCommandOutput = (char*)av_malloc(length);
memcpy(lastCommandOutput, logMessage, length);
} else {
size_t length1 = strlen(lastCommandOutput);
length = length1 + logMessageLength + 1;
char *newLastCommandOutput = (char*)av_malloc(length);
memcpy(newLastCommandOutput, lastCommandOutput, length1);
memcpy(newLastCommandOutput + length1, logMessage, logMessageLength + 1);
tempLastCommandOutput = lastCommandOutput;
lastCommandOutput = newLastCommandOutput;
}
av_bprintf(&lastCommandOutput, "%s", logMessage->str);
lastCommandOutputUnlock();
if (tempLastCommandOutput != NULL) {
av_free(tempLastCommandOutput);
}
}
void monitorWait(int milliSeconds) {
@@ -343,15 +324,15 @@ void monitorNotify() {
* @param level log level
* @param data log data
*/
void logCallbackDataAdd(int level, const char *data) {
void logCallbackDataAdd(int level, AVBPrint *data) {
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 1;
newData->executionId = executionId;
newData->logLevel = level;
size_t dataSize = strlen(data) + 1;
newData->logData = (char*)av_malloc(dataSize);
memcpy(newData->logData, data, dataSize);
av_bprint_init(&newData->logData, 0, AV_BPRINT_SIZE_UNLIMITED);
av_bprintf(&newData->logData, "%s", data->str);
newData->next = NULL;
mutexLock();
@@ -385,6 +366,7 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 2;
newData->executionId = executionId;
newData->statisticsFrameNumber = frameNumber;
newData->statisticsFps = fps;
newData->statisticsQuality = quality;
@@ -418,6 +400,20 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
monitorNotify();
}
/**
* Adds an execution id to the execution map.
*
* @param id execution id
*/
void addExecution(long id) {
executionMapLock();
int key = id % EXECUTION_MAP_SIZE;
executionMap[key] = 1;
executionMapUnlock();
}
/**
* Removes head of callback data list.
*/
@@ -450,6 +446,41 @@ struct CallbackData *callbackDataRemove() {
return currentData;
}
/**
* Removes an execution id from the execution map.
*
* @param id execution id
*/
void removeExecution(long id) {
executionMapLock();
int key = id % EXECUTION_MAP_SIZE;
executionMap[key] = 0;
executionMapUnlock();
}
/**
* Checks whether a cancel request for the given execution id exists in the execution map.
*
* @param id execution id
* @return 1 if exists, false otherwise
*/
int cancelRequested(long id) {
int found = 0;
executionMapLock();
int key = id % EXECUTION_MAP_SIZE;
if (executionMap[key] == 0) {
found = 1;
}
executionMapUnlock();
return found;
}
/**
* Callback function for FFmpeg logs.
*
@@ -459,7 +490,7 @@ struct CallbackData *callbackDataRemove() {
* @param vargs arguments
*/
void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs) {
char line[LOG_LINE_SIZE];
AVBPrint fullLine;
AVBPrint part[4];
int print_prefix = 1;
@@ -473,21 +504,27 @@ void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format
return;
}
av_bprint_init(&fullLine, 0, AV_BPRINT_SIZE_UNLIMITED);
avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix);
avutil_log_sanitize(part[0].str);
avutil_log_sanitize(part[1].str);
avutil_log_sanitize(part[2].str);
avutil_log_sanitize(part[3].str);
snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
// COMBINE ALL 4 LOG PARTS
av_bprintf(&fullLine, "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
logCallbackDataAdd(level, line);
appendLastCommandOutput(line);
if (fullLine.len > 0) {
logCallbackDataAdd(level, &fullLine);
appendLastCommandOutput(&fullLine);
}
av_bprint_finalize(part, NULL);
av_bprint_finalize(part+1, NULL);
av_bprint_finalize(part+2, NULL);
av_bprint_finalize(part+3, NULL);
av_bprint_finalize(&fullLine, NULL);
}
/**
@@ -533,25 +570,25 @@ void *callbackThreadFunction() {
// LOG CALLBACK
size_t size = strlen(callbackData->logData);
int size = callbackData->logData.len;
jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, (jbyte *)callbackData->logData);
(*env)->CallStaticVoidMethod(env, configClass, logMethod, callbackData->logLevel, byteArray);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, callbackData->logData.str);
(*env)->CallStaticVoidMethod(env, configClass, logMethod, (jlong) callbackData->executionId, callbackData->logLevel, byteArray);
(*env)->DeleteLocalRef(env, byteArray);
// CLEAN LOG DATA
av_free(callbackData->logData);
av_bprint_finalize(&callbackData->logData, NULL);
} else {
// STATISTICS CALLBACK
(*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
callbackData->statisticsFrameNumber, callbackData->statisticsFps,
callbackData->statisticsQuality, callbackData->statisticsSize,
callbackData->statisticsTime, callbackData->statisticsBitrate,
callbackData->statisticsSpeed);
(jlong) callbackData->executionId, callbackData->statisticsFrameNumber,
callbackData->statisticsFps, callbackData->statisticsQuality,
callbackData->statisticsSize, callbackData->statisticsTime,
callbackData->statisticsBitrate, callbackData->statisticsSpeed);
}
@@ -604,24 +641,21 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
(*env)->GetJavaVM(env, &globalVm);
logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(I[B)V");
logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(JI[B)V");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "log");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE;
}
statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(IFFJIDD)V");
statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(JIFFJIDD)V");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "statistics");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE;
}
stringConstructor = (*env)->GetMethodID(env, localStringClass, "<init>", "([BLjava/lang/String;)V");
if (stringConstructor == NULL) {
LOGE("OnLoad thread failed to GetMethodID for %s.\n", "<init>");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE;
}
@@ -634,10 +668,15 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
callbackDataHead = NULL;
callbackDataTail = NULL;
for(int i = 0; i<EXECUTION_MAP_SIZE; i++) {
executionMap[i] = 0;
}
mutexInit();
monitorInit();
logInit();
executionMapLockInit();
return JNI_VERSION_1_6;
}
@@ -650,7 +689,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
* @param level log level
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) {
av_log_set_level(level);
configuredLogLevel = level;
}
/**
@@ -660,7 +699,7 @@ JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(
* @param object reference to the class on which this method is invoked
*/
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object) {
return av_log_get_level();
return configuredLogLevel;
}
/**
@@ -741,14 +780,18 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersio
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param id execution id
* @param stringArray reference to the object holding FFmpeg command arguments
* @return zero on successful execution, non-zero on error
*/
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *env, jclass object, jobjectArray stringArray) {
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *env, jclass object, jlong id, jobjectArray stringArray) {
jstring *tempArray = NULL;
int argumentCount = 1;
char **argv = NULL;
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW EXECUTION
av_log_set_level(configuredLogLevel);
if (stringArray != NULL) {
int programArgumentCount = (*env)->GetArrayLength(env, stringArray);
argumentCount = programArgumentCount + 1;
@@ -777,9 +820,16 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecut
// LAST COMMAND OUTPUT SHOULD BE CLEARED BEFORE STARTING A NEW EXECUTION
clearLastCommandOutput();
// REGISTER THE ID BEFORE STARTING EXECUTION
executionId = (long) id;
addExecution((long) id);
// RUN
int retCode = ffmpeg_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP
removeExecution((long) id);
// CLEANUP
if (tempArray != NULL) {
for (int i = 0; i < (argumentCount - 1); i++) {
@@ -799,9 +849,10 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecut
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param id execution id
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *env, jclass object) {
cancel_operation();
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *env, jclass object, jlong id) {
cancel_operation(id);
}
/**
@@ -844,7 +895,11 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
const char *variableNameString = (*env)->GetStringUTFChars(env, variableName, 0);
const char *variableValueString = (*env)->GetStringUTFChars(env, variableValue, 0);
return setenv(variableNameString, variableValueString, 1);
int rc = setenv(variableNameString, variableValueString, 1);
(*env)->ReleaseStringUTFChars(env, variableName, variableNameString);
(*env)->ReleaseStringUTFChars(env, variableValue, variableValueString);
return rc;
}
/**
@@ -855,16 +910,34 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
* @return output of the last executed command
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput(JNIEnv *env, jclass object) {
if (lastCommandOutput != NULL) {
int size = strlen(lastCommandOutput);
if (size > 0) {
jbyteArray byteArray = (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, lastCommandOutput);
jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
return (jstring) (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName);
}
int size = lastCommandOutput.len;
if (size > 0) {
jbyteArray byteArray = (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, lastCommandOutput.str);
jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
return (jstring) (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName);
}
return (*env)->NewStringUTF(env, "");
}
/**
* Registers a new ignored signal. Ignored signals are not handled by the library.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param signum signal number
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum) {
if (signum == SIGQUIT) {
handleSIGQUIT = 0;
} else if (signum == SIGINT) {
handleSIGINT = 0;
} else if (signum == SIGTERM) {
handleSIGTERM = 0;
} else if (signum == SIGXCPU) {
handleSIGXCPU = 0;
} else if (signum == SIGPIPE) {
handleSIGPIPE = 0;
}
}
+12 -5
View File
@@ -27,7 +27,7 @@
#include "libavutil/ffversion.h"
/** Library version string */
#define MOBILE_FFMPEG_VERSION "4.3.1"
#define MOBILE_FFMPEG_VERSION "4.4"
/** Defines tag used for Android logging. */
#define LIB_NAME "mobile-ffmpeg"
@@ -92,16 +92,16 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersio
/*
* Class: com_arthenica_mobileffmpeg_Config
* Method: nativeFFmpegExecute
* Signature: ([Ljava/lang/String;)I
* Signature: (J[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *, jclass, jobjectArray);
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *, jclass, jlong id, jobjectArray);
/*
* Class: com_arthenica_mobileffmpeg_Config
* Method: nativeFFmpegCancel
* Signature: ()V
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *, jclass);
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *, jclass, jlong);
/*
* Class: com_arthenica_mobileffmpeg_Config
@@ -131,4 +131,11 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput(JNIEnv *env, jclass object);
/*
* Class: com_arthenica_mobileffmpeg_Config
* Method: ignoreNativeSignal
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum);
#endif /* MOBILE_FFMPEG_H */
@@ -122,7 +122,7 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_getNativeCpu
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @return YES or NO
* @return yes or no
*/
JNIEXPORT jboolean JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_isNativeLTSBuild(JNIEnv *env, jclass object) {
#if defined(MOBILE_FFMPEG_LTS)
+5
View File
@@ -32,6 +32,8 @@ int ffprobe_execute(int argc, char **argv);
/** Forward declaration for function defined in mobileffmpeg.c */
void clearLastCommandOutput();
extern int configuredLogLevel;
/**
* Synchronously executes FFprobe natively with arguments provided.
*
@@ -45,6 +47,9 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFprobeExecu
int argumentCount = 1;
char **argv = NULL;
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW EXECUTION
av_log_set_level(configuredLogLevel);
if (stringArray != NULL) {
int programArgumentCount = (*env)->GetArrayLength(env, stringArray);
argumentCount = programArgumentCount + 1;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,11 +19,8 @@
package com.arthenica.mobileffmpeg;
import android.os.Build;
/**
* <p>This class is used to detect running ABI name using Android's <code>cpufeatures</code>
* library.
* <p>This class is used to detect running ABI name using Google <code>cpu-features</code> library.
*
* @author Taner Sener
* @since v1.0
@@ -33,10 +30,6 @@ public class AbiDetect {
static {
armV7aNeonLoaded = false;
/* LOAD NOT-LOADED LIBRARIES ON API < 21 */
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
System.loadLibrary("cpufeatures");
}
System.loadLibrary("mobileffmpeg_abidetect");
/* ALL LIBRARIES LOADED AT STARTUP */
@@ -90,7 +83,7 @@ public class AbiDetect {
/**
* <p>Returns whether MobileFFmpeg release is a long term release or not.
*
* @return YES or NO
* @return yes or no
*/
native static boolean isNativeLTSBuild();
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
/**
* <p>Utility class to execute an FFmpeg command asynchronously.
*
* @author Taner Sener
*/
public class AsyncFFmpegExecuteTask extends AsyncTask<Void, Integer, Integer> {
private final String[] arguments;
private final ExecuteCallback executeCallback;
private final Long executionId;
public AsyncFFmpegExecuteTask(final String command, final ExecuteCallback executeCallback) {
this(FFmpeg.parseArguments(command), executeCallback);
}
public AsyncFFmpegExecuteTask(final String[] arguments, final ExecuteCallback executeCallback) {
this(FFmpeg.DEFAULT_EXECUTION_ID, arguments, executeCallback);
}
public AsyncFFmpegExecuteTask(final long executionId, final String command, final ExecuteCallback executeCallback) {
this(executionId, FFmpeg.parseArguments(command), executeCallback);
}
public AsyncFFmpegExecuteTask(final long executionId, final String[] arguments, final ExecuteCallback executeCallback) {
this.executionId = executionId;
this.arguments = arguments;
this.executeCallback = executeCallback;
}
@Override
protected Integer doInBackground(final Void... unused) {
return Config.ffmpegExecute(executionId, this.arguments);
}
@Override
protected void onPostExecute(final Integer rc) {
if (executeCallback != null) {
executeCallback.apply(executionId, rc);
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
/**
* <p>Utility class to execute an FFprobe command asynchronously.
*
* @author Taner Sener
*/
public class AsyncFFprobeExecuteTask extends AsyncTask<Void, Integer, Integer> {
private final String[] arguments;
private final ExecuteCallback ExecuteCallback;
public AsyncFFprobeExecuteTask(final String command, final ExecuteCallback executeCallback) {
this.arguments = FFmpeg.parseArguments(command);
this.ExecuteCallback = executeCallback;
}
public AsyncFFprobeExecuteTask(final String[] arguments, final ExecuteCallback executeCallback) {
this.arguments = arguments;
ExecuteCallback = executeCallback;
}
@Override
protected Integer doInBackground(final Void... unused) {
return FFprobe.execute(this.arguments);
}
@Override
protected void onPostExecute(final Integer rc) {
if (ExecuteCallback != null) {
ExecuteCallback.apply(FFmpeg.DEFAULT_EXECUTION_ID, rc);
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.FFprobe;
import com.arthenica.mobileffmpeg.GetMediaInformationCallback;
import com.arthenica.mobileffmpeg.MediaInformation;
/**
* <p>Utility class to get media information asynchronously.
*
* @author Taner Sener
*/
public class AsyncGetMediaInformationTask extends AsyncTask<String, MediaInformation, MediaInformation> {
private final String path;
private final GetMediaInformationCallback getMediaInformationCallback;
public AsyncGetMediaInformationTask(final String path, final GetMediaInformationCallback getMediaInformationCallback) {
this.path = path;
this.getMediaInformationCallback = getMediaInformationCallback;
}
@Override
protected MediaInformation doInBackground(final String... arguments) {
return FFprobe.getMediaInformation(path);
}
@Override
protected void onPostExecute(final MediaInformation mediaInformation) {
if (getMediaInformationCallback != null) {
getMediaInformationCallback.apply(mediaInformation);
}
}
}
@@ -33,6 +33,11 @@ import java.util.List;
import static android.content.Context.CAMERA_SERVICE;
import static com.arthenica.mobileffmpeg.Config.TAG;
/**
* Utility class for camera devices.
*
* @author Taner Sener
*/
class CameraSupport {
/**
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -27,6 +27,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@@ -78,25 +79,47 @@ public class Config {
private static int lastCreatedPipeIndex;
private static final List<FFmpegExecution> executions;
static {
Log.i(Config.TAG, "Loading mobile-ffmpeg.");
/* LOAD NOT-LOADED LIBRARIES ON API < 21 */
boolean nativeFFmpegLoaded = false;
boolean nativeFFmpegTriedAndFailed = false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
/* LOADING LIBRARIES MANUALLY ON API < 21 */
final List<String> externalLibrariesEnabled = getExternalLibraries();
if (externalLibrariesEnabled.contains("tesseract") || externalLibrariesEnabled.contains("x265") || externalLibrariesEnabled.contains("snappy") || externalLibrariesEnabled.contains("openh264")) {
// libc++_shared.so included only when tesseract or x265 is enabled
if (externalLibrariesEnabled.contains("tesseract") || externalLibrariesEnabled.contains("x265") || externalLibrariesEnabled.contains("snappy") || externalLibrariesEnabled.contains("openh264") || externalLibrariesEnabled.contains("rubberband")) {
System.loadLibrary("c++_shared");
}
System.loadLibrary("cpufeatures");
System.loadLibrary("avutil");
System.loadLibrary("swscale");
System.loadLibrary("swresample");
System.loadLibrary("avcodec");
System.loadLibrary("avformat");
System.loadLibrary("avfilter");
System.loadLibrary("avdevice");
if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
try {
System.loadLibrary("avutil_neon");
System.loadLibrary("swscale_neon");
System.loadLibrary("swresample_neon");
System.loadLibrary("avcodec_neon");
System.loadLibrary("avformat_neon");
System.loadLibrary("avfilter_neon");
System.loadLibrary("avdevice_neon");
nativeFFmpegLoaded = true;
} catch (final UnsatisfiedLinkError e) {
Log.i(Config.TAG, "NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.", e);
nativeFFmpegTriedAndFailed = true;
}
}
if (!nativeFFmpegLoaded) {
System.loadLibrary("avutil");
System.loadLibrary("swscale");
System.loadLibrary("swresample");
System.loadLibrary("avcodec");
System.loadLibrary("avformat");
System.loadLibrary("avfilter");
System.loadLibrary("avdevice");
}
}
/* ALL MOBILE-FFMPEG LIBRARIES LOADED AT STARTUP */
@@ -104,30 +127,23 @@ public class Config {
FFmpeg.class.getName();
FFprobe.class.getName();
/*
* NEON supported arm-v7a library has a different name
*/
boolean nativeLibraryLoaded = false;
if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
if (AbiDetect.isNativeLTSBuild()) {
boolean nativeMobileFFmpegLoaded = false;
if (!nativeFFmpegTriedAndFailed && AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
try {
/*
* IF CPU SUPPORTS ARM-V7A-NEON THE TRY TO LOAD IT FIRST. IF NOT LOAD DEFAULT ARM-V7A
* THE TRY TO LOAD ARM-V7A-NEON FIRST. IF NOT LOAD DEFAULT ARM-V7A
*/
try {
System.loadLibrary("mobileffmpeg_armv7a_neon");
nativeLibraryLoaded = true;
AbiDetect.setArmV7aNeonLoaded(true);
} catch (final UnsatisfiedLinkError e) {
Log.i(Config.TAG, "NEON supported armeabi-v7a library not found. Loading default armeabi-v7a library.", e);
}
} else {
System.loadLibrary("mobileffmpeg_armv7a_neon");
nativeMobileFFmpegLoaded = true;
AbiDetect.setArmV7aNeonLoaded(true);
} catch (final UnsatisfiedLinkError e) {
Log.i(Config.TAG, "NEON supported armeabi-v7a mobileffmpeg library not found. Loading default armeabi-v7a library.", e);
}
}
if (!nativeLibraryLoaded) {
if (!nativeMobileFFmpegLoaded) {
System.loadLibrary("mobileffmpeg");
}
@@ -141,6 +157,8 @@ public class Config {
enableRedirection();
lastCreatedPipeIndex = 0;
executions = Collections.synchronizedList(new ArrayList<FFmpegExecution>());
}
/**
@@ -214,10 +232,11 @@ public class Config {
/**
* <p>Log redirection method called by JNI/native part.
*
* @param levelValue log level as defined in {@link Level}
* @param logMessage redirected log message
* @param executionId id of the execution that generated this log, 0 by default
* @param levelValue log level as defined in {@link Level}
* @param logMessage redirected log message
*/
private static void log(final int levelValue, final byte[] logMessage) {
private static void log(final long executionId, final int levelValue, final byte[] logMessage) {
final Level level = Level.from(levelValue);
final String text = new String(logMessage);
@@ -229,7 +248,7 @@ public class Config {
if (logCallbackFunction != null) {
try {
logCallbackFunction.apply(new LogMessage(level, text));
logCallbackFunction.apply(new LogMessage(executionId, level, text));
} catch (final Exception e) {
Log.e(Config.TAG, "Exception thrown inside LogCallback block", e);
}
@@ -274,6 +293,7 @@ public class Config {
/**
* <p>Statistics redirection method called by JNI/native part.
*
* @param executionId id of the execution that generated this statistics, 0 by default
* @param videoFrameNumber last processed frame number for videos
* @param videoFps frames processed per second for videos
* @param videoQuality quality of the video stream
@@ -282,10 +302,10 @@ public class Config {
* @param bitrate output bit rate in kbits/s
* @param speed processing speed = processed duration / operation duration
*/
private static void statistics(final int videoFrameNumber, final float videoFps,
final float videoQuality, final long size, final int time,
final double bitrate, final double speed) {
final Statistics newStatistics = new Statistics(videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
private static void statistics(final long executionId, final int videoFrameNumber,
final float videoFps, final float videoQuality, final long size,
final int time, final double bitrate, final double speed) {
final Statistics newStatistics = new Statistics(executionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
lastReceivedStatistics.update(newStatistics);
if (statisticsCallbackFunction != null) {
@@ -497,7 +517,7 @@ public class Config {
* @return MobileFFmpeg version
*/
public static String getVersion() {
if (AbiDetect.isNativeLTSBuild()) {
if (isLTSBuild()) {
return String.format("%s-lts", getNativeVersion());
} else {
return getNativeVersion();
@@ -586,6 +606,48 @@ public class Config {
} while (buffer.length() > 0);
}
/**
* <p>Sets an environment variable.
*
* @param variableName environment variable name
* @param variableValue environment variable value
* @return zero on success, non-zero on error
*/
public static int setEnvironmentVariable(final String variableName, final String variableValue) {
return setNativeEnvironmentVariable(variableName, variableValue);
}
/**
* <p>Registers a new ignored signal. Ignored signals are not handled by the library.
*
* @param signal signal number to ignore
*/
public static void ignoreSignal(final Signal signal) {
ignoreNativeSignal(signal.getValue());
}
/**
* <p>Synchronously executes FFmpeg with arguments provided.
*
* @param executionId id of the execution
* @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
static int ffmpegExecute(final long executionId, final String[] arguments) {
final FFmpegExecution currentFFmpegExecution = new FFmpegExecution(executionId, arguments);
executions.add(currentFFmpegExecution);
try {
final int lastReturnCode = nativeFFmpegExecute(executionId, arguments);
Config.setLastReturnCode(lastReturnCode);
return lastReturnCode;
} finally {
executions.remove(currentFFmpegExecution);
}
}
/**
* Updates return code value for the last executed command.
*
@@ -595,6 +657,15 @@ public class Config {
lastReturnCode = newLastReturnCode;
}
/**
* <p>Lists ongoing FFmpeg executions.
*
* @return list of ongoing FFmpeg executions
*/
static List<FFmpegExecution> listFFmpegExecutions() {
return new ArrayList<>(executions);
}
/**
* <p>Enables native redirection. Necessary for log and statistics callback functions.
*/
@@ -624,28 +695,31 @@ public class Config {
*
* @return FFmpeg version
*/
native static String getNativeFFmpegVersion();
private native static String getNativeFFmpegVersion();
/**
* <p>Returns MobileFFmpeg library version natively.
*
* @return MobileFFmpeg version
*/
native static String getNativeVersion();
private native static String getNativeVersion();
/**
* <p>Synchronously executes FFmpeg natively with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @param executionId id of the execution
* @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
native static int nativeFFmpegExecute(final String[] arguments);
private native static int nativeFFmpegExecute(final long executionId, final String[] arguments);
/**
* <p>Cancels an ongoing FFmpeg operation natively. This function does not wait for termination
* to complete and returns immediately.
*
* @param executionId id of the execution
*/
native static void nativeFFmpegCancel();
native static void nativeFFmpegCancel(final long executionId);
/**
* <p>Synchronously executes FFprobe natively with arguments provided.
@@ -663,14 +737,14 @@ public class Config {
* @param ffmpegPipePath full path of ffmpeg pipe
* @return zero on successful creation, non-zero on error
*/
native static int registerNewNativeFFmpegPipe(final String ffmpegPipePath);
private native static int registerNewNativeFFmpegPipe(final String ffmpegPipePath);
/**
* <p>Returns MobileFFmpeg library build date natively.
*
* @return MobileFFmpeg library build date
*/
native static String getNativeBuildDate();
private native static String getNativeBuildDate();
/**
* <p>Sets an environment variable natively.
@@ -679,13 +753,20 @@ public class Config {
* @param variableValue environment variable value
* @return zero on success, non-zero on error
*/
public native static int setNativeEnvironmentVariable(final String variableName, final String variableValue);
private native static int setNativeEnvironmentVariable(final String variableName, final String variableValue);
/**
* <p>Returns log output of the last executed single command natively.
*
* @return output of the last executed single command
*/
native static String getNativeLastCommandOutput();
private native static String getNativeLastCommandOutput();
/**
* <p>Registers a new ignored signal natively. Ignored signals are not handled by the library.
*
* @param signum signal number
*/
private native static void ignoreNativeSignal(final int signum);
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to receive an asynchronous execution result.
*
* @author Taner Sener
* @since v2.1
*/
@FunctionalInterface
public interface ExecuteCallback {
/**
* <p>Called when an asynchronous FFmpeg execution is completed.
*
* @param executionId id of the execution that completed
* @param returnCode return code of the execution completed, 0 on successful completion, 255
* on user cancel, other non-zero codes on error
*/
void apply(long executionId, int returnCode);
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,22 +19,34 @@
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
/**
* <p>Main class for FFmpeg operations. Provides {@link #execute(String...)} method to execute
* FFmpeg commands.
* <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and
* asynchronous {@link #executeAsync(String, ExecuteCallback)} methods to execute FFmpeg commands.
* <pre>
* int rc = FFmpeg.execute("-i file1.mp4 -c:v libxvid file1.avi");
* Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
* </pre>
* <pre>
* long executionId = FFmpeg.executeAsync("-i file1.mp4 -c:v libxvid file1.avi", executeCallback);
* Log.i(Config.TAG, String.format("Asynchronous execution %d started.", executionId));
* </pre>
*
* @author Taner Sener
* @since v1.0
*/
public class FFmpeg {
static final long DEFAULT_EXECUTION_ID = 0;
private static final AtomicLong executionIdCounter = new AtomicLong(3000);
static {
AbiDetect.class.getName();
Config.class.getName();
@@ -50,14 +62,43 @@ public class FFmpeg {
* <p>Synchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
*/
public static int execute(final String[] arguments) {
final int lastReturnCode = Config.nativeFFmpegExecute(arguments);
return Config.ffmpegExecute(DEFAULT_EXECUTION_ID, arguments);
}
Config.setLastReturnCode(lastReturnCode);
/**
* <p>Asynchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback) {
final long newExecutionId = executionIdCounter.incrementAndGet();
return lastReturnCode;
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return newExecutionId;
}
/**
* <p>Asynchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback, final Executor executor) {
final long newExecutionId = executionIdCounter.incrementAndGet();
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(executor);
return newExecutionId;
}
/**
@@ -66,7 +107,7 @@ public class FFmpeg {
*
* @param command FFmpeg command
* @param delimiter delimiter used to split arguments
* @return zero on successful execution, 255 on user cancel and non-zero on error
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
* @since 3.0
* @deprecated argument splitting mechanism used in this method is pretty simple and prone to
* errors. Consider using a more advanced method like {@link #execute(String)} or
@@ -82,18 +123,76 @@ public class FFmpeg {
* your command.
*
* @param command FFmpeg command
* @return zero on successful execution, 255 on user cancel and non-zero on error
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
*/
public static int execute(final String command) {
return execute(parseArguments(command));
}
/**
* <p>Cancels an ongoing operation. This function does not wait for termination to complete and
* returns immediately.
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
*
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String command, final ExecuteCallback executeCallback) {
final long newExecutionId = executionIdCounter.incrementAndGet();
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return newExecutionId;
}
/**
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
*
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String command, final ExecuteCallback executeCallback, final Executor executor) {
final long newExecutionId = executionIdCounter.incrementAndGet();
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(executor);
return newExecutionId;
}
/**
* <p>Cancels an ongoing operation.
*
* <p>This function does not wait for termination to complete and returns immediately.
*/
public static void cancel() {
Config.nativeFFmpegCancel();
Config.nativeFFmpegCancel(DEFAULT_EXECUTION_ID);
}
/**
* <p>Cancels an ongoing operation.
*
* <p>This function does not wait for termination to complete and returns immediately.
*
* @param executionId id of the execution
*/
public static void cancel(final long executionId) {
Config.nativeFFmpegCancel(executionId);
}
/**
* <p>Lists ongoing executions.
*
* @return list of ongoing executions
*/
public static List<FFmpegExecution> listExecutions() {
return Config.listFFmpegExecutions();
}
/**
@@ -153,4 +252,26 @@ public class FFmpeg {
return argumentList.toArray(new String[0]);
}
/**
* <p>Combines arguments into a string.
*
* @param arguments arguments
* @return string containing all arguments
*/
static String argumentsToString(final String[] arguments) {
if (arguments == null) {
return "null";
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < arguments.length; i++) {
if (i > 0) {
stringBuilder.append(" ");
}
stringBuilder.append(arguments[i]);
}
return stringBuilder.toString();
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import java.util.Date;
/**
* <p>Represents an ongoing FFmpeg execution.
*
* @author Taner Sener
* @since v4.4
*/
public class FFmpegExecution {
private final Date startTime;
private final long executionId;
private final String command;
public FFmpegExecution(final long executionId, final String[] arguments) {
this.startTime = new Date();
this.executionId = executionId;
this.command = FFmpeg.argumentsToString(arguments);
}
public Date getStartTime() {
return startTime;
}
public long getExecutionId() {
return executionId;
}
public String getCommand() {
return command;
}
}
@@ -72,7 +72,7 @@ public class FFprobe {
}
/**
* <p>Returns media information for given file.
* <p>Returns media information for the given file.
*
* <p>This method does not support executing multiple concurrent operations. If you execute
* multiple operations (execute or getMediaInformation) at the same time, the response of this
@@ -83,14 +83,22 @@ public class FFprobe {
* @since 3.0
*/
public static MediaInformation getMediaInformation(final String path) {
final int rc = execute(new String[]{"-v", "info", "-hide_banner", "-i", path});
return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path});
}
if (rc == 0) {
return MediaInformationParser.from(Config.getLastCommandOutput());
} else {
Log.i(Config.TAG, Config.getLastCommandOutput());
return null;
}
/**
* <p>Returns media information for the given command.
*
* <p>This method does not support executing multiple concurrent operations. If you execute
* multiple operations (execute or getMediaInformation) at the same time, the response of this
* method is not predictable.
*
* @param command command to execute
* @return media information
* @since 4.3.3
*/
public static MediaInformation getMediaInformationFromCommand(final String command) {
return getMediaInformationFromCommandArguments(FFmpeg.parseArguments(command));
}
/**
@@ -111,4 +119,15 @@ public class FFprobe {
return getMediaInformation(path);
}
private static MediaInformation getMediaInformationFromCommandArguments(final String[] arguments) {
final int rc = execute(arguments);
if (rc == 0) {
return MediaInformationParser.from(Config.getLastCommandOutput());
} else {
Log.w(Config.TAG, Config.getLastCommandOutput());
return null;
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to receive asynchronous getMediaInformation result.
*
* @author Taner Sener
*/
@FunctionalInterface
public interface GetMediaInformationCallback {
void apply(MediaInformation mediaInformation);
}
@@ -20,7 +20,7 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to redirect logs.
* <p>Represents a callback function to receive logs from running executions
*
* @author Taner Sener
* @since v2.1
@@ -20,21 +20,27 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a redirected log message.
* <p>Logs for running executions.
*
* @author Taner Sener
* @since v2.1
*/
public class LogMessage {
private final long executionId;
private final Level level;
private final String text;
public LogMessage(final Level level, final String text) {
public LogMessage(final long executionId, final Level level, final String text) {
this.executionId = executionId;
this.level = level;
this.text = text;
}
public long getExecutionId() {
return executionId;
}
public Level getLevel() {
return level;
}
@@ -43,4 +49,21 @@ public class LogMessage {
return text;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("LogMessage{");
stringBuilder.append("executionId=");
stringBuilder.append(executionId);
stringBuilder.append(", level=");
stringBuilder.append(level);
stringBuilder.append(", text=");
stringBuilder.append("\'");
stringBuilder.append(text);
stringBuilder.append('\'');
stringBuilder.append('}');
return stringBuilder.toString();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,11 +19,9 @@
package com.arthenica.mobileffmpeg;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONObject;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Media information class.
@@ -32,49 +30,38 @@ import java.util.Set;
*/
public class MediaInformation {
/**
* Format
*/
private String format;
private static final String KEY_MEDIA_PROPERTIES = "format";
private static final String KEY_FILENAME = "filename";
private static final String KEY_FORMAT = "format_name";
private static final String KEY_FORMAT_LONG = "format_long_name";
private static final String KEY_START_TIME = "start_time";
private static final String KEY_DURATION = "duration";
private static final String KEY_SIZE = "size";
private static final String KEY_BIT_RATE = "bit_rate";
private static final String KEY_TAGS = "tags";
/**
* Path
* Stores all properties.
*/
private String path;
private final JSONObject jsonObject;
/**
* Duration, in milliseconds
* Stores streams.
*/
private Long duration;
private final List<StreamInformation> streams;
public MediaInformation(final JSONObject jsonObject, final List<StreamInformation> streams) {
this.jsonObject = jsonObject;
this.streams = streams;
}
/**
* Start time, in milliseconds
* Returns file name.
*
* @return media file name
*/
private Long startTime;
/**
* Bitrate, kb/s
*/
private Long bitrate;
/**
* Metadata map
*/
private Map<String, String> metadata;
/**
* List of streams
*/
private List<StreamInformation> streams;
/**
* Raw unparsed media information
*/
private String rawInformation;
public MediaInformation() {
this.metadata = new HashMap<>();
this.streams = new ArrayList<>();
public String getFilename() {
return getStringProperty(KEY_FILENAME);
}
/**
@@ -83,34 +70,16 @@ public class MediaInformation {
* @return media format
*/
public String getFormat() {
return format;
return getStringProperty(KEY_FORMAT);
}
/**
* Sets media format.
* Returns long format.
*
* @param format media format
* @return media long format
*/
public void setFormat(String format) {
this.format = format;
}
/**
* Returns path.
*
* @return media path
*/
public String getPath() {
return path;
}
/**
* Sets path.
*
* @param path media path
*/
public void setPath(String path) {
this.path = path;
public String getLongFormat() {
return getStringProperty(KEY_FORMAT_LONG);
}
/**
@@ -118,17 +87,8 @@ public class MediaInformation {
*
* @return media duration in milliseconds
*/
public Long getDuration() {
return duration;
}
/**
* Sets duration.
*
* @param duration media duration in milliseconds
*/
public void setDuration(Long duration) {
this.duration = duration;
public String getDuration() {
return getStringProperty(KEY_DURATION);
}
/**
@@ -136,17 +96,17 @@ public class MediaInformation {
*
* @return media start time in milliseconds
*/
public Long getStartTime() {
return startTime;
public String getStartTime() {
return getStringProperty(KEY_START_TIME);
}
/**
* Sets start time.
* Returns size.
*
* @param startTime media start time in milliseconds
* @return media size in bytes
*/
public void setStartTime(Long startTime) {
this.startTime = startTime;
public String getSize() {
return getStringProperty(KEY_SIZE);
}
/**
@@ -154,63 +114,17 @@ public class MediaInformation {
*
* @return media bitrate in kb/s
*/
public Long getBitrate() {
return bitrate;
public String getBitrate() {
return getStringProperty(KEY_BIT_RATE);
}
/**
* Sets bitrate.
* Returns all tags.
*
* @param bitrate media bitrate in kb/s
* @return tags dictionary
*/
public void setBitrate(Long bitrate) {
this.bitrate = bitrate;
}
/**
* Returns unparsed media information.
*
* @return unparsed media information data
*/
public String getRawInformation() {
return rawInformation;
}
/**
* Sets unparsed media information.
*
* @param rawInformation unparsed media information data
*/
public void setRawInformation(String rawInformation) {
this.rawInformation = rawInformation;
}
/**
* Adds metadata.
*
* @param key metadata key
* @param value metadata value
*/
public void addMetadata(String key, String value) {
this.metadata.put(key, value);
}
/**
* Returns all metadata entries.
*
* @return set of metadata entries
*/
public Set<Map.Entry<String, String>> getMetadataEntries() {
return this.metadata.entrySet();
}
/**
* Adds new stream.
*
* @param stream new stream information
*/
public void addStream(StreamInformation stream) {
this.streams.add(stream);
public JSONObject getTags() {
return getProperties(KEY_TAGS);
}
/**
@@ -222,4 +136,75 @@ public class MediaInformation {
return streams;
}
/**
* Returns the media property associated with the key.
*
* @param key property key
* @return media property as string or null if the key is not found
*/
public String getStringProperty(final String key) {
JSONObject mediaProperties = getMediaProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optString(key);
} else {
return null;
}
}
/**
* Returns the media property associated with the key.
*
* @param key property key
* @return media property as Long or null if the key is not found
*/
public Long getNumberProperty(String key) {
JSONObject mediaProperties = getMediaProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optLong(key);
} else {
return null;
}
}
/**
* Returns the media properties associated with the key.
*
* @param key properties key
* @return media properties as a JSONObject or null if the key is not found
*/
public JSONObject getProperties(String key) {
JSONObject mediaProperties = getMediaProperties();
if (mediaProperties == null) {
return null;
}
return mediaProperties.optJSONObject(key);
}
/**
* Returns all media properties.
*
* @return all media properties as a JSONObject or null if no media properties are defined
*/
public JSONObject getMediaProperties() {
return jsonObject.optJSONObject(KEY_MEDIA_PROPERTIES);
}
/**
* Returns all properties defined.
*
* @return all properties as a JSONObject or null if no properties are defined
*/
public JSONObject getAllProperties() {
return jsonObject;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -21,505 +21,55 @@ package com.arthenica.mobileffmpeg;
import android.util.Log;
import com.arthenica.mobileffmpeg.util.Pair;
import com.arthenica.mobileffmpeg.util.Trio;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ArrayList;
/**
* Helper class for {@link MediaInformation}.
* Helper class for parsing {@link MediaInformation}.
*
* @since 3.0
*/
public class MediaInformationParser {
public static SimpleDateFormat DURATION_FORMAT;
public static Date REFERENCE_DURATION;
static {
/**
* Extracts MediaInformation from the given ffprobe json output.
*
* @param ffprobeJsonOutput ffprobe json output
* @return created {@link MediaInformation} instance of null if a parsing error occurs
*/
public static MediaInformation from(final String ffprobeJsonOutput) {
try {
DURATION_FORMAT = new SimpleDateFormat("kk:mm:ss", Locale.getDefault());
REFERENCE_DURATION = DURATION_FORMAT.parse("00:00:00");
} catch (final ParseException e) {
Log.i(Config.TAG, "Preparing duration reference failed.", e);
DURATION_FORMAT = null;
REFERENCE_DURATION = null;
return fromWithError(ffprobeJsonOutput);
} catch (JSONException e) {
Log.e(Config.TAG, "MediaInformation parsing failed.", e);
e.printStackTrace();
return null;
}
}
/**
* Parses media information command output and builds a {@link MediaInformation} instance.
* Extracts MediaInformation from the given ffprobe json output.
*
* @param rawCommandOutput media information command output
* @return parsed instance of null if a parsing error occurs
* @param ffprobeJsonOutput ffprobe json output
* @return created {@link MediaInformation} instance
* @throws JSONException if a parsing error occurs
*/
public static MediaInformation from(final String rawCommandOutput) {
final MediaInformation mediaInformation = new MediaInformation();
public static MediaInformation fromWithError(final String ffprobeJsonOutput) throws JSONException {
JSONObject jsonObject = new JSONObject(ffprobeJsonOutput);
JSONArray streamArray = jsonObject.optJSONArray("streams");
if (rawCommandOutput != null) {
final String[] split = rawCommandOutput.split("\n");
boolean metadata = false;
boolean sidedata = false;
StreamInformation lastCreatedStream = null;
final StringBuilder rawInformation = new StringBuilder();
for (final String outputLine : split) {
if (outputLine.startsWith("[")) {
metadata = false;
sidedata = false;
continue;
}
final String trimmedLine = outputLine.trim();
if (trimmedLine.startsWith("Input")) {
metadata = false;
sidedata = false;
lastCreatedStream = null;
Pair<String, String> pair = parseInputBlock(trimmedLine);
mediaInformation.setFormat(pair.getFirst());
mediaInformation.setPath(pair.getSecond());
} else if (trimmedLine.startsWith("Duration")) {
metadata = false;
sidedata = false;
lastCreatedStream = null;
Trio<Long, Long, Long> trio = parseDurationBlock(trimmedLine);
mediaInformation.setDuration(trio.getFirst());
mediaInformation.setStartTime(trio.getSecond());
mediaInformation.setBitrate(trio.getThird());
} else if (trimmedLine.toLowerCase(Locale.ENGLISH).startsWith("metadata")) {
sidedata = false;
metadata = true;
} else if (trimmedLine.toLowerCase(Locale.ENGLISH).startsWith("side data")) {
metadata = false;
sidedata = true;
} else if (trimmedLine.startsWith("Stream mapping") || trimmedLine.startsWith("Press [q] to stop") || trimmedLine.startsWith("Output")) {
break;
} else if (trimmedLine.startsWith("Stream")) {
metadata = false;
sidedata = false;
lastCreatedStream = MediaInformationParser.parseStreamBlock(trimmedLine);
mediaInformation.addStream(lastCreatedStream);
} else if (metadata) {
Pair<String, String> pair = parseMetadataBlock(trimmedLine);
if (pair.getFirst() != null && pair.getSecond() != null) {
if (lastCreatedStream != null) {
lastCreatedStream.addMetadata(pair.getFirst(), pair.getSecond());
} else {
mediaInformation.addMetadata(pair.getFirst(), pair.getSecond());
}
}
} else if (sidedata) {
Pair<String, String> pair = parseMetadataBlock(trimmedLine);
if (pair.getFirst() != null && pair.getSecond() != null) {
if (lastCreatedStream != null) {
lastCreatedStream.addSidedata(pair.getFirst(), pair.getSecond());
}
}
}
rawInformation.append(outputLine);
rawInformation.append("\n");
}
mediaInformation.setRawInformation(rawInformation.toString());
}
return mediaInformation;
}
static Pair<String, String> parseInputBlock(final String input) {
String format = substring(input, ",", ", from", Collections.<String>emptyList());
String path = substring(input, "\'", "\'", Collections.<String>emptyList());
return new Pair<>(format, path);
}
static Trio<Long, Long, Long> parseDurationBlock(final String line) {
Long duration = parseDuration(substring(line, "Duration:", ",", Collections.singletonList("uration:")));
Long start = parseStartTime(substring(line, "start:", ",", Collections.singletonList("tart:")));
Long bitrate = toLongObject(substring(line, "bitrate:", Arrays.asList("itrate:", "kb/s")));
return new Trio<>(duration, start, bitrate);
}
static Pair<String, String> parseMetadataBlock(final String metadata) {
String key = null;
String value = null;
if (metadata != null) {
int index = metadata.indexOf(':');
if (index > -1) {
key = metadata.substring(0, index).trim();
value = metadata.substring(index + 1).trim();
ArrayList<StreamInformation> arrayList = new ArrayList<>();
for (int i = 0; streamArray != null && i < streamArray.length(); i++) {
JSONObject streamObject = streamArray.optJSONObject(i);
if (streamObject != null) {
arrayList.add(new StreamInformation(streamObject));
}
}
return new Pair<>(key, value);
}
static StreamInformation parseStreamBlock(final String input) {
final StreamInformation streamInformation = new StreamInformation();
if (input != null) {
streamInformation.setIndex(parseStreamIndex(input));
int typeBlockStartIndex = index(input, ":", 0, 2);
if (typeBlockStartIndex > -1 && (typeBlockStartIndex < input.length())) {
String[] parts = input.substring(typeBlockStartIndex + 1).split(",");
String typePart = safeGet(parts, 0);
final String type = parseStreamType(typePart);
streamInformation.setType(type);
streamInformation.setCodec(parseStreamCodec(typePart));
streamInformation.setFullCodec(parseStreamFullCodec(typePart));
String part2 = safeGet(parts, 1);
String part3 = safeGet(parts, 2);
String part4 = safeGet(parts, 3);
String part5 = safeGet(parts, 4);
if ("video".equals(type)) {
int lastUsedPart = 1;
if (part2 != null) {
int pStart = count(part2, "(");
int pEnd = count(part2, ")");
while (pStart != pEnd) {
lastUsedPart++;
String newPart = safeGet(parts, lastUsedPart);
if (newPart == null) {
break;
}
part2 = String.format("%s,%s", part2, newPart);
pStart = count(part2, "(");
pEnd = count(part2, ")");
}
streamInformation.setFullFormat(part2.toLowerCase(Locale.getDefault()).trim());
streamInformation.setFormat(part2.replaceAll("\\(.*\\)", "").toLowerCase(Locale.getDefault()).trim());
}
lastUsedPart++;
String videoDimensionPart = safeGet(parts, lastUsedPart);
if (videoDimensionPart != null) {
String videoLayout = videoDimensionPart.toLowerCase(Locale.getDefault()).trim();
Pair<Long, Long> dimensions = parseVideoDimensions(videoLayout);
streamInformation.setWidth(dimensions.getFirst());
streamInformation.setHeight(dimensions.getSecond());
streamInformation.setSampleAspectRatio(parseVideoStreamSampleAspectRatio(videoLayout));
streamInformation.setDisplayAspectRatio(parseVideoStreamDisplayAspectRatio(videoLayout));
}
for (int i = lastUsedPart + 1; i < parts.length; i++) {
String part = parts[i].replaceAll("\\(.*\\)", "").toLowerCase(Locale.getDefault());
if (part.contains("kb/s")) {
streamInformation.setBitrate(toLongObject(part.replaceAll("kb/s", "").trim()));
} else if (part.contains("fps")) {
streamInformation.setAverageFrameRate(part.replaceAll("fps", "").trim());
} else if (part.contains("tbr")) {
streamInformation.setRealFrameRate(part.replaceAll("tbr", "").trim());
} else if (part.contains("tbn")) {
streamInformation.setTimeBase(part.replaceAll("tbn", "").trim());
} else if (part.contains("tbc")) {
streamInformation.setCodecTimeBase(part.replaceAll("tbc", "").trim());
}
}
} else if ("audio".equals(type)) {
if (part2 != null) {
streamInformation.setSampleRate(parseAudioStreamSampleRate(part2));
}
if (part3 != null) {
streamInformation.setChannelLayout(part3.toLowerCase(Locale.getDefault()).trim());
}
if (part4 != null) {
streamInformation.setSampleFormat(part4.toLowerCase(Locale.getDefault()).trim());
}
if (part5 != null) {
streamInformation.setBitrate(toLongObject(part5.toLowerCase(Locale.getDefault()).replaceAll("\\(.*\\)", "").replaceAll("kb/s", "").trim()));
}
} else if ("data".equals(type)) {
if (part2 != null) {
streamInformation.setBitrate(toLongObject(part2.toLowerCase(Locale.getDefault()).replaceAll("\\(.*\\)", "").replaceAll("kb/s", "").trim()));
}
}
}
}
return streamInformation;
}
static Pair<Long, Long> parseVideoDimensions(final String input) {
Long width = null;
Long height = null;
if (input != null) {
final String[] dimensions = input.toLowerCase(Locale.getDefault()).replaceAll("\\[.*\\]", "").trim().split("x");
width = toLongObject(safeGet(dimensions, 0));
height = toLongObject(safeGet(dimensions, 1));
}
return new Pair<>(width, height);
}
static String parseVideoStreamSampleAspectRatio(final String input) {
if (input != null) {
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
for (int i = 0; i < parts.length; i++) {
if (parts[i].toLowerCase(Locale.getDefault()).equals("sar")) {
return safeGet(parts, i + 1);
}
}
}
return null;
}
static String parseVideoStreamDisplayAspectRatio(final String input) {
if (input != null) {
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
for (int i = 0; i < parts.length; i++) {
if (parts[i].toLowerCase(Locale.getDefault()).equals("dar")) {
return safeGet(parts, i + 1);
}
}
}
return null;
}
static Long parseAudioStreamSampleRate(final String input) {
if (input != null) {
boolean khz = false;
boolean mhz = false;
String lowerCase = input.toLowerCase(Locale.getDefault());
if (lowerCase.contains("khz")) {
khz = true;
}
if (lowerCase.contains("mhz")) {
mhz = true;
}
String sampleRate = lowerCase
.replaceAll("khz", "")
.replaceAll("mhz", "")
.replaceAll("hz", "")
.trim();
if (khz) {
return 1000 * toLong(sampleRate);
} else if (mhz) {
return 1000000 * toLong(sampleRate);
} else {
return toLong(sampleRate);
}
}
return null;
}
static String parseStreamType(final String input) {
if (input != null) {
if (input.toLowerCase(Locale.getDefault()).contains("audio:")) {
return "audio";
} else if (input.toLowerCase(Locale.getDefault()).contains("video:")) {
return "video";
} else if (input.toLowerCase(Locale.getDefault()).contains("data:")) {
return "data";
}
}
return null;
}
static String parseStreamCodec(final String input) {
if (input != null) {
return input.toLowerCase(Locale.getDefault())
.replaceAll("\\(.*\\)", "")
.replaceAll("video:", "")
.replaceAll("audio:", "")
.replaceAll("data:", "")
.trim();
}
return null;
}
static String parseStreamFullCodec(final String input) {
if (input != null) {
return input.toLowerCase(Locale.getDefault())
.replaceAll("video:", "")
.replaceAll("audio:", "")
.replaceAll("data:", "")
.trim();
}
return null;
}
static Long parseStreamIndex(final String input) {
String substring = substring(input, "Stream #0:", ":", Collections.singletonList("tream #0"));
if (substring != null) {
// DISCARD PARANTHESIS
return toLongObject(substring
.replace(":", "")
.replaceAll("\\(.*\\)", ""));
}
return null;
}
static Long parseDuration(final String duration) {
if (duration == null || duration.equals("N/A")) {
return null;
}
try {
final Date calculated = DURATION_FORMAT.parse(duration);
long secondsPartInMilliseconds = calculated.getTime() - REFERENCE_DURATION.getTime();
int index = duration.indexOf('.');
if (index > -1) {
Long centiSeconds = toLong(duration.substring(index + 1));
secondsPartInMilliseconds += 10 * centiSeconds;
}
return secondsPartInMilliseconds;
} catch (final ParseException e) {
Log.d(Config.TAG, String.format("Parsing duration: %s failed.", duration), e);
return null;
}
}
static Long parseStartTime(final String startTime) {
if (startTime == null || startTime.equals("N/A")) {
return null;
}
try {
BigDecimal bigDecimal = new BigDecimal(startTime);
bigDecimal = bigDecimal.setScale(3, BigDecimal.ROUND_CEILING).multiply(new BigDecimal(1000));
return bigDecimal.longValue();
} catch (NumberFormatException e) {
Log.d(Config.TAG, String.format("Parsing startTime: %s failed.", startTime), e);
return null;
}
}
public static String substring(final String string, final String start, final String end, final List<String> ignoredTokens) {
String extractedSubstring = null;
if (string != null) {
int formatStart = string.indexOf(start);
if (formatStart > -1) {
int formatEnd = string.indexOf(end, formatStart + start.length());
if (formatEnd > -1) {
extractedSubstring = string.substring(formatStart + start.length(), formatEnd);
}
}
}
if ((ignoredTokens != null) && (extractedSubstring != null)) {
for (String token : ignoredTokens) {
extractedSubstring = extractedSubstring.replaceAll(token, "");
}
}
return (extractedSubstring == null) ? null : extractedSubstring.trim();
}
public static String substring(final String string, final String start, final List<String> ignoredTokens) {
String extractedSubstring = null;
if (string != null) {
int formatStart = string.indexOf(start);
if (formatStart > -1) {
extractedSubstring = string.substring(formatStart + 1);
}
}
if ((ignoredTokens != null) && (extractedSubstring != null)) {
for (String token : ignoredTokens) {
extractedSubstring = extractedSubstring.replaceAll(token, "");
}
}
return (extractedSubstring == null) ? null : extractedSubstring.trim();
}
public static int index(final String string, String substring, int startIndex, int n) {
int count = 1;
while (count <= n) {
startIndex = string.indexOf(substring, startIndex + substring.length());
count++;
}
return startIndex;
}
public static int count(final String string, String substring) {
int count = 0;
int index = 0;
do {
index = string.indexOf(substring, index);
if (index >= 0) {
count++;
index = index + substring.length();
}
} while (index >= 0);
return count;
}
private static <K> K safeGet(final K[] array, final int index) {
if (array == null) {
return null;
}
try {
final int size = array.length;
if (size > index) {
return array[index];
} else {
return null;
}
} catch (final ArrayIndexOutOfBoundsException e) {
return null;
}
}
private static long toLong(final String value) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return 0;
}
}
static Long toLongObject(final String value) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return null;
}
return new MediaInformation(jsonObject, arrayList);
}
}
@@ -54,6 +54,7 @@ class Packages {
supportedExternalLibraries.add("opencore-amr");
supportedExternalLibraries.add("openh264");
supportedExternalLibraries.add("opus");
supportedExternalLibraries.add("rubberband");
supportedExternalLibraries.add("sdl2");
supportedExternalLibraries.add("shine");
supportedExternalLibraries.add("snappy");
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
/**
* <p>Lists signals handled by MobileFFmpeg library.
*
* @author Taner Sener
* @since v4.4
*/
public enum Signal {
SIGINT(2),
SIGQUIT(3),
SIGPIPE(13),
SIGTERM(15),
SIGXCPU(24);
private int value;
Signal(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
@@ -20,13 +20,14 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents statistics data.
* <p>Statistics for running executions.
*
* @author Taner Sener
* @since v2.1
*/
public class Statistics {
private long executionId;
private int videoFrameNumber;
private float videoFps;
private float videoQuality;
@@ -36,6 +37,7 @@ public class Statistics {
private double speed;
public Statistics() {
executionId = 0;
videoFrameNumber = 0;
videoFps = 0;
videoQuality = 0;
@@ -45,7 +47,8 @@ public class Statistics {
speed = 0;
}
public Statistics(int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
public Statistics(long executionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
this.executionId = executionId;
this.videoFrameNumber = videoFrameNumber;
this.videoFps = videoFps;
this.videoQuality = videoQuality;
@@ -57,35 +60,44 @@ public class Statistics {
public void update(final Statistics newStatistics) {
if (newStatistics != null) {
this.executionId = newStatistics.getExecutionId();
if (newStatistics.getVideoFrameNumber() > 0) {
this.videoFrameNumber = newStatistics.getVideoFrameNumber();
}
if (newStatistics.getVideoFps() > 0){
if (newStatistics.getVideoFps() > 0) {
this.videoFps = newStatistics.getVideoFps();
}
if (newStatistics.getVideoQuality() > 0){
if (newStatistics.getVideoQuality() > 0) {
this.videoQuality = newStatistics.getVideoQuality();
}
if (newStatistics.getSize() > 0){
if (newStatistics.getSize() > 0) {
this.size = newStatistics.getSize();
}
if (newStatistics.getTime() > 0){
if (newStatistics.getTime() > 0) {
this.time = newStatistics.getTime();
}
if (newStatistics.getBitrate() > 0){
if (newStatistics.getBitrate() > 0) {
this.bitrate = newStatistics.getBitrate();
}
if (newStatistics.getSpeed() > 0){
if (newStatistics.getSpeed() > 0) {
this.speed = newStatistics.getSpeed();
}
}
}
public long getExecutionId() {
return executionId;
}
public void setExecutionId(long executionId) {
this.executionId = executionId;
}
public int getVideoFrameNumber() {
return videoFrameNumber;
}
@@ -142,4 +154,30 @@ public class Statistics {
this.speed = speed;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Statistics{");
stringBuilder.append("executionId=");
stringBuilder.append(executionId);
stringBuilder.append(", videoFrameNumber=");
stringBuilder.append(videoFrameNumber);
stringBuilder.append(", videoFps=");
stringBuilder.append(videoFps);
stringBuilder.append(", videoQuality=");
stringBuilder.append(videoQuality);
stringBuilder.append(", size=");
stringBuilder.append(size);
stringBuilder.append(", time=");
stringBuilder.append(time);
stringBuilder.append(", bitrate=");
stringBuilder.append(bitrate);
stringBuilder.append(", speed=");
stringBuilder.append(speed);
stringBuilder.append('}');
return stringBuilder.toString();
}
}
@@ -20,7 +20,7 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to receive statistics of running operation.
* <p>Represents a callback function to receive statistics from running executions.
*
* @author Taner Sener
* @since v2.1
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,9 +19,7 @@
package com.arthenica.mobileffmpeg;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.json.JSONObject;
/**
* Stream information class.
@@ -30,68 +28,32 @@ import java.util.Set;
*/
public class StreamInformation {
/**
* Stream index
*/
private Long index;
private String type;
private String codec;
private String fullCodec;
private String format;
private String fullFormat;
private Long width;
private Long height;
private Long bitrate;
private Long sampleRate;
private String sampleFormat;
private String channelLayout;
private static final String KEY_INDEX = "index";
private static final String KEY_TYPE = "codec_type";
private static final String KEY_CODEC = "codec_name";
private static final String KEY_CODEC_LONG = "codec_long_name";
private static final String KEY_FORMAT = "pix_fmt";
private static final String KEY_WIDTH = "width";
private static final String KEY_HEIGHT = "height";
private static final String KEY_BIT_RATE = "bit_rate";
private static final String KEY_SAMPLE_RATE = "sample_rate";
private static final String KEY_SAMPLE_FORMAT = "sample_fmt";
private static final String KEY_CHANNEL_LAYOUT = "channel_layout";
private static final String KEY_SAMPLE_ASPECT_RATIO = "sample_aspect_ratio";
private static final String KEY_DISPLAY_ASPECT_RATIO = "display_aspect_ratio";
private static final String KEY_AVERAGE_FRAME_RATE = "avg_frame_rate";
private static final String KEY_REAL_FRAME_RATE = "r_frame_rate";
private static final String KEY_TIME_BASE = "time_base";
private static final String KEY_CODEC_TIME_BASE = "codec_time_base";
private static final String KEY_TAGS = "tags";
/**
* SAR
* Stores all properties.
*/
private String sampleAspectRatio;
private final JSONObject jsonObject;
/**
* DAR
*/
private String displayAspectRatio;
/**
* fps
*/
private String averageFrameRate;
/**
* tbr
*/
private String realFrameRate;
/**
* tbn
*/
private String timeBase;
/**
* tbc
*/
private String codecTimeBase;
/**
* Metadata map
*/
private final Map<String, String> metadata;
/**
* Side data map
*/
private final Map<String, String> sidedata;
public StreamInformation() {
this.metadata = new HashMap<>();
this.sidedata = new HashMap<>();
public StreamInformation(final JSONObject jsonObject) {
this.jsonObject = jsonObject;
}
/**
@@ -100,16 +62,7 @@ public class StreamInformation {
* @return stream index, starting from zero
*/
public Long getIndex() {
return index;
}
/**
* Sets stream index.
*
* @param index stream index, starting from zero
*/
public void setIndex(Long index) {
this.index = index;
return getNumberProperty(KEY_INDEX);
}
/**
@@ -118,16 +71,7 @@ public class StreamInformation {
* @return stream type; audio or video
*/
public String getType() {
return type;
}
/**
* Sets stream type.
*
* @param type stream type; audio or video
*/
public void setType(String type) {
this.type = type;
return getStringProperty(KEY_TYPE);
}
/**
@@ -136,16 +80,7 @@ public class StreamInformation {
* @return stream codec
*/
public String getCodec() {
return codec;
}
/**
* Sets stream codec.
*
* @param codec stream codec
*/
public void setCodec(String codec) {
this.codec = codec;
return getStringProperty(KEY_CODEC);
}
/**
@@ -154,16 +89,7 @@ public class StreamInformation {
* @return stream codec with additional profile and mode information
*/
public String getFullCodec() {
return fullCodec;
}
/**
* Sets full stream codec.
*
* @param fullCodec stream codec with additional profile and mode information
*/
public void setFullCodec(String fullCodec) {
this.fullCodec = fullCodec;
return getStringProperty(KEY_CODEC_LONG);
}
/**
@@ -172,34 +98,7 @@ public class StreamInformation {
* @return stream format
*/
public String getFormat() {
return format;
}
/**
* Sets stream format.
*
* @param format stream format
*/
public void setFormat(String format) {
this.format = format;
}
/**
* Returns full stream format.
*
* @return stream format with
*/
public String getFullFormat() {
return fullFormat;
}
/**
* Sets full stream format.
*
* @param fullFormat stream format with
*/
public void setFullFormat(String fullFormat) {
this.fullFormat = fullFormat;
return getStringProperty(KEY_FORMAT);
}
/**
@@ -208,16 +107,7 @@ public class StreamInformation {
* @return width in pixels
*/
public Long getWidth() {
return width;
}
/**
* Sets width.
*
* @param width width in pixels
*/
public void setWidth(Long width) {
this.width = width;
return getNumberProperty(KEY_WIDTH);
}
/**
@@ -226,16 +116,7 @@ public class StreamInformation {
* @return height in pixels
*/
public Long getHeight() {
return height;
}
/**
* Sets height.
*
* @param height height in pixels
*/
public void setHeight(Long height) {
this.height = height;
return getNumberProperty(KEY_HEIGHT);
}
/**
@@ -243,17 +124,8 @@ public class StreamInformation {
*
* @return bitrate in kb/s
*/
public Long getBitrate() {
return bitrate;
}
/**
* Sets bitrate.
*
* @param bitrate bitrate in kb/s
*/
public void setBitrate(Long bitrate) {
this.bitrate = bitrate;
public String getBitrate() {
return getStringProperty(KEY_BIT_RATE);
}
/**
@@ -261,17 +133,8 @@ public class StreamInformation {
*
* @return sample rate in hz
*/
public Long getSampleRate() {
return sampleRate;
}
/**
* Sets sample rate.
*
* @param sampleRate sample rate in hz
*/
public void setSampleRate(Long sampleRate) {
this.sampleRate = sampleRate;
public String getSampleRate() {
return getStringProperty(KEY_SAMPLE_RATE);
}
/**
@@ -280,16 +143,7 @@ public class StreamInformation {
* @return sample format
*/
public String getSampleFormat() {
return sampleFormat;
}
/**
* Sets sample format.
*
* @param sampleFormat sample format
*/
public void setSampleFormat(String sampleFormat) {
this.sampleFormat = sampleFormat;
return getStringProperty(KEY_SAMPLE_FORMAT);
}
/**
@@ -298,16 +152,7 @@ public class StreamInformation {
* @return channel layout
*/
public String getChannelLayout() {
return channelLayout;
}
/**
* Sets channel layout.
*
* @param channelLayout channel layout
*/
public void setChannelLayout(String channelLayout) {
this.channelLayout = channelLayout;
return getStringProperty(KEY_CHANNEL_LAYOUT);
}
/**
@@ -316,16 +161,7 @@ public class StreamInformation {
* @return sample aspect ratio
*/
public String getSampleAspectRatio() {
return sampleAspectRatio;
}
/**
* Sets sample aspect ratio.
*
* @param sampleAspectRatio sample aspect ratio
*/
public void setSampleAspectRatio(String sampleAspectRatio) {
this.sampleAspectRatio = sampleAspectRatio;
return getStringProperty(KEY_SAMPLE_ASPECT_RATIO);
}
/**
@@ -334,16 +170,7 @@ public class StreamInformation {
* @return display aspect ratio
*/
public String getDisplayAspectRatio() {
return displayAspectRatio;
}
/**
* Sets display aspect ratio.
*
* @param displayAspectRatio display aspect ratio
*/
public void setDisplayAspectRatio(String displayAspectRatio) {
this.displayAspectRatio = displayAspectRatio;
return getStringProperty(KEY_DISPLAY_ASPECT_RATIO);
}
/**
@@ -352,16 +179,7 @@ public class StreamInformation {
* @return average frame rate in fps
*/
public String getAverageFrameRate() {
return averageFrameRate;
}
/**
* Sets average frame rate.
*
* @param averageFrameRate average frame rate in fps
*/
public void setAverageFrameRate(String averageFrameRate) {
this.averageFrameRate = averageFrameRate;
return getStringProperty(KEY_AVERAGE_FRAME_RATE);
}
/**
@@ -370,16 +188,7 @@ public class StreamInformation {
* @return real frame rate in tbr
*/
public String getRealFrameRate() {
return realFrameRate;
}
/**
* Sets real frame rate.
*
* @param realFrameRate real frame rate in tbr
*/
public void setRealFrameRate(String realFrameRate) {
this.realFrameRate = realFrameRate;
return getStringProperty(KEY_REAL_FRAME_RATE);
}
/**
@@ -388,16 +197,7 @@ public class StreamInformation {
* @return time base in tbn
*/
public String getTimeBase() {
return timeBase;
}
/**
* Sets time base.
*
* @param timeBase time base in tbn
*/
public void setTimeBase(String timeBase) {
this.timeBase = timeBase;
return getStringProperty(KEY_TIME_BASE);
}
/**
@@ -406,74 +206,78 @@ public class StreamInformation {
* @return codec time base in tbc
*/
public String getCodecTimeBase() {
return codecTimeBase;
return getStringProperty(KEY_CODEC_TIME_BASE);
}
/**
* Sets codec time base.
* Returns all tags.
*
* @param codecTimeBase codec time base in tbc
* @return tags dictionary
*/
public void setCodecTimeBase(String codecTimeBase) {
this.codecTimeBase = codecTimeBase;
public JSONObject getTags() {
return getProperties(KEY_TAGS);
}
/**
* Adds metadata.
* Returns the stream property associated with the key.
*
* @param key metadata key
* @param value metadata value
* @param key property key
* @return stream property as string or null if the key is not found
*/
public void addMetadata(String key, String value) {
this.metadata.put(key, value);
public String getStringProperty(final String key) {
JSONObject mediaProperties = getAllProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optString(key);
} else {
return null;
}
}
/**
* Retrieves metadata value associated with this key.
* Returns the stream property associated with the key.
*
* @param key metadata key
* @return metadata value associated with this key
* @param key property key
* @return stream property as Long or null if the key is not found
*/
public String getMetadata(String key) {
return this.metadata.get(key);
public Long getNumberProperty(String key) {
JSONObject mediaProperties = getAllProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optLong(key);
} else {
return null;
}
}
/**
* Returns all metadata entries.
* Returns the stream properties associated with the key.
*
* @return set of metadata entries
* @param key properties key
* @return stream properties as a JSONObject or null if the key is not found
*/
public Set<Map.Entry<String, String>> getMetadataEntries() {
return this.metadata.entrySet();
public JSONObject getProperties(String key) {
JSONObject mediaProperties = getAllProperties();
if (mediaProperties == null) {
return null;
}
return mediaProperties.optJSONObject(key);
}
/**
* Adds side data.
* Returns all stream properties defined.
*
* @param key side data key
* @param value side data value
* @return all stream properties as a JSONObject or null if no properties are defined
*/
public void addSidedata(String key, String value) {
this.sidedata.put(key, value);
}
/**
* Retrieves side data value associated with this key.
*
* @param key side data key
* @return side data value associated with this key
*/
public String getSidedata(String key) {
return this.sidedata.get(key);
}
/**
* Returns all side data entries.
*
* @return set of site data entries
*/
public Set<Map.Entry<String, String>> getSidedataEntries() {
return this.sidedata.entrySet();
public JSONObject getAllProperties() {
return jsonObject;
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (c) 2018-2019 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFmpeg;
public class AsyncSingleFFmpegExecuteTask extends AsyncTask<String, Integer, Integer> {
private final String command;
private final SingleExecuteCallback singleExecuteCallback;
public AsyncSingleFFmpegExecuteTask(final String command, final SingleExecuteCallback singleExecuteCallback) {
this.command = command;
this.singleExecuteCallback = singleExecuteCallback;
}
@Override
protected Integer doInBackground(final String... arguments) {
return FFmpeg.execute(command);
}
@Override
protected void onPostExecute(final Integer rc) {
if (singleExecuteCallback != null) {
singleExecuteCallback.apply(rc, Config.getLastCommandOutput());
}
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (c) 2018-2019 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFprobe;
public class AsyncSingleFFprobeExecuteTask extends AsyncTask<String, Integer, Integer> {
private final String command;
private final SingleExecuteCallback singleExecuteCallback;
public AsyncSingleFFprobeExecuteTask(final String command, final SingleExecuteCallback singleExecuteCallback) {
this.command = command;
this.singleExecuteCallback = singleExecuteCallback;
}
@Override
protected Integer doInBackground(final String... arguments) {
return FFprobe.execute(command);
}
@Override
protected void onPostExecute(final Integer rc) {
if (singleExecuteCallback != null) {
singleExecuteCallback.apply(rc, Config.getLastCommandOutput());
}
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (c) 2018-2019 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.FFprobe;
import com.arthenica.mobileffmpeg.MediaInformation;
public class AsyncSingleGetMediaInformationTask extends AsyncTask<String, MediaInformation, MediaInformation> {
private final String path;
private final SingleGetMediaInformationCallback singleGetMediaInformationCallback;
public AsyncSingleGetMediaInformationTask(final String path, final SingleGetMediaInformationCallback singleGetMediaInformationCallback) {
this.path = path;
this.singleGetMediaInformationCallback = singleGetMediaInformationCallback;
}
@Override
protected MediaInformation doInBackground(final String... arguments) {
return FFprobe.getMediaInformation(path);
}
@Override
protected void onPostExecute(final MediaInformation mediaInformation) {
if (singleGetMediaInformationCallback != null) {
singleGetMediaInformationCallback.apply(mediaInformation);
}
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (c) 2018 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
/**
* <p>Represents a callback function to receive a single execution result.
*
* @author Taner Sener
* @since v2.1
*/
@FunctionalInterface
public interface SingleExecuteCallback {
void apply(int returnCode, String executeOutput);
}
@@ -1,34 +0,0 @@
/*
* Copyright (c) 2018 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
import com.arthenica.mobileffmpeg.MediaInformation;
/**
* <p>Represents a callback function to receive a single getMediaInformation result.
*
* @author Taner Sener
*/
@FunctionalInterface
public interface SingleGetMediaInformationCallback {
void apply(MediaInformation mediaInformation);
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
File diff suppressed because it is too large Load Diff
@@ -1,299 +0,0 @@
/*
* Copyright (c) 2018 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import com.arthenica.mobileffmpeg.util.Pair;
import com.arthenica.mobileffmpeg.util.Trio;
import org.junit.Assert;
import org.junit.Test;
public class MediaInformationTest {
@Test
public void parseVideoStream() {
parseVideoStreamBlock(" Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 2560x1708 [SAR 1:1 DAR 640:427], 25 tbr, 25 tbn, 25 tbc", 0L, "mjpeg", "mjpeg", "yuvj420p", "yuvj420p(pc, bt470bg/unknown/unknown)", 2560L, 1708L, "1:1", "640:427", null, null, "25", "25", "25");
parseVideoStreamBlock(" Stream #0:0: Video: gif, bgra, 420x236, 6 fps, 6 tbr, 100 tbn, 100 tbc", 0L, "gif",
"gif", "bgra", "bgra", 420L, 236L, null, null, null, "6", "6", "100", "100");
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 7762 kb/s, 25 fps, 30 tbr, 15360 tbn, 60 tbc (default)", 0L, "h264", "h264 (main) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1280L, 720L, "1:1", "16:9", 7762L, "25", "30", "15360", "60");
parseVideoStreamBlock(" Stream #0:0: Video: png, rgba(pc), 544x184, 25 tbr, 25 tbn, 25 tbc", 0L, "png", "png", "rgba", "rgba(pc)", 544L, 184L, null, null, null, null, "25", "25", "25");
parseVideoStreamBlock(" Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 25 fps, 25 tbr, 1200k tbn, 50 tbc", 0L, "h264", "h264 (main)", "yuv420p", "yuv420p(tv, bt709, progressive)", 1920L, 1080L, null, null, null, "25", "25", "1200k", "50");
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 3840x4320 [SAR 1:1 DAR 8:9], 9902 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 3840L, 4320L, "1:1", "8:9", 9902L, "30", "30", "30k", "60");
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3992 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1920L, 1080L, "1:1", "16:9", 3992L, "30", "30", "30k", "60");
parseVideoStreamBlock(" Stream #0:0: Video: theora, yuv420p(bt470bg/bt470bg/bt709), 720x400, 25 fps, 25 tbr, 25 tbn, 25 tbc", 0L, "theora", "theora", "yuv420p", "yuv420p(bt470bg/bt470bg/bt709)", 720L, 400L, null, null, null, "25", "25", "25", "25");
}
@Test
public void parseAudioStream() {
parseAudioStreamBlock("Stream #0:0: Audio: adpcm_ms ([2][0][0][0] / 0x0002), 22050 Hz, stereo, s16, 176 kb/s", 0L, "adpcm_ms", "adpcm_ms ([2][0][0][0] / 0x0002)", 22050L, "stereo", "s16", 176L);
parseAudioStreamBlock("Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, mono, s16, 705 kb/s", 0L, "pcm_s16le", "pcm_s16le ([1][0][0][0] / 0x0001)", 44100L, "mono", "s16", 705L);
parseAudioStreamBlock("Stream #0:0: Audio: pcm_s24le ([1][0][0][0] / 0x0001), 48000 Hz, mono, s32 (24 bit), 1152 kb/s", 0L, "pcm_s24le", "pcm_s24le ([1][0][0][0] / 0x0001)", 48000L, "mono", "s32 (24 bit)", 1152L);
parseAudioStreamBlock("Stream #0:0: Audio: mp3, 48000 Hz, stereo, fltp, 192 kb/s", 0L, "mp3", "mp3", 48000L, "stereo", "fltp", 192L);
parseAudioStreamBlock("Stream #0:0: Audio: vorbis, 44100 Hz, stereo, fltp, 128 kb/s", 0L, "vorbis", "vorbis", 44100L, "stereo", "fltp", 128L);
parseAudioStreamBlock("Stream #0:0: Audio: pcm_u8 ([1][0][0][0] / 0x0001), 44100 Hz, stereo, u8, 705 kb/s", 0L, "pcm_u8", "pcm_u8 ([1][0][0][0] / 0x0001)", 44100L, "stereo", "u8", 705L);
parseAudioStreamBlock("Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
parseAudioStreamBlock("Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
parseAudioStreamBlock("Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
parseAudioStreamBlock("Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
}
@Test
public void index() {
Assert.assertEquals(7, MediaInformationParser.index("one:two:three:", ":", 0, 2));
Assert.assertEquals(13, MediaInformationParser.index("one:two:three:", ":", 0, 3));
Assert.assertEquals(8, MediaInformationParser.index("one::two::three::", "::", 0, 2));
}
@Test
public void count() {
Assert.assertEquals(3, MediaInformationParser.count("one:two:three:", ":"));
Assert.assertEquals(2, MediaInformationParser.count("one,two:three,four", ","));
}
@Test
public void parseAverageFrameRate() {
Assert.assertEquals("24", "24 fps".replaceAll("fps", "").trim());
Assert.assertEquals("30", "30 fps".replaceAll("fps", "").trim());
}
@Test
public void parseBitrate() {
Assert.assertEquals((Long) 3992L, MediaInformationParser.toLongObject("3992 kb/s".replaceAll("kb/s", "").trim()));
Assert.assertEquals((Long) 7762L, MediaInformationParser.toLongObject("7762 kb/s".replaceAll("kb/s", "").trim()));
}
@Test
public void parseVideoStreamDisplayAspectRatio() {
Assert.assertNull(MediaInformationParser.parseVideoStreamDisplayAspectRatio(""));
Assert.assertNull(MediaInformationParser.parseVideoStreamDisplayAspectRatio("544x184"));
Assert.assertEquals("640:427", MediaInformationParser.parseVideoStreamDisplayAspectRatio("2560x1708 [SAR 1:1 DAR 640:427]"));
Assert.assertEquals("8:9", MediaInformationParser.parseVideoStreamDisplayAspectRatio("3840x4320 [SAR 1:1 DAR 8:9]"));
}
@Test
public void parseVideoStreamSampleAspectRatio() {
Assert.assertNull(MediaInformationParser.parseVideoStreamSampleAspectRatio(""));
Assert.assertNull(MediaInformationParser.parseVideoStreamSampleAspectRatio("544x184"));
Assert.assertEquals("1:1", MediaInformationParser.parseVideoStreamSampleAspectRatio("2560x1708 [SAR 1:1 DAR 640:427]"));
Assert.assertEquals("1:1", MediaInformationParser.parseVideoStreamSampleAspectRatio("3840x4320 [SAR 1:1 DAR 8:9]"));
}
@Test
public void parseVideoDimensions() {
parseVideoDimensions("", null, null);
parseVideoDimensions("544x184", 544L, 184L);
parseVideoDimensions("720x400", 720L, 400L);
parseVideoDimensions("2560x1708 [SAR 1:1 DAR 640:427]", 2560L, 1708L);
parseVideoDimensions("3840x4320 [SAR 1:1 DAR 8:9]", 3840L, 4320L);
}
@Test
public void parseAudioSampleRate() {
Assert.assertEquals((Long) 44000L, MediaInformationParser.parseAudioStreamSampleRate("44000"));
Assert.assertEquals((Long) 44000L, MediaInformationParser.parseAudioStreamSampleRate("44 khz"));
Assert.assertEquals((Long) 44100L, MediaInformationParser.parseAudioStreamSampleRate("44100"));
Assert.assertEquals((Long) 5000000L, MediaInformationParser.parseAudioStreamSampleRate("5 mhz"));
}
@Test
public void parseAudioStreamType() {
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: theora"));
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: png"));
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: h264 (Main)"));
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: mp3 (mp4a / 0x6134706D)"));
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
}
@Test
public void parseStreamCodec() {
Assert.assertEquals("theora", MediaInformationParser.parseStreamCodec("Video: theora"));
Assert.assertEquals("png", MediaInformationParser.parseStreamCodec("Video: png"));
Assert.assertEquals("h264", MediaInformationParser.parseStreamCodec("Video: h264 (Main)"));
Assert.assertEquals("adpcm_ms", MediaInformationParser.parseStreamCodec("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
Assert.assertEquals("mp3", MediaInformationParser.parseStreamCodec("Audio: mp3 (mp4a / 0x6134706D)"));
Assert.assertEquals("pcm_u8", MediaInformationParser.parseStreamCodec("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
}
@Test
public void parseStreamFullCodec() {
Assert.assertEquals("theora", MediaInformationParser.parseStreamFullCodec("Video: theora"));
Assert.assertEquals("png", MediaInformationParser.parseStreamFullCodec("Video: png"));
Assert.assertEquals("h264 (main)", MediaInformationParser.parseStreamFullCodec("Video: h264 (Main)"));
Assert.assertEquals("adpcm_ms ([2][0][0][0] / 0x0002)", MediaInformationParser.parseStreamFullCodec("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
Assert.assertEquals("mp3 (mp4a / 0x6134706d)", MediaInformationParser.parseStreamFullCodec("Audio: mp3 (mp4a / 0x6134706D)"));
Assert.assertEquals("pcm_u8 ([1][0][0][0] / 0x0001)", MediaInformationParser.parseStreamFullCodec("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
}
@Test
public void parseStreamIndex() {
Assert.assertEquals((Long) 0L, MediaInformationParser.parseStreamIndex("Stream #0:0(und): Audio"));
Assert.assertEquals((Long) 1L, MediaInformationParser.parseStreamIndex("Stream #0:1(und): Video"));
Assert.assertEquals((Long) 2L, MediaInformationParser.parseStreamIndex("Stream #0:2(und): Audio"));
Assert.assertEquals((Long) 0L, MediaInformationParser.parseStreamIndex("Stream #0:0: Video"));
Assert.assertEquals((Long) 1L, MediaInformationParser.parseStreamIndex("Stream #0:1: Audio"));
Assert.assertEquals((Long) 2L, MediaInformationParser.parseStreamIndex("Stream #0:2: Video"));
}
@Test
public void parseDuration() {
parseDuration(null, null);
parseDuration("", null);
parseDuration("N/A", null);
parseDuration("00:03:33.24", 213240L);
parseDuration("00:10:34.53", 634530L);
parseDuration("00:00:00.04", 40L);
parseDuration("00:00:15.00", 15000L);
}
@Test
public void parseStartTime() {
parseStartTime(null, null);
parseStartTime("", null);
parseStartTime("N/A", null);
parseStartTime("0.000000", 0L);
parseStartTime("10.003000", 10003L);
parseStartTime("324.000000", 324000L);
parseStartTime("-4.000000", -4000L);
parseStartTime("14.00030", 14001L);
parseStartTime("14.00080", 14001L);
}
@Test
public void parseDurationBlock() {
parseDurationBlock(" Duration: 00:03:33.24, start: 0.000000, bitrate: 320 kb/s", 213240L, 0L, 320L);
parseDurationBlock(" Duration: 00:00:00.04, start: 0.000000, bitrate: 391187 kb/s", 40L, 0L, 391187L);
parseDurationBlock(" Duration: N/A, bitrate: N/A", null, null, null);
parseDurationBlock(" Duration: 00:00:15.00, start: 0.000000, bitrate: 7764 kb/s", 15000L, 0L, 7764L);
parseDurationBlock(" Duration: 00:10:34.53, start: 0.000000, bitrate: 4474 kb/s", 634530L, 0L, 4474L);
}
@Test
public void parseInputBlock() {
parseInputBlock("Input #0,", null, null);
parseInputBlock("Input #0, ogg, from 'trailer_400p.ogg':", "ogg", "trailer_400p.ogg");
parseInputBlock("Input #0, mp3, from 'beethoven_-_symphony_no_9.mp3':", "mp3", "beethoven_-_symphony_no_9.mp3");
parseInputBlock("Input #0, image2, from '/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg':", "image2", "/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg");
parseInputBlock("Input #0, gif, from 'advanced_zoom_in_and_pan_with_fade_in_out.gif':", "gif", "advanced_zoom_in_and_pan_with_fade_in_out.gif");
parseInputBlock("Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'transition_rotate.mp4':", "mov,mp4,m4a,3gp,3g2,mj2", "transition_rotate.mp4");
parseInputBlock("Input #0, png_pipe, from 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png':", "png_pipe", "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png");
}
@Test
public void parseMetadataBlock() {
parseMetadataBlock(" ENCODER:", "ENCODER", "");
parseMetadataBlock(" ENCODER:ffmpeg2theora 0.19", "ENCODER", "ffmpeg2theora 0.19");
parseMetadataBlock(" ENCODER : ffmpeg2theora 0.19", "ENCODER", "ffmpeg2theora 0.19");
parseMetadataBlock(" creation_time : 2013-12-16T17:50:04.000000Z", "creation_time", "2013-12-16T17:50:04.000000Z");
parseMetadataBlock(" handler_name : GPAC ISO Audio Handler", "handler_name", "GPAC ISO Audio Handler");
parseMetadataBlock(" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net", "comment", "Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net");
parseMetadataBlock(" minor_version : 1", "minor_version", "1");
parseMetadataBlock(" encoder : Lavf58.12.100", "encoder", "Lavf58.12.100");
parseMetadataBlock(" title : Planet X", "title", "Planet X");
parseMetadataBlock(" compatible_brands: isomiso2avc1mp41", "compatible_brands", "isomiso2avc1mp41");
}
private void parseVideoStreamBlock(String input, Long index, String codec, String fullCodec, String format, String fullFormat, Long width, Long height, String sampleAspectRatio, String displayAspectRatio, Long bitrate, String averageFrameRate, String realFrameRate, String timeBase, String codecTimeBase) {
StreamInformation stream = MediaInformationParser.parseStreamBlock(input);
Assert.assertNotNull(stream);
Assert.assertEquals(index, stream.getIndex());
Assert.assertEquals("video", stream.getType());
Assert.assertEquals(codec, stream.getCodec());
Assert.assertEquals(fullCodec, stream.getFullCodec());
Assert.assertEquals(format, stream.getFormat());
Assert.assertEquals(fullFormat, stream.getFullFormat());
Assert.assertEquals(width, stream.getWidth());
Assert.assertEquals(height, stream.getHeight());
Assert.assertEquals(sampleAspectRatio, stream.getSampleAspectRatio());
Assert.assertEquals(displayAspectRatio, stream.getDisplayAspectRatio());
Assert.assertEquals(bitrate, stream.getBitrate());
Assert.assertEquals(averageFrameRate, stream.getAverageFrameRate());
Assert.assertEquals(realFrameRate, stream.getRealFrameRate());
Assert.assertEquals(timeBase, stream.getTimeBase());
Assert.assertEquals(codecTimeBase, stream.getCodecTimeBase());
}
private void parseAudioStreamBlock(String input, Long index, String codec, String fullCodec, Long sampleRate, String channelLayout, String sampleFormat, Long bitrate) {
StreamInformation stream = MediaInformationParser.parseStreamBlock(input);
Assert.assertNotNull(stream);
Assert.assertEquals(index, stream.getIndex());
Assert.assertEquals("audio", stream.getType());
Assert.assertEquals(codec, stream.getCodec());
Assert.assertEquals(fullCodec, stream.getFullCodec());
Assert.assertEquals(sampleRate, stream.getSampleRate());
Assert.assertEquals(channelLayout, stream.getChannelLayout());
Assert.assertEquals(sampleFormat, stream.getSampleFormat());
Assert.assertEquals(bitrate, stream.getBitrate());
}
private void parseVideoDimensions(String value, Long width, Long height) {
Pair<Long, Long> videoDimensions = MediaInformationParser.parseVideoDimensions(value);
Assert.assertNotNull(videoDimensions);
Assert.assertEquals(width, videoDimensions.getFirst());
Assert.assertEquals(height, videoDimensions.getSecond());
}
private void parseDuration(String value, Long expected) {
Long duration = MediaInformationParser.parseDuration(value);
Assert.assertEquals(expected, duration);
}
private void parseStartTime(String value, Long expected) {
Long duration = MediaInformationParser.parseStartTime(value);
Assert.assertEquals(expected, duration);
}
private void parseDurationBlock(String value, Long first, Long second, Long third) {
Trio<Long, Long, Long> pair = MediaInformationParser.parseDurationBlock(value);
Assert.assertNotNull(pair);
Assert.assertEquals(first, pair.getFirst());
Assert.assertEquals(second, pair.getSecond());
Assert.assertEquals(third, pair.getThird());
}
private void parseInputBlock(String value, String first, String second) {
Pair<String, String> pair = MediaInformationParser.parseInputBlock(value);
Assert.assertNotNull(pair);
Assert.assertEquals(first, pair.getFirst());
Assert.assertEquals(second, pair.getSecond());
}
private void parseMetadataBlock(String value, String first, String second) {
Pair<String, String> pair = MediaInformationParser.parseMetadataBlock(value);
Assert.assertNotNull(pair);
Assert.assertNotNull(pair.getFirst());
Assert.assertNotNull(pair.getSecond());
Assert.assertEquals(first, pair.getFirst());
Assert.assertEquals(second, pair.getSecond());
}
}
+2 -1
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.android.tools.build:gradle:3.6.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -16,6 +16,7 @@ buildscript {
allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
#Sat May 09 20:36:16 IDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
+73 -29
View File
@@ -1,14 +1,32 @@
LOCAL_PATH := $(call my-dir)
$(call import-add-path, $(LOCAL_PATH))
MY_LOCAL_PATH := $(call my-dir)
$(call import-add-path, $(MY_LOCAL_PATH))
MY_ARMV7 := false
MY_ARMV7_NEON := false
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
ifeq ("$(shell test -e $(MY_LOCAL_PATH)/../build/.armv7 && echo armv7)","armv7")
MY_ARMV7 := true
endif
ifeq ("$(shell test -e $(MY_LOCAL_PATH)/../build/.armv7neon && echo armv7neon)","armv7neon")
MY_ARMV7_NEON := true
endif
endif
ifeq ($(MY_ARMV7_NEON), true)
FFMPEG_INCLUDES := $(MY_LOCAL_PATH)/../../prebuilt/android-$(TARGET_ARCH)/neon/ffmpeg/include
$(call import-module, cpu-features/neon)
else
FFMPEG_INCLUDES := $(MY_LOCAL_PATH)/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
$(call import-module, cpu-features)
endif
MY_ARM_MODE := arm
MY_ARM_NEON := false
MY_PATH := ../app/src/main/cpp
LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
# DEFINE ARCH FLAGS
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
MY_ARCH_FLAGS := ARM_V7A
MY_ARM_NEON := true
MY_ARM_NEON := false
endif
ifeq ($(TARGET_ARCH_ABI), arm64-v8a)
MY_ARCH_FLAGS := ARM64_V8A
@@ -21,40 +39,66 @@ ifeq ($(TARGET_ARCH_ABI), x86_64)
MY_ARCH_FLAGS := X86_64
endif
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := cpufeatures
LOCAL_SRC_FILES := $(NDK_ROOT)/sources/android/cpufeatures/cpu-features.c
LOCAL_CFLAGS := -Wall -Wextra -Werror
LOCAL_EXPORT_C_INCLUDES := $(NDK_ROOT)/sources/android/cpufeatures
LOCAL_EXPORT_LDLIBS := -ldl
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg_abidetect
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg_abidetect.c
LOCAL_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include -I$(NDK_ROOT)/sources/android/cpufeatures -DMOBILE_FFMPEG_${MY_ARCH_FLAGS}
LOCAL_SRC_FILES := mobileffmpeg_abidetect.c
LOCAL_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -DMOBILE_FFMPEG_${MY_ARCH_FLAGS}
LOCAL_C_INCLUDES := $(FFMPEG_INCLUDES)
LOCAL_LDLIBS := -llog -lz -landroid
LOCAL_SHARED_LIBRARIES := cpufeatures
LOCAL_STATIC_LIBRARIES := cpu-features
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg
ifeq ($(TARGET_PLATFORM),android-16)
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/mobileffprobe.c $(MY_PATH)/android_lts_support.c $(MY_PATH)/mobileffmpeg_exception.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffprobe.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
MY_SRC_FILES := mobileffmpeg.c mobileffprobe.c android_lts_support.c mobileffmpeg_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
else ifeq ($(TARGET_PLATFORM),android-17)
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/mobileffprobe.c $(MY_PATH)/android_lts_support.c $(MY_PATH)/mobileffmpeg_exception.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffprobe.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
MY_SRC_FILES := mobileffmpeg.c mobileffprobe.c android_lts_support.c mobileffmpeg_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
else
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/mobileffprobe.c $(MY_PATH)/mobileffmpeg_exception.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffprobe.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
MY_SRC_FILES := mobileffmpeg.c mobileffprobe.c mobileffmpeg_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
endif
LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
LOCAL_LDLIBS := -llog -lz -landroid
LOCAL_SHARED_LIBRARIES := c++_shared libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
$(call import-module, ffmpeg)
MY_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare
MY_LDLIBS := -llog -lz -landroid
MY_BUILD_GENERIC_MOBILE_FFMPEG := true
ifeq ($(MY_ARMV7_NEON), true)
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg_armv7a_neon
LOCAL_SRC_FILES := $(MY_SRC_FILES)
LOCAL_CFLAGS := $(MY_CFLAGS)
LOCAL_LDLIBS := $(MY_LDLIBS)
LOCAL_SHARED_LIBRARIES := libavcodec_neon libavfilter_neon libswscale_neon libavformat_neon libavutil_neon libswresample_neon libavdevice_neon
ifeq ($(APP_STL), c++_shared)
LOCAL_SHARED_LIBRARIES += c++_shared # otherwise NDK will not add the library for packaging
endif
LOCAL_ARM_NEON := true
include $(BUILD_SHARED_LIBRARY)
$(call import-module, ffmpeg/neon)
ifneq ($(MY_ARMV7), true)
MY_BUILD_GENERIC_MOBILE_FFMPEG := false
endif
endif
ifeq ($(MY_BUILD_GENERIC_MOBILE_FFMPEG), true)
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg
LOCAL_SRC_FILES := $(MY_SRC_FILES)
LOCAL_CFLAGS := $(MY_CFLAGS)
LOCAL_LDLIBS := $(MY_LDLIBS)
LOCAL_SHARED_LIBRARIES := libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
ifeq ($(APP_STL), c++_shared)
LOCAL_SHARED_LIBRARIES += c++_shared # otherwise NDK will not add the library for packaging
endif
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
$(call import-module, ffmpeg)
endif
+8
View File
@@ -0,0 +1,8 @@
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/cpu-features/lib
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := cpu-features
LOCAL_SRC_FILES := libndk_compat.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include/ndk_compat
include $(PREBUILT_STATIC_LIBRARY)
+8
View File
@@ -0,0 +1,8 @@
LOCAL_PATH := $(call my-dir)/../../../../prebuilt/android-$(TARGET_ARCH)/neon/cpu-features/lib
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := cpu-features
LOCAL_SRC_FILES := libndk_compat.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include/ndk_compat
include $(PREBUILT_STATIC_LIBRARY)
+9 -9
View File
@@ -1,46 +1,46 @@
LOCAL_PATH := $(call my-dir)
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/lib
MY_ARM_MODE := arm
MY_FFMPEG_LIB := ../../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/lib
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavcodec.so
LOCAL_SRC_FILES := libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavfilter
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavfilter.so
LOCAL_SRC_FILES := libavfilter.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavdevice
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavdevice.so
LOCAL_SRC_FILES := libavdevice.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavformat.so
LOCAL_SRC_FILES := libavformat.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavutil.so
LOCAL_SRC_FILES := libavutil.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswresample.so
LOCAL_SRC_FILES := libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswscale.so
LOCAL_SRC_FILES := libswscale.so
include $(PREBUILT_SHARED_LIBRARY)
+17 -9
View File
@@ -1,53 +1,61 @@
LOCAL_PATH := $(call my-dir)
LOCAL_PATH := $(call my-dir)/../../../../prebuilt/android-$(TARGET_ARCH)/neon/ffmpeg/lib
MY_ARM_MODE := arm
MY_FFMPEG_LIB := ../../../../prebuilt/android-$(TARGET_ARCH)/neon/ffmpeg/lib
MY_ARM_NEON := true
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavcodec_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavcodec.so
LOCAL_SRC_FILES := libavcodec_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavfilter_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavfilter.so
LOCAL_SRC_FILES := libavfilter_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavdevice_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavdevice.so
LOCAL_SRC_FILES := libavdevice_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavformat_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavformat.so
LOCAL_SRC_FILES := libavformat_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavutil_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavutil.so
LOCAL_SRC_FILES := libavutil_neon.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libswresample_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswresample.so
LOCAL_SRC_FILES := libswresample_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libswscale_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswscale.so
LOCAL_SRC_FILES := libswscale_neon.so
include $(PREBUILT_SHARED_LIBRARY)
+3
View File
@@ -1 +1,4 @@
include ':app', ':test-app'
include ':mobile-ffmpeg'
project(':mobile-ffmpeg').projectDir = new File('..')
rootProject.name='Mobile FFmpeg (android)'
+5 -4
View File
@@ -12,10 +12,10 @@ android {
compileSdkVersion 29
defaultConfig {
applicationId "com.arthenica.mobileffmpeg.test"
minSdkVersion 24
minSdkVersion 16
targetSdkVersion 29
versionCode 240431
versionName "4.3.1"
versionCode 160440
versionName "4.4.LTS"
}
buildTypes {
debug {
@@ -52,7 +52,8 @@ android.applicationVariants.all { variant ->
dependencies {
// implementation project(':app')
implementation 'com.arthenica:mobile-ffmpeg-full:4.3.1'
implementation 'com.arthenica:mobile-ffmpeg-full:4.4.LTS'
implementation 'com.arthenica:smart-exception-java:0.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}
@@ -39,7 +39,7 @@ import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import java.io.File;
import java.util.concurrent.Callable;
@@ -103,7 +103,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
@Override
public void apply(final LogMessage message) {
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -145,17 +145,17 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
clearLog();
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -164,7 +164,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
android.util.Log.d(TAG, "Encode completed successfully.");
} else {
Popup.show(requireContext(), "Encode failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Encode failed with rc=%d", returnCode));
android.util.Log.d(TAG, String.format("Encode failed with rc=%d.", returnCode));
}
return null;
@@ -181,11 +181,11 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
final String ffmpegCommand = String.format("-v quiet -i %s -f chromaprint -fp_format 2 -", audioSampleFile.getAbsolutePath());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
int returnCode = FFmpeg.execute(ffmpegCommand);
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
}
public void createAudioSample() {
@@ -198,14 +198,14 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
String ffmpegCommand = String.format("-hide_banner -y -f lavfi -i sine=frequency=1000:duration=5 -c:a pcm_s16le %s", audioSampleFile.getAbsolutePath());
android.util.Log.d(TAG, String.format("Sample file is created with '%s'", ffmpegCommand));
android.util.Log.d(TAG, String.format("Sample file is created with '%s'.", ffmpegCommand));
int result = FFmpeg.execute(ffmpegCommand);
if (result == 0) {
encodeButton.setEnabled(true);
android.util.Log.d(TAG, "AUDIO sample created");
} else {
android.util.Log.d(TAG, String.format("Creating AUDIO sample failed with rc=%d", result));
android.util.Log.d(TAG, String.format("Creating AUDIO sample failed with rc=%d.", result));
Popup.show(requireContext(), "Creating AUDIO sample failed. Please check log for the details.");
}
}
@@ -228,7 +228,10 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
case "opus":
extension = "opus";
break;
case "amr":
case "amr-nb":
extension = "amr";
break;
case "amr-wb":
extension = "amr";
break;
case "ilbc":
@@ -295,8 +298,10 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
return String.format("-hide_banner -y -i %s -c:a libvorbis -b:a 64k %s", audioSampleFile, audioOutputFile);
case "opus":
return String.format("-hide_banner -y -i %s -c:a libopus -b:a 64k -vbr on -compression_level 10 %s", audioSampleFile, audioOutputFile);
case "amr":
case "amr-nb":
return String.format("-hide_banner -y -i %s -ar 8000 -ab 12.2k -c:a libopencore_amrnb %s", audioSampleFile, audioOutputFile);
case "amr-wb":
return String.format("-hide_banner -y -i %s -ar 8000 -ab 12.2k -c:a libvo_amrwbenc -strict experimental %s", audioSampleFile, audioOutputFile);
case "ilbc":
return String.format("-hide_banner -y -i %s -c:a ilbc -ar 8000 -b:a 15200 %s", audioSampleFile, audioOutputFile);
case "speex":
@@ -112,13 +112,15 @@ public class CommandTabFragment extends Fragment {
final String ffmpegCommand = String.format("%s", commandText.getText().toString());
android.util.Log.d(MainActivity.TAG, String.format("Current log level is %s.", Config.getLogLevel()));
android.util.Log.d(MainActivity.TAG, "Testing FFmpeg COMMAND synchronously.");
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process started with arguments\n\'%s\'", ffmpegCommand));
int result = FFmpeg.execute(ffmpegCommand);
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process exited with rc %d", result));
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process exited with rc %d.", result));
if (result != 0) {
Popup.show(requireContext(), "Command failed. Please check output for the details.");
@@ -136,7 +138,7 @@ public class CommandTabFragment extends Fragment {
int result = FFprobe.execute(ffprobeCommand);
android.util.Log.d(MainActivity.TAG, String.format("FFprobe process exited with rc %d", result));
android.util.Log.d(MainActivity.TAG, String.format("FFprobe process exited with rc %d.", result));
if (result != 0) {
Popup.show(requireContext(), "Command failed. Please check output for the details.");
@@ -0,0 +1,273 @@
/*
* Copyright (c) 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.test;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.FFmpegExecution;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import static com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL;
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
public class ConcurrentExecutionTabFragment extends Fragment {
private TextView outputText;
private long executionId1;
private long executionId2;
private long executionId3;
public ConcurrentExecutionTabFragment() {
super(R.layout.fragment_concurrent_tab);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
View encodeButton1 = view.findViewById(R.id.encodeButton1);
if (encodeButton1 != null) {
encodeButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encodeVideo(1);
}
});
}
View encodeButton2 = view.findViewById(R.id.encodeButton2);
if (encodeButton2 != null) {
encodeButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encodeVideo(2);
}
});
}
View encodeButton3 = view.findViewById(R.id.encodeButton3);
if (encodeButton3 != null) {
encodeButton3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encodeVideo(3);
}
});
}
View cancelButton1 = view.findViewById(R.id.cancelButton1);
if (cancelButton1 != null) {
cancelButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel(1);
}
});
}
View cancelButton2 = view.findViewById(R.id.cancelButton2);
if (cancelButton2 != null) {
cancelButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel(2);
}
});
}
View cancelButton3 = view.findViewById(R.id.cancelButton3);
if (cancelButton3 != null) {
cancelButton3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel(3);
}
});
}
View cancelButtonAll = view.findViewById(R.id.cancelButtonAll);
if (cancelButtonAll != null) {
cancelButtonAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel(0);
}
});
}
outputText = view.findViewById(R.id.outputText);
outputText.setMovementMethod(new ScrollingMovementMethod());
}
@Override
public void onResume() {
super.onResume();
setActive();
}
public static ConcurrentExecutionTabFragment newInstance() {
return new ConcurrentExecutionTabFragment();
}
public void enableLogCallback() {
Config.enableLogCallback(new LogCallback() {
@Override
public void apply(final LogMessage message) {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
appendLog(String.format(Locale.getDefault(), "%d:%s", message.getExecutionId(), message.getText()));
return null;
}
});
}
});
}
public void encodeVideo(final int buttonNumber) {
final File image1File = new File(requireContext().getCacheDir(), "colosseum.jpg");
final File image2File = new File(requireContext().getCacheDir(), "pyramid.jpg");
final File image3File = new File(requireContext().getCacheDir(), "tajmahal.jpg");
final File videoFile = new File(requireContext().getFilesDir(), String.format(Locale.getDefault(), "video%d.mp4", buttonNumber));
try {
Log.d(TAG, String.format("Testing CONCURRENT EXECUTION for button %d.", buttonNumber));
ResourcesUtil.resourceToFile(getResources(), R.drawable.colosseum, image1File);
ResourcesUtil.resourceToFile(getResources(), R.drawable.pyramid, image2File);
ResourcesUtil.resourceToFile(getResources(), R.drawable.tajmahal, image3File);
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath(), "mpeg4", "");
Log.d(TAG, String.format("FFmpeg process starting for button %d with arguments\n'%s'.", buttonNumber, ffmpegCommand));
long executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
@Override
public void apply(final long executionId, final int returnCode) {
if (returnCode == RETURN_CODE_CANCEL) {
Log.d(TAG, String.format("FFmpeg process ended with cancel for button %d with executionId %d.", buttonNumber, executionId));
} else {
Log.d(TAG, String.format("FFmpeg process ended with rc %d for button %d with executionId %d.", returnCode, buttonNumber, executionId));
}
}
});
Log.d(TAG, String.format("Async FFmpeg process started for button %d with executionId %d.", buttonNumber, executionId));
switch (buttonNumber) {
case 1: {
executionId1 = executionId;
}
break;
case 2: {
executionId2 = executionId;
}
break;
default: {
executionId3 = executionId;
}
}
} catch (IOException e) {
Log.e(TAG, String.format("Encode video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode video failed");
}
listFFmpegExecutions();
}
public void listFFmpegExecutions() {
final List<FFmpegExecution> ffmpegExecutions = FFmpeg.listExecutions();
Log.d(TAG, "Listing ongoing FFmpeg executions.");
for (int i = 0; i < ffmpegExecutions.size(); i++) {
FFmpegExecution execution = ffmpegExecutions.get(i);
Log.d(TAG, String.format("Execution %d = id:%d, startTime:%s, command:%s.", i, execution.getExecutionId(), execution.getStartTime(), execution.getCommand()));
}
Log.d(TAG, "Listed ongoing FFmpeg executions.");
}
public void cancel(final int buttonNumber) {
long executionId = 0;
switch (buttonNumber) {
case 1: {
executionId = executionId1;
}
break;
case 2: {
executionId = executionId2;
}
break;
case 3: {
executionId = executionId3;
}
}
Log.d(TAG, String.format("Cancelling FFmpeg process for button %d with executionId %d.", buttonNumber, executionId));
if (executionId == 0) {
FFmpeg.cancel();
} else {
FFmpeg.cancel(executionId);
}
}
public void setActive() {
Log.i(MainActivity.TAG, "Concurrent Execution Tab Activated");
enableLogCallback();
Popup.show(requireContext(), Tooltip.CONCURRENT_EXECUTION_TEST_TOOLTIP_TEXT);
}
public void appendLog(final String logMessage) {
outputText.append(logMessage);
}
}
@@ -37,8 +37,9 @@ import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.MediaInformation;
import com.arthenica.mobileffmpeg.StreamInformation;
import java.util.Map;
import java.util.Set;
import org.json.JSONObject;
import java.util.Iterator;
import java.util.concurrent.Callable;
public class HttpsTabFragment extends Fragment {
@@ -105,9 +106,9 @@ public class HttpsTabFragment extends Fragment {
if (testUrl.isEmpty()) {
testUrl = HTTPS_TEST_DEFAULT_URL;
urlText.setText(testUrl);
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with default url '%s'", testUrl));
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with default url '%s'.", testUrl));
} else {
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with url '%s'", testUrl));
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with url '%s'.", testUrl));
}
// HTTPS COMMAND ARGUMENTS
@@ -115,7 +116,7 @@ public class HttpsTabFragment extends Fragment {
if (information == null) {
appendLog("Get media information failed\n");
} else {
appendLog("Media information for " + information.getPath() + "\n");
appendLog("Media information for " + information.getFilename() + "\n");
if (information.getFormat() != null) {
appendLog("Format: " + information.getFormat() + "\n");
@@ -129,10 +130,14 @@ public class HttpsTabFragment extends Fragment {
if (information.getStartTime() != null) {
appendLog("Start time: " + information.getStartTime() + "\n");
}
if (information.getMetadataEntries() != null) {
Set<Map.Entry<String, String>> entries = information.getMetadataEntries();
for (Map.Entry<String, String> entry : entries) {
appendLog("Metadata: " + entry.getKey() + ":" + entry.getValue() + "\n");
if (information.getTags() != null) {
JSONObject tags = information.getTags();
if (tags != null) {
Iterator<String> keys = tags.keys();
while (keys.hasNext()) {
String next = keys.next();
appendLog("Tag: " + next + ":" + tags.optString(next) + "\n");
}
}
}
if (information.getStreams() != null) {
@@ -152,9 +157,6 @@ public class HttpsTabFragment extends Fragment {
if (stream.getFormat() != null) {
appendLog("Stream format: " + stream.getFormat() + "\n");
}
if (stream.getFullFormat() != null) {
appendLog("Stream full format: " + stream.getFullFormat() + "\n");
}
if (stream.getWidth() != null) {
appendLog("Stream width: " + stream.getWidth() + "\n");
@@ -196,10 +198,14 @@ public class HttpsTabFragment extends Fragment {
appendLog("Stream codec time base: " + stream.getCodecTimeBase() + "\n");
}
if (stream.getMetadataEntries() != null) {
Set<Map.Entry<String, String>> entries = stream.getMetadataEntries();
for (Map.Entry<String, String> entry : entries) {
appendLog("Stream metadata: " + entry.getKey() + ":" + entry.getValue() + "\n");
if (stream.getTags() != null) {
JSONObject tags = stream.getTags();
if (tags != null) {
Iterator<String> keys = tags.keys();
while (keys.hasNext()) {
String next = keys.next();
appendLog(String.format("Stream tag: %s:%s\n", next, tags.optString(next)));
}
}
}
}
@@ -32,10 +32,13 @@ import androidx.core.app.ActivityCompat;
import androidx.viewpager.widget.PagerTabStrip;
import androidx.viewpager.widget.ViewPager;
import com.arthenica.mobileffmpeg.AsyncFFmpegExecuteTask;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.util.AsyncSingleFFmpegExecuteTask;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.Level;
import com.arthenica.mobileffmpeg.Signal;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -56,7 +59,11 @@ public class MainActivity extends AppCompatActivity {
Manifest.permission.CAMERA
};
protected static final Queue<Callable> actionQueue = new ConcurrentLinkedQueue<>();
static {
Exceptions.registerRootPackage("com.arthenica");
}
protected static final Queue<Callable<Object>> actionQueue = new ConcurrentLinkedQueue<>();
protected static final Handler handler = new Handler();
@@ -64,7 +71,7 @@ public class MainActivity extends AppCompatActivity {
@Override
public void run() {
Callable callable;
Callable<Object> callable;
do {
callable = actionQueue.poll();
@@ -72,7 +79,7 @@ public class MainActivity extends AppCompatActivity {
try {
callable.call();
} catch (final Exception e) {
android.util.Log.e(MainActivity.TAG, "Running UI action received error.", e);
android.util.Log.e(TAG, String.format("Running UI action received error.%s.", Exceptions.getStackTraceString(e)));
}
}
} while (callable != null);
@@ -123,11 +130,13 @@ public class MainActivity extends AppCompatActivity {
registerAppFont();
Log.d(TAG, "Application fonts registered.");
} catch (final IOException e) {
Log.e(TAG, "Font registration failed.", e);
Log.e(TAG, String.format("Font registration failed.%s.", Exceptions.getStackTraceString(e)));
}
Log.d(TAG, "Listing supported camera ids.");
listSupportedCameraIds();
Config.ignoreSignal(Signal.SIGXCPU);
Config.setLogLevel(Level.AV_LOG_DEBUG);
}
@Override
@@ -140,11 +149,11 @@ public class MainActivity extends AppCompatActivity {
/**
* <p>Starts a new asynchronous FFmpeg operation with command provided.
*
* @param singleExecuteCallback callback function to receive result of this execution
* @param command FFmpeg command
* @param ExecuteCallback callback function to receive result of this execution
* @param command FFmpeg command
*/
public static void executeAsync(final SingleExecuteCallback singleExecuteCallback, final String command) {
final AsyncSingleFFmpegExecuteTask asyncCommandTask = new AsyncSingleFFmpegExecuteTask(command, singleExecuteCallback);
public static void executeAsync(final ExecuteCallback ExecuteCallback, final String command) {
final AsyncFFmpegExecuteTask asyncCommandTask = new AsyncFFmpegExecuteTask(command, ExecuteCallback);
asyncCommandTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -152,7 +161,7 @@ public class MainActivity extends AppCompatActivity {
handler.postDelayed(runnable, 250);
}
public static void addUIAction(final Callable callable) {
public static void addUIAction(final Callable<Object> callable) {
actionQueue.add(callable);
}
@@ -166,7 +175,7 @@ public class MainActivity extends AppCompatActivity {
final HashMap<String, String> fontNameMapping = new HashMap<>();
fontNameMapping.put("MyFontName", "Doppio One");
Config.setFontDirectory(this, cacheDirectory.getAbsolutePath(), fontNameMapping);
// Config.setFontDirectory(this, cacheDirectory.getAbsolutePath(), null);
Config.setEnvironmentVariable("FFREPORT", String.format("file=%s", new File(cacheDirectory.getAbsolutePath(), "ffreport.txt").getAbsolutePath()));
}
protected void listSupportedCameraIds() {
@@ -26,7 +26,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
public class PagerAdapter extends FragmentPagerAdapter {
private static final int NUMBER_OF_TABS = 7;
private static final int NUMBER_OF_TABS = 8;
private final Context context;
@@ -59,6 +59,9 @@ public class PagerAdapter extends FragmentPagerAdapter {
case 6: {
return PipeTabFragment.newInstance();
}
case 7: {
return ConcurrentExecutionTabFragment.newInstance();
}
default: {
return null;
}
@@ -94,6 +97,9 @@ public class PagerAdapter extends FragmentPagerAdapter {
case 6: {
return context.getString(R.string.pipe_tab);
}
case 7: {
return context.getString(R.string.concurrent_tab);
}
default: {
return null;
}
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.Statistics;
@@ -42,7 +43,7 @@ import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.AsyncCatImageTask;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -107,7 +108,7 @@ public class PipeTabFragment extends Fragment {
@Override
public void apply(final Statistics newStatistics) {
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -154,17 +155,17 @@ public class PipeTabFragment extends Fragment {
final String ffmpegCommand = Video.generateCreateVideoWithPipesScript(pipe1, pipe2, pipe3, videoFile.getAbsolutePath());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -173,7 +174,7 @@ public class PipeTabFragment extends Fragment {
playVideo();
} else {
Popup.show(requireContext(), "Create failed. Please check log for the details.");
Log.d(TAG, String.format("Create failed with rc=%d", returnCode));
Log.d(TAG, String.format("Create failed with rc=%d.", returnCode));
}
return null;
@@ -188,7 +189,7 @@ public class PipeTabFragment extends Fragment {
startAsyncCatImageProcess(image3File.getAbsolutePath(), pipe3);
} catch (IOException e) {
Log.e(TAG, "Create video failed", e);
Log.e(TAG, String.format("Create video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Create video failed");
}
}
@@ -250,7 +251,7 @@ public class PipeTabFragment extends Fragment {
TextView textView = progressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Creating video: %% %s", completePercentage));
textView.setText(String.format("Creating video: %% %s.", completePercentage));
}
}
}
@@ -258,7 +259,7 @@ public class PipeTabFragment extends Fragment {
protected void hideProgressDialog() {
progressDialog.dismiss();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
@@ -41,7 +42,7 @@ import com.arthenica.mobileffmpeg.Statistics;
import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -65,6 +66,7 @@ public class SubtitleTabFragment extends Fragment {
private AlertDialog burnProgressDialog;
private Statistics statistics;
private State state;
private Long executionId;
public SubtitleTabFragment() {
super(R.layout.fragment_subtitle_tab);
@@ -103,7 +105,7 @@ public class SubtitleTabFragment extends Fragment {
@Override
public void apply(LogMessage message) {
android.util.Log.d(MainActivity.TAG, message.getText());
Log.d(MainActivity.TAG, message.getText());
}
});
}
@@ -157,23 +159,24 @@ public class SubtitleTabFragment extends Fragment {
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath(), "mpeg4", "");
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
state = State.CREATING;
MainActivity.executeAsync(new SingleExecuteCallback() {
executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideCreateProgressDialog();
MainActivity.addUIAction(new Callable() {
if (returnCode == RETURN_CODE_SUCCESS) {
@Override
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
Log.d(TAG, "Create completed successfully; burning subtitles.");
@@ -181,19 +184,19 @@ public class SubtitleTabFragment extends Fragment {
showBurnProgressDialog();
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", burnSubtitlesCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", burnSubtitlesCommand));
state = State.BURNING;
MainActivity.executeAsync(new SingleExecuteCallback() {
FFmpeg.executeAsync(burnSubtitlesCommand, new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideBurnProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -205,31 +208,26 @@ public class SubtitleTabFragment extends Fragment {
Log.e(TAG, "Burn subtitles operation cancelled");
} else {
Popup.show(requireContext(), "Burn subtitles failed. Please check log for the details.");
Log.e(TAG, String.format("Burn subtitles failed with rc=%d", returnCode));
Log.e(TAG, String.format("Burn subtitles failed with rc=%d.", returnCode));
}
return null;
}
});
}
}, burnSubtitlesCommand);
});
} else if (returnCode == RETURN_CODE_CANCEL) {
Popup.show(requireContext(), "Create operation cancelled.");
Log.e(TAG, "Create operation cancelled");
} else {
Popup.show(requireContext(), "Create video failed. Please check log for the details.");
Log.e(TAG, String.format("Create failed with rc=%d", returnCode));
return null;
}
return null;
}
});
});
}
}
}, ffmpegCommand);
});
Log.d(TAG, String.format("FFmpeg started execution id: %d.", executionId));
} catch (IOException e) {
Log.e(TAG, "Burn subtitles failed", e);
Log.e(TAG, String.format("Burn subtitles failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Burn subtitles failed");
}
}
@@ -287,7 +285,10 @@ public class SubtitleTabFragment extends Fragment {
@Override
public void onClick(View v) {
FFmpeg.cancel();
if (executionId != null) {
Log.d(TAG, String.format("Cancelling FFmpeg execution with executionId %d.", executionId));
FFmpeg.cancel(executionId);
}
}
});
createProgressDialog.show();
@@ -306,12 +307,12 @@ public class SubtitleTabFragment extends Fragment {
if (state == State.CREATING) {
TextView textView = createProgressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Creating video: %% %s", completePercentage));
textView.setText(String.format("Creating video: %% %s.", completePercentage));
}
} else if (state == State.BURNING) {
TextView textView = burnProgressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Burning subtitles: %% %s", completePercentage));
textView.setText(String.format("Burning subtitles: %% %s.", completePercentage));
}
}
@@ -42,4 +42,7 @@ public interface Tooltip {
// PIPE TEST
String PIPE_TEST_TOOLTIP_TEXT = "Click the button to create a video using pipe redirection. Created video will play inside the frame below";
// CONCURRENT EXECUTION TEST
String CONCURRENT_EXECUTION_TEST_TOOLTIP_TEXT = " Use ENCODE nad CANCEL buttons to start/stop multiple execution";
}
@@ -37,7 +37,8 @@ import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -92,7 +93,7 @@ public class VidStabTabFragment extends Fragment {
@Override
public void apply(LogMessage message) {
android.util.Log.d(MainActivity.TAG, message.getText());
Log.d(MainActivity.TAG, message.getText());
}
});
}
@@ -121,7 +122,7 @@ public class VidStabTabFragment extends Fragment {
stabilizedVideoFile.delete();
}
android.util.Log.d(TAG, "Testing VID.STAB");
Log.d(TAG, "Testing VID.STAB");
showCreateProgressDialog();
@@ -131,60 +132,60 @@ public class VidStabTabFragment extends Fragment {
final String ffmpegCommand = Video.generateShakingVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath());
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideCreateProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) {
android.util.Log.d(TAG, "Create completed successfully; stabilizing video.");
Log.d(TAG, "Create completed successfully; stabilizing video.");
final String analyzeVideoCommand = String.format("-y -i %s -vf vidstabdetect=shakiness=10:accuracy=15:result=%s -f null -", videoFile.getAbsolutePath(), shakeResultsFile.getAbsolutePath());
showStabilizeProgressDialog();
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", analyzeVideoCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", analyzeVideoCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
if (returnCode == RETURN_CODE_SUCCESS) {
final String stabilizeVideoCommand = String.format("-y -i %s -vf vidstabtransform=smoothing=30:input=%s -c:v mpeg4 %s", videoFile.getAbsolutePath(), shakeResultsFile.getAbsolutePath(), stabilizedVideoFile.getAbsolutePath());
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", stabilizeVideoCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", stabilizeVideoCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideStabilizeProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) {
android.util.Log.d(TAG, "Stabilize video completed successfully; playing videos.");
Log.d(TAG, "Stabilize video completed successfully; playing videos.");
playVideo();
playStabilizedVideo();
} else {
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
Log.d(TAG, String.format("Stabilize video failed with rc=%d.", returnCode));
}
return null;
@@ -196,14 +197,14 @@ public class VidStabTabFragment extends Fragment {
} else {
hideStabilizeProgressDialog();
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
Log.d(TAG, String.format("Stabilize video failed with rc=%d.", returnCode));
}
}
}, analyzeVideoCommand);
} else {
Popup.show(requireContext(), "Create video failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Create failed with rc=%d", returnCode));
Log.d(TAG, String.format("Create failed with rc=%d.", returnCode));
}
return null;
@@ -213,7 +214,7 @@ public class VidStabTabFragment extends Fragment {
}, ffmpegCommand);
} catch (IOException e) {
android.util.Log.e(TAG, "Stabilize video failed", e);
Log.e(TAG, String.format("Stabilize video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Stabilize video failed");
}
}
@@ -38,6 +38,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
@@ -45,7 +46,7 @@ import com.arthenica.mobileffmpeg.Statistics;
import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -121,7 +122,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
@Override
public void apply(final Statistics newStatistics) {
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -173,13 +174,13 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath(), getSelectedVideoCodec(), getCustomOptions());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
long executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
Log.d(TAG, "FFmpeg process output:");
@@ -187,7 +188,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
hideProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -196,17 +197,19 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
playVideo();
} else {
Popup.show(requireContext(), "Encode failed. Please check log for the details.");
Log.d(TAG, String.format("Encode failed with rc=%d", returnCode));
Log.d(TAG, String.format("Encode failed with rc=%d.", returnCode));
}
return null;
}
});
}
}, ffmpegCommand);
});
Log.d(TAG, String.format("Async FFmpeg process started with executionId %d.", executionId));
} catch (IOException e) {
Log.e(TAG, "Encode video failed", e);
Log.e(TAG, String.format("Encode video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode video failed");
}
}
@@ -222,13 +225,13 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
final String ffmpegCommand = String.format("-hide_banner -i %s %s", imageFile.getAbsolutePath(), outputFile.getAbsolutePath());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
int returnCode = FFmpeg.execute(ffmpegCommand);
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
} catch (IOException e) {
Log.e(TAG, "Encode webp failed", e);
Log.e(TAG, String.format("Encode webp failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode webp failed");
}
}
@@ -268,6 +271,9 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
case "x264":
videoCodec = "libx264";
break;
case "openh264":
videoCodec = "libopenh264";
break;
case "x265":
videoCodec = "libx265";
break;
@@ -375,7 +381,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
TextView textView = progressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Encoding video: %% %s", completePercentage));
textView.setText(String.format("Encoding video: %% %s.", completePercentage));
}
}
}
@@ -383,7 +389,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
protected void hideProgressDialog() {
progressDialog.dismiss();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -22,6 +22,8 @@ package com.arthenica.mobileffmpeg.util;
import android.os.AsyncTask;
import android.util.Log;
import com.arthenica.smartexception.java.Exceptions;
import java.io.IOException;
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
@@ -41,7 +43,7 @@ public class AsyncCatImageTask extends AsyncTask<String, Integer, Integer> {
return rc;
} catch (final IOException | InterruptedException e) {
Log.e(TAG, String.format("Async cat image command failed for %s.", inputs[0]), e);
Log.e(TAG, String.format("Async cat image command failed for %s.%s", inputs[0], Exceptions.getStackTraceString(e)));
return -1;
}
}
@@ -25,12 +25,15 @@ import android.graphics.BitmapFactory;
import android.util.Log;
import com.arthenica.mobileffmpeg.test.MainActivity;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
public class ResourcesUtil {
public static void resourceToFile(Resources resources, final int resourceId, final File file) throws IOException {
Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId);
@@ -60,7 +63,7 @@ public class ResourcesUtil {
outputStream.write(buffer, 0, readSize);
}
} catch (final IOException e) {
Log.e(MainActivity.TAG, "Saving raw resource failed.", e);
Log.e(TAG, String.format("Saving raw resource failed.%s", Exceptions.getStackTraceString(e)));
} finally {
inputStream.close();
outputStream.flush();
@@ -42,7 +42,7 @@
android:background="@drawable/dialog_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/dialog_cancel_button_text"
android:text="@string/cancel_button_text"
android:textAlignment="center"
android:textColor="@android:color/black"
android:textSize="16sp"
@@ -45,7 +45,7 @@
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/audio_encode_button_text"
android:text="@string/encode_button_text"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".ConcurrentExecutionTabFragment">
<LinearLayout
android:id="@+id/videoEncodeSpinnerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:layout_marginTop="40dp"
android:gravity="center"
android:orientation="horizontal"/>
<LinearLayout
android:id="@+id/encodeButtonLayout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/encodeButton1"
android:layout_width="90dp"
android:layout_height="36dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/encode_button_text_1"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/encodeButton2"
android:layout_width="90dp"
android:layout_height="36dp"
android:layout_margin="20dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/encode_button_text_2"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/encodeButton3"
android:layout_width="90dp"
android:layout_height="36dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/encode_button_text_3"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/cancelButtonLayout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/cancelButton1"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_1"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/cancelButton2"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_2"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/cancelButton3"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_3"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/cancelButtonAll"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_all"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/outputText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:layout_marginBottom="20dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:background="@drawable/rounded_output_frame"
android:fontFamily="sans-serif"
android:gravity="bottom"
android:overScrollMode="ifContentScrolls"
android:scrollbars="vertical"
android:textColor="@android:color/black"
android:textSize="14sp"
android:typeface="sans"
tools:targetApi="jelly_bean" />
</LinearLayout>
@@ -45,7 +45,7 @@
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/video_encode_button_text"
android:text="@string/encode_button_text"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
@@ -6,7 +6,8 @@
<item>mp3 (libshine)</item>
<item>vorbis</item>
<item>opus</item>
<item>amr</item>
<item>amr-nb</item>
<item>amr-wb</item>
<item>ilbc</item>
<item>soxr</item>
<item>speex</item>
@@ -7,15 +7,22 @@
<string name="subtitle_tab">SUBTITLE</string>
<string name="vidstab_tab">VID.STAB</string>
<string name="pipe_tab">PIPE</string>
<string name="concurrent_tab">CONCURRENT</string>
<string name="command_text_input_placeholder">Enter command</string>
<string name="command_run_ffmpeg_button_text">RUN FFMPEG</string>
<string name="command_run_ffprobe_button_text">RUN FFPROBE</string>
<string name="video_encode_button_text">ENCODE</string>
<string name="encode_button_text">ENCODE</string>
<string name="encode_button_text_1">ENCODE 1</string>
<string name="encode_button_text_2">ENCODE 2</string>
<string name="encode_button_text_3">ENCODE 3</string>
<string name="video_create_button_text">CREATE</string>
<string name="https_get_info_button_text">GET INFO</string>
<string name="https_text_input_placeholder">Enter https url</string>
<string name="audio_encode_button_text">ENCODE</string>
<string name="subtitle_burn_subtitles_button_text">BURN SUBTITLES</string>
<string name="vidstab_stabilize_video_button_text">STABILIZE VIDEO</string>
<string name="dialog_cancel_button_text">Cancel</string>
<string name="cancel_button_text">Cancel</string>
<string name="cancel_button_text_1">Cancel 1</string>
<string name="cancel_button_text_2">Cancel 2</string>
<string name="cancel_button_text_3">Cancel 3</string>
<string name="cancel_button_text_all">Cancel All</string>
</resources>
@@ -3,6 +3,7 @@
<string-array name="video_codec">
<item>mpeg4</item>
<item>x264</item>
<item>openh264</item>
<item>x265</item>
<item>xvid</item>
<item>vp8</item>
+3 -3
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="chromaprint"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
CFLAGS=$(get_cflags ${LIB_NAME})
CXXFLAGS=$(get_cxxflags ${LIB_NAME})
LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -64,6 +64,6 @@ cmake -Wno-dev \
make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY
create_chromaprint_package_config "1.4.3"
create_chromaprint_package_config "1.5.0"
make install || exit 1
+86 -337
View File
@@ -1,20 +1,6 @@
#!/bin/bash
get_cpu_count() {
if [ "$(uname)" == "Darwin" ]; then
echo $(sysctl -n hw.physicalcpu)
else
echo $(nproc)
fi
}
prepare_inline_sed() {
if [ "$(uname)" == "Darwin" ]; then
export SED_INLINE="sed -i .tmp"
else
export SED_INLINE="sed -i"
fi
}
source "${BASEDIR}/build/arch-common.sh"
get_library_name() {
case $1 in
@@ -40,28 +26,32 @@ get_library_name() {
19) echo "xvidcore" ;;
20) echo "x265" ;;
21) echo "libvidstab" ;;
22) echo "libilbc" ;;
23) echo "opus" ;;
24) echo "snappy" ;;
25) echo "soxr" ;;
26) echo "libaom" ;;
27) echo "chromaprint" ;;
28) echo "twolame" ;;
29) echo "sdl" ;;
30) echo "tesseract" ;;
31) echo "openh264" ;;
32) echo "giflib" ;;
33) echo "jpeg" ;;
34) echo "libogg" ;;
35) echo "libpng" ;;
36) echo "libuuid" ;;
37) echo "nettle" ;;
38) echo "tiff" ;;
39) echo "expat" ;;
40) echo "libsndfile" ;;
41) echo "leptonica" ;;
42) echo "android-zlib" ;;
43) echo "android-media-codec" ;;
22) echo "rubberband" ;;
23) echo "libilbc" ;;
24) echo "opus" ;;
25) echo "snappy" ;;
26) echo "soxr" ;;
27) echo "libaom" ;;
28) echo "chromaprint" ;;
29) echo "twolame" ;;
30) echo "sdl" ;;
31) echo "tesseract" ;;
32) echo "openh264" ;;
33) echo "vo-amrwbenc" ;;
34) echo "giflib" ;;
35) echo "jpeg" ;;
36) echo "libogg" ;;
37) echo "libpng" ;;
38) echo "libuuid" ;;
39) echo "nettle" ;;
40) echo "tiff" ;;
41) echo "expat" ;;
42) echo "libsndfile" ;;
43) echo "leptonica" ;;
44) echo "libsamplerate" ;;
45) echo "android-zlib" ;;
46) echo "android-media-codec" ;;
47) echo "cpu-features" ;;
esac
}
@@ -75,7 +65,7 @@ get_arch_name() {
esac
}
get_target_host() {
get_build_host() {
case ${ARCH} in
arm-v7a | arm-v7a-neon)
echo "arm-linux-androideabi"
@@ -151,11 +141,7 @@ get_target_build() {
echo "arm"
;;
arm-v7a-neon)
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]]; then
echo "arm/neon"
else
echo "arm"
fi
echo "arm/neon"
;;
arm64-v8a)
echo "arm64"
@@ -204,7 +190,7 @@ get_android_arch() {
}
get_common_includes() {
echo "-I${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/include -I${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/local/include"
echo ""
}
get_common_cflags() {
@@ -282,7 +268,6 @@ get_size_optimization_cflags() {
}
get_app_specific_cflags() {
local APP_FLAGS=""
case $1 in
xvidcore)
@@ -291,15 +276,18 @@ get_app_specific_cflags() {
ffmpeg)
APP_FLAGS="-Wno-unused-function -DBIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD"
;;
kvazaar)
APP_FLAGS="-std=gnu99 -Wno-unused-function"
;;
rubberband)
APP_FLAGS="-std=c99 -Wno-unused-function"
;;
shine)
APP_FLAGS="-Wno-unused-function"
;;
soxr | snappy | libwebp)
APP_FLAGS="-std=gnu99 -Wno-unused-function -DPIC"
;;
kvazaar)
APP_FLAGS="-std=gnu99 -Wno-unused-function"
;;
*)
APP_FLAGS="-std=c99 -Wno-unused-function"
;;
@@ -352,6 +340,9 @@ get_cxxflags() {
x265)
echo "-std=c++11 -fno-exceptions ${OPTIMIZATION_FLAGS}"
;;
rubberband)
echo "-std=c++11 ${OPTIMIZATION_FLAGS}"
;;
*)
echo "-std=c++11 -fno-exceptions -fno-rtti ${OPTIMIZATION_FLAGS}"
;;
@@ -359,7 +350,7 @@ get_cxxflags() {
}
get_common_linked_libraries() {
local COMMON_LIBRARY_PATHS="-L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/${TARGET_HOST}/lib -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/lib/${TARGET_HOST}/${API} -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/lib"
local COMMON_LIBRARY_PATHS="-L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/${BUILD_HOST}/lib -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/lib/${BUILD_HOST}/${API} -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/lib"
case $1 in
ffmpeg)
@@ -674,25 +665,6 @@ Cflags: -I\${includedir}
EOF
}
create_libwebp_package_config() {
local LIB_WEBP_VERSION="$1"
cat > "${INSTALL_PKG_CONFIG_DIR}/libwebp.pc" << EOF
prefix=${BASEDIR}/prebuilt/android-$(get_target_build)/libwebp
exec_prefix=\${prefix}
libdir=\${prefix}/lib
includedir=\${prefix}/include
Name: libwebp
Description: webp codec library
Version: ${LIB_WEBP_VERSION}
Requires:
Libs: -L\${libdir} -lwebp -lwebpdecoder -lwebpdemux
Cflags: -I\${includedir}
EOF
}
create_libxml2_package_config() {
local LIBXML2_VERSION="$1"
@@ -831,7 +803,7 @@ Cflags: -I\${includedir}
EOF
}
create_zlib_package_config() {
create_zlib_system_package_config() {
ZLIB_VERSION=$(grep '#define ZLIB_VERSION' ${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/include/zlib.h | grep -Eo '\".*\"' | sed -e 's/\"//g')
cat > "${INSTALL_PKG_CONFIG_DIR}/zlib.pc" << EOF
@@ -851,168 +823,75 @@ EOF
}
create_cpufeatures_package_config() {
cat > "${INSTALL_PKG_CONFIG_DIR}/cpufeatures.pc" << EOF
prefix=${ANDROID_NDK_ROOT}/sources/android/cpufeatures
exec_prefix=\${prefix}
libdir=\${exec_prefix}
includedir=\${prefix}
cat > "${INSTALL_PKG_CONFIG_DIR}/cpu-features.pc" << EOF
prefix=${BASEDIR}/prebuilt/android-$(get_target_build)/cpu-features
exec_prefix=\${prefix}/bin
libdir=\${prefix}/lib
includedir=\${prefix}/include/ndk_compat
Name: cpufeatures
Description: cpu features Android utility
URL: https://github.com/google/cpu_features
Description: cpu_features Android compatibility library
Version: 1.${API}
Requires:
Libs: -L\${libdir} -lcpufeatures
Libs: -L\${libdir} -lndk_compat
Cflags: -I\${includedir}
EOF
}
#
# download <url> <local file name> <on error action>
#
download() {
if [ ! -d "${MOBILE_FFMPEG_TMPDIR}" ]; then
mkdir -p "${MOBILE_FFMPEG_TMPDIR}"
fi
(curl --fail --location $1 -o ${MOBILE_FFMPEG_TMPDIR}/$2 1>>${BASEDIR}/build.log 2>&1)
local RC=$?
if [ ${RC} -eq 0 ]; then
echo -e "\nDEBUG: Downloaded $1 to ${MOBILE_FFMPEG_TMPDIR}/$2\n" 1>>${BASEDIR}/build.log 2>&1
else
rm -f ${MOBILE_FFMPEG_TMPDIR}/$2 1>>${BASEDIR}/build.log 2>&1
echo -e -n "\nINFO: Failed to download $1 to ${MOBILE_FFMPEG_TMPDIR}/$2, rc=${RC}. " 1>>${BASEDIR}/build.log 2>&1
if [ "$3" == "exit" ]; then
echo -e "DEBUG: Build will now exit.\n" 1>>${BASEDIR}/build.log 2>&1
exit 1
else
echo -e "DEBUG: Build will continue.\n" 1>>${BASEDIR}/build.log 2>&1
fi
fi
echo ${RC}
}
download_gpl_library_source() {
local GPL_LIB_URL=""
local GPL_LIB_FILE=""
local GPL_LIB_ORIG_DIR=""
local GPL_LIB_DEST_DIR=""
echo -e "\nDEBUG: Downloading GPL library source: $1\n" 1>>${BASEDIR}/build.log 2>&1
case $1 in
libvidstab)
GPL_LIB_URL="https://github.com/georgmartius/vid.stab/archive/v1.1.0.tar.gz"
GPL_LIB_FILE="v1.1.0.tar.gz"
GPL_LIB_ORIG_DIR="vid.stab-1.1.0"
GPL_LIB_DEST_DIR="libvidstab"
android_ndk_abi() { # to be used with CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake
case ${ARCH} in
arm-v7a | arm-v7a-neon)
echo "armeabi-v7a"
;;
x264)
GPL_LIB_URL="https://code.videolan.org/videolan/x264/-/archive/1771b556ee45207f8711744ccbd5d42a3949b14c/x264-1771b556ee45207f8711744ccbd5d42a3949b14c.tar.bz2"
GPL_LIB_FILE="x264-1771b556ee45207f8711744ccbd5d42a3949b14c.tar.bz2"
GPL_LIB_ORIG_DIR="x264-1771b556ee45207f8711744ccbd5d42a3949b14c"
GPL_LIB_DEST_DIR="x264"
arm64-v8a)
echo "arm64-v8a"
;;
x265)
GPL_LIB_URL="https://bitbucket.org/multicoreware/x265/downloads/x265_3.2.1.tar.gz"
GPL_LIB_FILE="x265-3.2.1.tar.gz"
GPL_LIB_ORIG_DIR="x265_3.2.1"
GPL_LIB_DEST_DIR="x265"
x86)
echo "x86"
;;
xvidcore)
GPL_LIB_URL="https://downloads.xvid.com/downloads/xvidcore-1.3.7.tar.gz"
GPL_LIB_FILE="xvidcore-1.3.7.tar.gz"
GPL_LIB_ORIG_DIR="xvidcore"
GPL_LIB_DEST_DIR="xvidcore"
x86-64)
echo "x86_64"
;;
esac
}
local GPL_LIB_SOURCE_PATH="${BASEDIR}/src/${GPL_LIB_DEST_DIR}"
android_build_dir() {
echo ${BASEDIR}/android/build/${LIB_NAME}/$(get_target_build)
}
if [ -d "${GPL_LIB_SOURCE_PATH}" ]; then
echo -e "INFO: $1 already downloaded. Source folder found at ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
echo 0
return
android_ndk_cmake() {
local cmake=$(find ${ANDROID_HOME}/cmake -path \*/bin/cmake -type f -print -quit)
if [[ -z ${cmake} ]]; then
cmake=$(which cmake)
fi
if [[ -z ${cmake} ]]; then
cmake="missing_cmake"
fi
local GPL_LIB_PACKAGE_PATH="${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_FILE}"
echo -e "DEBUG: $1 source not found. Checking if library package ${GPL_LIB_FILE} is downloaded at ${GPL_LIB_PACKAGE_PATH} \n" 1>>${BASEDIR}/build.log 2>&1
if [ ! -f "${GPL_LIB_PACKAGE_PATH}" ]; then
echo -e "DEBUG: $1 library package not found. Downloading from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
local DOWNLOAD_RC=$(download "${GPL_LIB_URL}" "${GPL_LIB_FILE}")
if [ ${DOWNLOAD_RC} -ne 0 ]; then
echo -e "INFO: Downloading GPL library $1 failed. Can not get library package from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
echo ${DOWNLOAD_RC}
return
else
echo -e "DEBUG: $1 library package downloaded\n" 1>>${BASEDIR}/build.log 2>&1
fi
else
echo -e "DEBUG: $1 library package already downloaded\n" 1>>${BASEDIR}/build.log 2>&1
fi
local EXTRACT_COMMAND=""
if [[ ${GPL_LIB_FILE} == *bz2 ]]; then
EXTRACT_COMMAND="tar jxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
else
EXTRACT_COMMAND="tar zxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
fi
echo -e "DEBUG: Extracting library package ${GPL_LIB_FILE} inside ${MOBILE_FFMPEG_TMPDIR}\n" 1>>${BASEDIR}/build.log 2>&1
${EXTRACT_COMMAND} 1>>${BASEDIR}/build.log 2>&1
local EXTRACT_RC=$?
if [ ${EXTRACT_RC} -ne 0 ]; then
echo -e "\nINFO: Downloading GPL library $1 failed. Extract for library package ${GPL_LIB_FILE} completed with rc=${EXTRACT_RC}. Deleting failed files.\n" 1>>${BASEDIR}/build.log 2>&1
rm -f ${GPL_LIB_PACKAGE_PATH} 1>>${BASEDIR}/build.log 2>&1
rm -rf ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} 1>>${BASEDIR}/build.log 2>&1
echo ${EXTRACT_RC}
return
fi
echo -e "DEBUG: Extract completed. Copying library source to ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
COPY_COMMAND="cp -r ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} ${GPL_LIB_SOURCE_PATH}"
${COPY_COMMAND} 1>>${BASEDIR}/build.log 2>&1
local COPY_RC=$?
if [ ${COPY_RC} -eq 0 ]; then
echo -e "DEBUG: Downloading GPL library source $1 completed successfully\n" 1>>${BASEDIR}/build.log 2>&1
else
echo -e "\nINFO: Downloading GPL library $1 failed. Copying library source to ${GPL_LIB_SOURCE_PATH} completed with rc=${COPY_RC}\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf ${GPL_LIB_SOURCE_PATH} 1>>${BASEDIR}/build.log 2>&1
echo ${COPY_RC}
return
fi
echo ${cmake} \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake \
-H${BASEDIR}/src/${LIB_NAME} \
-B$(android_build_dir) \
-DANDROID_ABI=$(android_ndk_abi) \
-DANDROID_PLATFORM=android-${API} \
-DCMAKE_INSTALL_PREFIX=${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME}
}
set_toolchain_clang_paths() {
export PATH=$PATH:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/bin
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export AR=${TARGET_HOST}-ar
export AR=${BUILD_HOST}-ar
export CC=$(get_clang_target_host)-clang
export CXX=$(get_clang_target_host)-clang++
if [ "$1" == "x264" ]; then
export AS=${CC}
else
export AS=${TARGET_HOST}-as
export AS=${BUILD_HOST}-as
fi
case ${ARCH} in
@@ -1021,9 +900,9 @@ set_toolchain_clang_paths() {
;;
esac
export LD=${TARGET_HOST}-ld
export RANLIB=${TARGET_HOST}-ranlib
export STRIP=${TARGET_HOST}-strip
export LD=${BUILD_HOST}-ld
export RANLIB=${BUILD_HOST}-ranlib
export STRIP=${BUILD_HOST}-strip
export INSTALL_PKG_CONFIG_DIR="${BASEDIR}/prebuilt/android-$(get_target_build)/pkgconfig"
export ZLIB_PACKAGE_CONFIG_PATH="${INSTALL_PKG_CONFIG_DIR}/zlib.pc"
@@ -1033,36 +912,12 @@ set_toolchain_clang_paths() {
fi
if [ ! -f ${ZLIB_PACKAGE_CONFIG_PATH} ]; then
create_zlib_package_config
create_zlib_system_package_config
fi
prepare_inline_sed
}
build_cpufeatures() {
# CLEAN OLD BUILD
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.a 1>>${BASEDIR}/build.log 2>&1
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.so 1>>${BASEDIR}/build.log 2>&1
echo -e "\nINFO: Building cpu-features for ${ARCH}\n" 1>>${BASEDIR}/build.log 2>&1
set_toolchain_clang_paths "cpu-features"
TARGET_HOST=$(get_target_host)
export CFLAGS=$(get_cflags "cpu-features")
export CXXFLAGS=$(get_cxxflags "cpu-features")
export LDFLAGS=$(get_ldflags "cpu-features")
# THEN BUILD FOR THIS ABI
$(get_clang_target_host)-clang -c ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.c -o ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
${TARGET_HOST}-ar rcs ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.a ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
$(get_clang_target_host)-clang -shared ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o -o ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.so 1>>${BASEDIR}/build.log 2>&1
create_cpufeatures_package_config
}
build_android_lts_support() {
# CLEAN OLD BUILD
@@ -1076,117 +931,11 @@ build_android_lts_support() {
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
CFLAGS=$(get_cflags ${LIB_NAME})
LDFLAGS=$(get_ldflags ${LIB_NAME})
# THEN BUILD FOR THIS ABI
$(get_clang_target_host)-clang ${CFLAGS} -Wno-unused-command-line-argument -c ${BASEDIR}/android/app/src/main/cpp/android_lts_support.c -o ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o ${LDFLAGS} 1>>${BASEDIR}/build.log 2>&1
${TARGET_HOST}-ar rcs ${BASEDIR}/android/app/src/main/cpp/libandroidltssupport.a ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o 1>>${BASEDIR}/build.log 2>&1
}
autoreconf_library() {
echo -e "\nDEBUG: Running full autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY FULL RECONF
(autoreconf --force --install)
local EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Full autoreconf failed. Running full autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY FULL RECONF WITH m4
(autoreconf --force --install -I m4)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Full autoreconf with include failed. Running autoreconf without force for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY RECONF WITHOUT FORCE
(autoreconf --install)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Autoreconf without force failed. Running autoreconf without force with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY RECONF WITHOUT FORCE WITH m4
(autoreconf --install -I m4)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Autoreconf without force with include failed. Running default autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY DEFAULT RECONF
(autoreconf)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Default autoreconf failed. Running default autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY DEFAULT RECONF WITH m4
(autoreconf -I m4)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
}
library_is_installed() {
local INSTALL_PATH=$1
local LIB_NAME=$2
echo -e "DEBUG: Checking if ${LIB_NAME} is already built and installed at ${INSTALL_PATH}/${LIB_NAME}\n" 1>>${BASEDIR}/build.log 2>&1
if [ ! -d ${INSTALL_PATH}/${LIB_NAME} ]; then
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME} directory not found\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/lib ]; then
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/lib directory not found\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/include ]; then
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/include directory not found\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
local HEADER_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/include | wc -l)
local LIB_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/lib | wc -l)
if [[ ${HEADER_COUNT} -eq 0 ]]; then
echo -e "DEBUG: No headers found under ${INSTALL_PATH}/${LIB_NAME}/include\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
if [[ ${LIB_COUNT} -eq 0 ]]; then
echo -e "DEBUG: No libraries found under ${INSTALL_PATH}/${LIB_NAME}/lib\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
echo -e "INFO: ${LIB_NAME} library is already built and installed\n" 1>>${BASEDIR}/build.log 2>&1
echo 0
${BUILD_HOST}-ar rcs ${BASEDIR}/android/app/src/main/cpp/libandroidltssupport.a ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o 1>>${BASEDIR}/build.log 2>&1
}
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
echo -e "(*) ANDROID_NDK_ROOT not defined\n"
exit 1
fi
if [[ -z ${ARCH} ]]; then
echo -e "(*) ARCH not defined\n"
exit 1
fi
if [[ -z ${API} ]]; then
echo -e "(*) API not defined\n"
exit 1
fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
LIB_NAME="cpu-features"
set_toolchain_clang_paths ${LIB_NAME}
# DOWNLOAD LIBRARY
DOWNLOAD_RESULT=$(download_library_source ${LIB_NAME})
if [[ ${DOWNLOAD_RESULT} -ne 0 ]]; then
exit 1
fi
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
$(android_ndk_cmake) -DBUILD_PIC=ON || exit 1
make -C $(android_build_dir) install || exit 1
create_cpufeatures_package_config
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="expat"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -37,7 +37,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_expat} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -51,7 +51,7 @@ fi
--enable-static \
--disable-shared \
--disable-fast-install \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+42 -27
View File
@@ -29,12 +29,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="ffmpeg"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
CFLAGS=$(get_cflags ${LIB_NAME})
CXXFLAGS=$(get_cxxflags ${LIB_NAME})
LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -42,41 +42,41 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
TARGET_CPU=""
TARGET_ARCH=""
ASM_FLAGS=""
ARCH_OPTIONS=""
case ${ARCH} in
arm-v7a)
TARGET_CPU="armv7-a"
TARGET_ARCH="armv7-a"
ASM_FLAGS=" --disable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --disable-neon --enable-asm --enable-inline-asm"
;;
arm-v7a-neon)
TARGET_CPU="armv7-a"
TARGET_ARCH="armv7-a"
ASM_FLAGS=" --enable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --enable-neon --enable-asm --enable-inline-asm --build-suffix=_neon"
;;
arm64-v8a)
TARGET_CPU="armv8-a"
TARGET_ARCH="aarch64"
ASM_FLAGS=" --enable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --enable-neon --enable-asm --enable-inline-asm"
;;
x86)
TARGET_CPU="i686"
TARGET_ARCH="i686"
# asm disabled due to this ticker https://trac.ffmpeg.org/ticket/4928
ASM_FLAGS=" --disable-neon --disable-asm --disable-inline-asm"
ARCH_OPTIONS=" --disable-neon --disable-asm --disable-inline-asm"
;;
x86-64)
TARGET_CPU="x86_64"
TARGET_ARCH="x86_64"
ASM_FLAGS=" --disable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --disable-neon --enable-asm --enable-inline-asm"
;;
esac
CONFIGURE_POSTFIX=""
HIGH_PRIORITY_INCLUDES=""
for library in {1..44}
for library in {1..49}
do
if [[ ${!library} -eq 1 ]]; then
ENABLED_LIBRARY=$(get_library_name $((library - 1)))
@@ -163,7 +163,7 @@ do
libvpx)
CFLAGS+=" $(pkg-config --cflags vpx)"
LDFLAGS+=" $(pkg-config --libs vpx)"
LDFLAGS+=" $(pkg-config --libs --static cpufeatures)"
LDFLAGS+=" $(pkg-config --libs cpu-features)"
CONFIGURE_POSTFIX+=" --enable-libvpx"
;;
libwebp)
@@ -178,11 +178,8 @@ do
;;
opencore-amr)
CFLAGS+=" $(pkg-config --cflags opencore-amrnb)"
CFLAGS+=" $(pkg-config --cflags opencore-amrwb)"
LDFLAGS+=" $(pkg-config --libs --static opencore-amrnb)"
LDFLAGS+=" $(pkg-config --libs --static opencore-amrwb)"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrnb"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrwb"
;;
openh264)
FFMPEG_CFLAGS+=" $(pkg-config --cflags openh264)"
@@ -194,6 +191,11 @@ do
LDFLAGS+=" $(pkg-config --libs --static opus)"
CONFIGURE_POSTFIX+=" --enable-libopus"
;;
rubberband)
CFLAGS+=" $(pkg-config --cflags rubberband)"
LDFLAGS+=" $(pkg-config --libs --static rubberband)"
CONFIGURE_POSTFIX+=" --enable-librubberband --enable-gpl"
;;
shine)
CFLAGS+=" $(pkg-config --cflags shine)"
LDFLAGS+=" $(pkg-config --libs --static shine)"
@@ -231,6 +233,11 @@ do
LDFLAGS+=" $(pkg-config --libs --static twolame)"
CONFIGURE_POSTFIX+=" --enable-libtwolame"
;;
vo-amrwbenc)
CFLAGS+=" $(pkg-config --cflags vo-amrwbenc)"
LDFLAGS+=" $(pkg-config --libs --static vo-amrwbenc)"
CONFIGURE_POSTFIX+=" --enable-libvo-amrwbenc"
;;
wavpack)
CFLAGS+=" $(pkg-config --cflags wavpack)"
LDFLAGS+=" $(pkg-config --libs --static wavpack)"
@@ -280,14 +287,14 @@ do
;;
android-media-codec)
CONFIGURE_POSTFIX+=" --enable-mediacodec"
;;
esac
else
# THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT
if [[ ${library} -eq 30 ]]; then
# NOTE THAT IDS MUST BE +1 OF THE INDEX VALUE
if [[ ${library} -eq 31 ]]; then
CONFIGURE_POSTFIX+=" --disable-sdl2"
elif [[ ${library} -eq 43 ]]; then
elif [[ ${library} -eq 46 ]]; then
CONFIGURE_POSTFIX+=" --disable-zlib"
fi
fi
@@ -317,13 +324,19 @@ if [[ -z ${MOBILE_FFMPEG_DEBUG} ]]; then
DEBUG_OPTIONS="--disable-debug --disable-lto";
fi
else
DEBUG_OPTIONS="--enable-debug";
DEBUG_OPTIONS="--enable-debug --disable-stripping";
fi
echo -n -e "\n${LIB_NAME}: "
# DOWNLOAD LIBRARY
DOWNLOAD_RESULT=$(download_library_source ${LIB_NAME})
if [[ ${DOWNLOAD_RESULT} -ne 0 ]]; then
exit 1
fi
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
echo -n -e "\n${LIB_NAME}: "
if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then
echo -e "INFO: Cleaning workspace for ${LIB_NAME}" 1>>${BASEDIR}/build.log 2>&1
make distclean 2>/dev/null 1>/dev/null
@@ -336,12 +349,15 @@ export LDFLAGS="${LDFLAGS}"
# USE HIGHER LIMITS FOR FFMPEG LINKING
ulimit -n 2048 1>>${BASEDIR}/build.log 2>&1
# Workaround for issue #328
rm -f ${BASEDIR}/src/${LIB_NAME}/libswscale/aarch64/hscale.S 1>>${BASEDIR}/build.log 2>&1
cp ${BASEDIR}/tools/make/ffmpeg/libswscale/aarch64/hscale.S ${BASEDIR}/src/${LIB_NAME}/libswscale/aarch64/hscale.S 1>>${BASEDIR}/build.log 2>&1
########################### CUSTOMIZATIONS #######################
# 1. Use thread local log level
${SED_INLINE} 's/static int av_log_level/__thread int av_log_level/g' ${BASEDIR}/src/${LIB_NAME}/libavutil/log.c 1>>${BASEDIR}/build.log 2>&1
###################################################################
./configure \
--cross-prefix="${TARGET_HOST}-" \
--cross-prefix="${BUILD_HOST}-" \
--sysroot="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot" \
--prefix="${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME}" \
--pkg-config="${HOST_PKG_CONFIG_PATH}" \
@@ -350,18 +366,17 @@ cp ${BASEDIR}/tools/make/ffmpeg/libswscale/aarch64/hscale.S ${BASEDIR}/src/${LIB
--cpu="${TARGET_CPU}" \
--cc="${CC}" \
--cxx="${CXX}" \
--extra-libs="$(pkg-config --libs --static cpu-features)" \
--target-os=android \
${ASM_FLAGS} \
${ARCH_OPTIONS} \
--enable-cross-compile \
--enable-pic \
--enable-jni \
--enable-optimizations \
--enable-swscale \
--enable-shared \
--disable-v4l2-m2m \
--disable-outdev=v4l2 \
--enable-v4l2-m2m \
--disable-outdev=fbdev \
--disable-indev=v4l2 \
--disable-indev=fbdev \
${SIZE_OPTIONS} \
--disable-openssl \
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="fontconfig"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -38,7 +38,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_fontconfig} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -55,7 +55,7 @@ fi
--disable-rpath \
--disable-libxml2 \
--disable-docs \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="freetype"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -58,11 +58,11 @@ export LIBPNG_LIBS="-L${BASEDIR}/prebuilt/android-$(get_target_build)/libpng/lib
--disable-shared \
--disable-fast-install \
--disable-mmap \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY
create_freetype_package_config "23.1.17"
create_freetype_package_config "23.2.17"
make install || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="fribidi"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -38,7 +38,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_fribidi} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -52,7 +52,7 @@ fi
--disable-fast-install \
--disable-debug \
--disable-deprecated \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="giflib"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})" -DS_IREAD=S_IRUSR -DS_IWRITE=S_IWUSR"
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -38,7 +38,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_giflib} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -50,7 +50,7 @@ fi
--enable-static \
--disable-shared \
--disable-fast-install \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+5 -5
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="gmp"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -37,7 +37,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_gmp} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -51,11 +51,11 @@ fi
--disable-shared \
--disable-fast-install \
--disable-maintainer-mode \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY
create_gmp_package_config "6.1.2"
create_gmp_package_config "6.2.0"
make install || exit 1
+5 -5
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="gnutls"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
COMMON_CFLAGS=$(get_cflags ${LIB_NAME})
COMMON_CXXFLAGS=$(get_cxxflags ${LIB_NAME})
COMMON_LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -58,7 +58,7 @@ esac
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_gnutls} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -83,11 +83,11 @@ fi
--disable-tests \
--disable-tools \
--disable-maintainer-mode \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY
create_gnutls_package_config "3.6.11.1"
create_gnutls_package_config "3.6.13"
make install || exit 1
+2 -2
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="jpeg"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
+5 -9
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="kvazaar"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -36,14 +36,10 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
./autogen.sh || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
if [[ ${RECONF_kvazaar} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
# ALWAYS RECONFIGURE
autoreconf_library ${LIB_NAME}
# LINKING WITH ANDROID LTS SUPPORT LIBRARY IS NECESSARY FOR API < 18
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${API} < 18 ]]; then
@@ -62,7 +58,7 @@ LIBS="${ARCH_SPECIFIC_LIBS}" ./configure \
--enable-static \
--disable-shared \
--disable-fast-install \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="lame"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -38,7 +38,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_lame} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -55,7 +55,7 @@ fi
--disable-frontend \
--disable-efence \
--disable-gtktest \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="leptonica"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export CPPFLAGS="-I${BASEDIR}/prebuilt/android-$(get_target_build)/giflib/include"
@@ -54,7 +54,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_leptonica} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -72,7 +72,7 @@ fi
--disable-shared \
--disable-fast-install \
--disable-programs \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+2 -2
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libaom"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
CFLAGS=$(get_cflags ${LIB_NAME})
CXXFLAGS=$(get_cxxflags ${LIB_NAME})
LDFLAGS=$(get_ldflags ${LIB_NAME})
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libass"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -38,7 +38,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libass} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -69,7 +69,7 @@ esac
--disable-profile \
--disable-coretext \
${ASM_FLAGS} \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libiconv"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -37,7 +37,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libiconv} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -50,7 +50,7 @@ fi
--disable-shared \
--disable-fast-install \
--disable-rpath \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libilbc"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -37,7 +37,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libilbc} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -49,7 +49,7 @@ fi
--enable-static \
--disable-shared \
--disable-fast-install \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libogg"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -37,7 +37,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libogg} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -49,7 +49,7 @@ fi
--enable-static \
--disable-shared \
--disable-fast-install \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+9 -9
View File
@@ -23,29 +23,29 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libpng"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
CPU_SPECIFIC_OPTIONS=""
ARCH_OPTIONS=""
case ${ARCH} in
x86 | x86-64)
CPU_SPECIFIC_OPTIONS="--enable-hardware-optimizations --enable-intel-sse=yes"
ARCH_OPTIONS="--enable-hardware-optimizations --enable-intel-sse=yes"
;;
arm-v7a-neon | arm64-v8a)
CPU_SPECIFIC_OPTIONS="--enable-hardware-optimizations --enable-arm-neon=yes"
ARCH_OPTIONS="--enable-hardware-optimizations --enable-arm-neon=yes"
;;
arm-v7a)
# hardware-optimizations not enabled because
# when --enable-hardware-optimizations is added
# make tries to build arm-neon specific instructions, which breaks compilation
CPU_SPECIFIC_OPTIONS="--enable-arm-neon=no"
ARCH_OPTIONS="--enable-arm-neon=no"
;;
esac
@@ -53,7 +53,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libpng} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -67,8 +67,8 @@ fi
--disable-fast-install \
--disable-unversioned-libpng-pc \
--disable-unversioned-libpng-config \
${CPU_SPECIFIC_OPTIONS} \
--host=${TARGET_HOST} || exit 1
${ARCH_OPTIONS} \
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
echo -e "(*) ANDROID_NDK_ROOT not defined\n"
exit 1
fi
if [[ -z ${ARCH} ]]; then
echo -e "(*) ARCH not defined\n"
exit 1
fi
if [[ -z ${API} ]]; then
echo -e "(*) API not defined\n"
exit 1
fi
if [[ -z ${BASEDIR} ]]; then
echo -e "(*) BASEDIR not defined\n"
exit 1
fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libsamplerate"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# DISABLE building of examples manually
${SED_INLINE} 's/examples tests//g' ${BASEDIR}/src/${LIB_NAME}/Makefile*
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libsamplerate} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
./configure \
--prefix=${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME} \
--with-pic \
--with-sysroot=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot \
--enable-static \
--disable-fftw \
--disable-shared \
--disable-fast-install \
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
# MANUALLY COPY PKG-CONFIG FILES
cp ./*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
make install || exit 1
+3 -3
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libsndfile"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -53,7 +53,7 @@ fi
--disable-alsa \
--disable-full-suite \
--disable-external-libs \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libtheora"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -38,7 +38,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libtheora} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -60,7 +60,7 @@ fi
--disable-sdltest \
${ASM_FLAGS} \
--disable-valgrind-testing \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="libuuid"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -37,7 +37,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_libuuid} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -49,7 +49,7 @@ fi
--enable-static \
--disable-shared \
--disable-fast-install \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1

Some files were not shown because too many files have changed in this diff Show More