Compare commits

...

118 Commits

Author SHA1 Message Date
Taner Şener 3327609fde Merge pull request #690 from alexcohn/bugfix/#634_fdsan
fixing #634
2021-03-07 11:53:53 +00:00
alexcohn bd5dfc1241 fixing #634
attempted to close file descriptor XX, expected to be unowned, actually owned by ParcelFileDescriptor XXX
2021-03-06 22:49:04 +02:00
Taner Sener 6064b7dd02 update android release script 2021-02-07 00:54:51 +00:00
Juha Heinanen 9d1aefd1ea Added --no-archive option to android.sh 2021-01-23 19:49:48 +00:00
Taner Sener 729ef3e496 fix android async example 2020-11-03 22:22:59 +00:00
Taner Sener ce3a906b4e update ios manual test application 2020-10-22 20:48:02 +01:00
Taner Sener dc4feaceb4 update android test app 2020-09-26 16:20:04 +01:00
Taner Sener 035ad8cd39 support iOS 14, fixes #553 2020-09-26 15:29:08 +01:00
Alex Cohn b898574b53 Support Android scoped storage (#440)
* introducing saf_wrapper - a transparent API to use the file descriptor via custom AVIO for saf: Url

* demonstrate how use to use SAF with some demo tabs

SAF is enabled for API 19 or higher
SAF pickers are used for FFprobe on CommandTabFragment and for video output on PipeTabFragment

* Revert "demonstrate how use to use SAF with some demo tabs"

This reverts commit 97b25815

* add SAF tab with two buttons

* change the getSafParameter handles filenames with spaces

see https://github.com/alexcohn/mobile-ffmpeg/pull/1#issuecomment-688643836

* take advantage of Cursor being Closeable

* provide NBSP in hexa, to make sure that git and editor don't destroy it

* Avoid "rw" because it may not be supported (e.g. Google Drive).

Also, some code cleanup in SafTabFragment.

* don't run FFprobe after video playback

* ACTION_GET_CONTENT is better than ACTION_OPEN_DOCUMENT: it gives us more options to choose.

* add closeParcelFileDescriptor to fix working with Google Drive

keep the temp files open, we must keep the ParcelFileDecriptor until the fd is closed
2020-09-21 22:29:22 +01:00
Pete ef39b78068 Update arch-common.sh (#534)
* Update arch-common.sh

x265 download on Bitbucket no longer works. Use GitHub link instead
2020-08-24 13:48:38 +01:00
Taner Sener 3490446b1b fix monitorWait method for android, fixes #520 2020-08-10 23:41:56 +01:00
Taner Sener 0f911a26ed reorganise android.mk for cpu-features 2020-08-06 23:51:56 +01:00
Taner Sener 55700fb8bb update the packaging for android arm-v7a and arm-v7a-neon architectures and remove custom abi rules for releases, fixes #511 2020-08-02 21:45:51 +01:00
Taner Sener 4be74be2a2 fix proguard rules, fixes #508 2020-08-01 10:30:24 +01:00
Taner Sener 70b8686eee target android api level 30 2020-07-30 09:40:08 +01:00
Taner Sener 870959fbd3 update android gradle version 2020-07-30 09:30:02 +01:00
Taner Şener 64b92f9d85 Merge pull request #501 from alexcohn/fix/tooltips
move tooltips to resources
2020-07-30 09:11:57 +01:00
Taner Sener 495e0490a9 Update README 2020-07-29 19:00:04 +01:00
alexcohn dbebf28b10 move tooltips to resources
on the way, fix a typo
2020-07-27 14:56:17 +03:00
Taner Sener 5b9637f517 fix android release packaging script 2020-07-25 17:35:17 +01:00
Taner Sener 5c3409484c update ios test-app settings 2020-07-25 13:46:50 +01:00
Taner Sener 8bbbf4b0a7 fix ios/tvos system library indexes in ffmpeg build scripts 2020-07-25 09:39:35 +01:00
Taner Sener 83fca5d126 use v4.4.LTS in test applications 2020-07-25 00:15:55 +01:00
Taner Sener 56a8d28225 release v4.4.LTS 2020-07-24 01:03:26 +01:00
Taner Sener 48836fc94a fix mobile-ffmpeg videotoolbox flags for tvos 2020-07-24 01:03:26 +01:00
Taner Sener db2fe927e6 fix processing of ios/tvos flags 2020-07-24 01:03:26 +01:00
Taner Sener e9a6f5fb21 fix android lts neon packaging 2020-07-23 21:53:01 +01:00
Taner Sener 13a2d0e264 fix x265 compilation errors on ios/tvos 2020-07-22 23:00:32 +01:00
Taner Sener 79cda81490 introduce execution specific log levels, fixes #495 2020-07-22 00:10:01 +01:00
Taner Sener 704ee05c3b synchronize updating lastCommandOutput on ios/tvos, fixes #494 2020-07-21 01:43:01 +01:00
Taner Sener 8dd51a619d do not print parsing exceptions for not found keys on android 2020-07-19 23:01:43 +01:00
Taner Sener 0dbdec7ae5 update libsamplerate build workarounds 2020-07-19 15:55:12 +01:00
Taner Sener 2bae484aff do not process log lines with zero length on ios/tvos 2020-07-19 15:44:21 +01:00
Taner Sener 05880133ec disable building of audiotoolbox output device on ios/tvos 2020-07-19 12:47:06 +01:00
Taner Sener 091b4c62fb freeze v4.4 2020-07-19 03:45:48 +01:00
Taner Sener 1c0dcf92ae enable redownloading of ffmpeg source code 2020-07-19 03:08:40 +01:00
Taner Sener 47a64b3d7f use new freetype version 2.10.2 2020-07-19 02:06:02 +01:00
Taner Sener 7e289a014d remove coreimage references on ios/tvos, fixes #480 2020-07-19 01:22:58 +01:00
Taner Sener 4f6a80a5bb use new ffmpeg version v4.4-dev-416-g1998d1d6af 2020-07-19 01:19:34 +01:00
Taner Sener abddac04ea use new libjpeg-turbo version v2.0.5 2020-07-18 18:02:31 +01:00
Taner Sener effa46c2d1 use new nettle version v3.6 2020-07-18 17:23:53 +01:00
Taner Sener 6f22bd47bd use new libvorbis version v1.3.7 2020-07-18 17:04:06 +01:00
Taner Sener 6f4e7577ba use new kvazaar version v2.0.0 2020-07-18 16:34:59 +01:00
Taner Sener b97fddb0ed use new wavpack version v5.3.0 2020-07-18 16:23:32 +01:00
Taner Sener 258fa6e40a use new chromaprint version v1.5.0 2020-07-18 16:13:03 +01:00
Taner Sener 36be20cdf0 use new x264 version v20200630-stable 2020-07-18 14:45:58 +01:00
Taner Sener 6d3189eee0 use new x265 version v3.4 2020-07-18 14:33:25 +01:00
Taner Sener ed65d00f59 test openh264 in ios/tvos test applications 2020-07-18 13:54:56 +01:00
Taner Sener b4710f7025 add startTime to FFmpegExecution class 2020-07-18 13:51:23 +01:00
Taner Sener c2ffdd7a10 use a thread safe implementation to list ongoing executions 2020-07-18 11:21:13 +01:00
Taner Sener 2e0ac940dc introduce an api method to list ongoing executions 2020-07-18 01:54:12 +01:00
Taner Sener 60892d4a84 switch to personal fork for cpu-features, use in-repo sources for expat and openh264 2020-07-17 22:23:05 +01:00
Taner Sener e3a1fbcc99 fix android env variables in travis 2020-06-27 10:53:23 +01:00
Taner Sener accd5976cd fix test commands in android test app 2020-06-27 00:41:09 +01:00
Taner Sener c888eee1c0 fix ndk version in travis 2020-06-27 00:32:19 +01:00
Taner Sener c075268d0b add executionId to log and statistic callbacks 2020-06-27 00:00:10 +01:00
Taner Sener d46274f5af implement ffmpeg async api 2020-06-26 18:25:52 +01:00
Taner Sener 2ea824c983 update stale.yml 2020-06-21 11:23:59 +01:00
Taner Sener 074bcd553a update release scripts to use the latest ndk version 2020-06-19 22:48:40 +01:00
Taner Sener 13f72226d8 implement an api method to set environment variables, fixes #466 2020-06-19 22:47:49 +01:00
Taner Sener 2469b8e85b update ios test-app versions 2020-06-18 01:03:24 +01:00
Taner Sener 1bb6cd532a disable avfoundation device for ios lts releases, fixes #462 2020-06-18 00:45:09 +01:00
Taner Sener e52bad8a35 use the latest android ndk 2020-06-17 23:28:50 +01:00
Taner Sener 3efd2ecee5 implement ignoreSignal api method, fixes #258, #214 2020-06-17 23:28:40 +01:00
Taner Sener f6189815f0 implement bash functions to download external libraries 2020-06-14 22:57:35 +01:00
Taner Sener f382a0c3a0 switch openh264 source code with openh264 submodule 2020-06-14 01:51:27 +01:00
Taner Sener 1706d2aead update submodules in top level build scripts 2020-06-14 01:32:02 +01:00
Taner Sener c43aa5bcdf switch expat source code with expat submodule 2020-06-14 01:13:01 +01:00
Taner Sener ca2e6f0ae0 switch src/cpu-features remote to google/cpu_features 2020-06-13 23:54:16 +01:00
Taner Sener 8307c30491 remove unused test resources 2020-06-13 23:19:06 +01:00
Taner Sener 986ad8c094 fix travis builds 2020-06-13 22:49:04 +01:00
Taner Sener 66539ab037 do not install lldb for android in travis builds 2020-06-13 13:11:00 +01:00
Taner Sener 70ae95f5fa implement toString() method for Statistics and LogMessage classes, fixes #438 2020-06-13 13:05:22 +01:00
Taner Sener 9095e3262e release unreleased utf strings in setNativeEnvironmentVariable(), fixes #443 2020-06-12 23:15:46 +01:00
Taner Sener 9a2e0a75f2 do not detach main thread on error cases, fixes #446 2020-06-12 22:57:17 +01:00
Taner Sener ce10a88194 disable stripping of debug symbols for ffmpeg, fixes #447 2020-06-12 00:17:40 +01:00
Taner Sener f041df28a9 fix javadoc warnings 2020-06-09 00:57:42 +01:00
Taner Sener d216310c9c list cpu-features after builtin libraries 2020-06-09 00:51:37 +01:00
Taner Sener 8a1653fc7b bump version 2020-06-09 00:42:50 +01:00
Taner Sener 687863efc8 disable publishing to bintray 2020-06-09 00:42:50 +01:00
Taner Sener 7a3a94ee9b automatically update submodules from android.sh 2020-06-09 00:42:49 +01:00
Taner Sener 4a343d8b87 make android-cpu-features.sh executable 2020-06-07 20:42:55 +01:00
Taner Sener b9c2b573df Merge pull request #375 from alexcohn/development
use google/cpu_features
2020-05-26 23:09:53 +01:00
alexcohn 79d313e201 set packages path 2020-05-26 13:24:08 +03:00
alexcohn 888d622662 let openh264 work with external cpu-features
compatible with NDK deperecated implementation and with ndk_compat from https://github.com/google/cpu_features
2020-05-26 11:47:02 +03:00
alexcohn 6aeb10570d Using static lib for Google cpu-features 2020-05-26 09:11:19 +03:00
alexcohn 90ebed56f6 revert the commit 740fd2e9
load of c++_shared determined by the list of enabled external libraries
2020-05-25 19:01:25 +03:00
alexcohn ffccbc889d not needed anymore 2020-05-25 18:52:22 +03:00
alexcohn 68bba7b7f2 fix for LTS 2020-05-25 18:51:29 +03:00
alexcohn de7f3fcef7 update the Android.mk files to reflect cpu-features rename
also, remove unnecessary dependencies
2020-05-25 18:50:23 +03:00
alexcohn 555bee6f36 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
2020-05-25 18:39:30 +03:00
alexcohn df02234ece rename the submodule library 2020-05-25 18:13:17 +03:00
alexcohn 2fb22fd3d4 switch the submodule to fork 2020-05-19 22:42:10 +03:00
alexcohn 740fd2e92a 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).
2020-05-17 22:31:58 +03:00
alexcohn 8047e66a52 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.
2020-05-17 22:22:14 +03:00
alexcohn 58336842e4 Revert "with NDK r20 and higher, openh264.a does not depend on libc++"
This reverts commit dc751016
2020-05-17 14:10:23 +03:00
alexcohn dc75101660 with NDK r20 and higher, openh264.a does not depend on libc++ 2020-05-16 01:37:37 +03:00
alexcohn b20a66521f fix Android.lts.mk 2020-05-12 19:15:51 +03:00
alexcohn e59955e164 Merge remote-tracking branch 'upstream/development' into development
One more library added in upstream
2020-05-12 19:14:47 +03:00
alexcohn e6a694ad6d Merge branch 'feature/cpu-features' into development 2020-05-12 14:39:07 +03:00
alexcohn e77f897159 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
2020-05-12 14:07:01 +03:00
alexcohn 5139538028 use cpu_features script
it's a regular library, only it's enabled by default
2020-05-12 13:59:42 +03:00
alexcohn 42d2f302d4 fix lint errors 2020-05-12 13:57:03 +03:00
alexcohn c4a5c88f55 add cpu_features script
and some helper fuctions to android-common
2020-05-12 08:54:10 +03:00
alexcohn 95ff39f588 bring in cpu_features as a submodule 2020-05-11 23:26:55 +03:00
alexcohn 0808b30494 let the project be opened and managed in Android Studio 2020-05-11 11:30:40 +03:00
Taner Sener 9df1b5263a Enable all gpl libraries when both --full and --enable-gpl flags are present, fixes #394 2020-05-11 00:25:35 +01:00
Taner Sener bf1f4744ce add support for vo-amrwbenc, fixes #381 2020-05-11 00:25:34 +01:00
Taner Sener f279c540c5 Remove internal log line limit on Android, fixes #418 2020-05-10 15:55:08 +01:00
Taner Sener 7b80cd367a Use JSON parsing in getMediaInformation, fixes #417 2020-05-09 23:34:00 +01:00
Taner Sener 87a7373383 Enable stale action 2020-05-08 14:29:34 +01:00
alexcohn 6e094b71e4 import cpufeatures from NDK 2020-04-06 19:39:04 +03:00
alexcohn 51c1534de3 Merge remote-tracking branch 'upstream/development' into development-prefab 2020-04-06 15:50:50 +03:00
alexcohn 116d8c662d let AndroidStudio manage project root 2020-04-06 15:49:48 +03:00
Alex Cohn f4148f716e 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 18:42:49 +03:00
Alex Cohn e888ce39d7 remove custom common_includes
The includes are handled by llvm via --sysroot parameter.
2020-04-01 16:52:21 +03:00
alexcohn 5efd74e033 apply 'use build host and target host in build scripts' efe1c59a 2020-03-31 16:13:44 +03:00
alexcohn 5e7c741ed3 cleanup openh264 build 2020-03-31 14:40:27 +03:00
10082 changed files with 158721 additions and 1827307 deletions
+7 -8
View File
@@ -4,6 +4,7 @@ branches:
- development - development
git: git:
quiet: true quiet: true
depth: false
jobs: jobs:
include: include:
- name: "Android Main Build" - name: "Android Main Build"
@@ -24,7 +25,6 @@ jobs:
- gperf - gperf
- texinfo - texinfo
- yasm - yasm
- nasm
- bison - bison
- autogen - autogen
- patch - patch
@@ -38,14 +38,14 @@ jobs:
- extra-google-m2repository - extra-google-m2repository
- extra-android-m2repository - extra-android-m2repository
install: install:
- echo y | sdkmanager "ndk;21.0.6113669" - echo y | sdkmanager "ndk;21.3.6528147"
- echo y | sdkmanager "cmake;3.10.2.4988404" - echo y | sdkmanager "cmake;3.10.2.4988404"
- echo y | sdkmanager "lldb;3.1"
before_install: before_install:
- touch $HOME/.android/repositories.cfg - touch $HOME/.android/repositories.cfg
before_script: before_script:
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/21.0.6113669 - export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
- rm -f ./build.log - 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: after_success:
- grep -e INFO ./build.log | grep build - grep -e INFO ./build.log | grep build
after_failure: after_failure:
@@ -71,7 +71,6 @@ jobs:
- gperf - gperf
- texinfo - texinfo
- yasm - yasm
- nasm
- bison - bison
- autogen - autogen
- patch - patch
@@ -86,14 +85,14 @@ jobs:
- extra-google-m2repository - extra-google-m2repository
- extra-android-m2repository - extra-android-m2repository
install: install:
- echo y | sdkmanager "ndk;21.0.6113669" - echo y | sdkmanager "ndk;21.3.6528147"
- echo y | sdkmanager "cmake;3.10.2.4988404" - echo y | sdkmanager "cmake;3.10.2.4988404"
- echo y | sdkmanager "lldb;3.1"
before_install: before_install:
- touch $HOME/.android/repositories.cfg - touch $HOME/.android/repositories.cfg
before_script: before_script:
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/21.0.6113669 - export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
- rm -f ./build.log - 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: after_success:
- grep -e INFO ./build.log | grep build - grep -e INFO ./build.log | grep build
after_failure: after_failure:
+165 -78
View File
@@ -1,4 +1,4 @@
# 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.2-blue.svg) ![Bintray](https://img.shields.io/badge/bintray-v4.3.2-blue.svg) ![CocoaPods](https://img.shields.io/badge/pod-v4.3.2-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) ![Bintray](https://img.shields.io/badge/bintray-v4.4-blue.svg) ![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
@@ -9,10 +9,10 @@ FFmpeg for Android, iOS and tvOS
- Use binaries available at `Github`/`JCenter`/`CocoaPods` or build your own version with external libraries you need - Use binaries available at `Github`/`JCenter`/`CocoaPods` or build your own version with external libraries you need
- Supports - Supports
- Android, iOS and tvOS - Android, iOS and tvOS
- FFmpeg `v3.4.x`, `v4.0.x`, `v4.1`, `v4.2` and `v4.3-dev` releases - FFmpeg `v3.4.x`, `v4.0.x`, `v4.1`, `v4.2` , `v4.3` and `v4.4-dev` releases
- 28 external libraries - 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`
- 5 external libraries with GPL license - 5 external libraries with GPL license
@@ -21,9 +21,9 @@ FFmpeg for Android, iOS and tvOS
- Concurrent execution - Concurrent execution
- Exposes both FFmpeg library and MobileFFmpeg wrapper library capabilities - Exposes both FFmpeg library and MobileFFmpeg wrapper library capabilities
- Includes cross-compile instructions for 46 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`, `libsamplerate`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `openh264`, `opus`, `rubberband`, `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 - Licensed under LGPL 3.0, can be customized to support GPL v3.0
@@ -37,7 +37,7 @@ FFmpeg for Android, iOS and tvOS
#### 1.2 iOS #### 1.2 iOS
- Builds `armv7`, `armv7s`, `arm64`, `arm64e`, `i386`, `x86_64` and `x86_64` (Mac Catalyst) architectures - Builds `armv7`, `armv7s`, `arm64`, `arm64e`, `i386`, `x86_64` and `x86_64` (Mac Catalyst) architectures
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` system frameworks - Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox`, `AVFoundation` system frameworks
- Objective-C API - Objective-C API
- Camera access - Camera access
- `ARC` enabled library - `ARC` enabled library
@@ -47,7 +47,7 @@ FFmpeg for Android, iOS and tvOS
#### 1.3 tvOS #### 1.3 tvOS
- Builds `arm64` and `x86_64` architectures - Builds `arm64` and `x86_64` architectures
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox` system frameworks - Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox` system frameworks
- Objective-C API - Objective-C API
- `ARC` enabled library - `ARC` enabled library
- Built with `-fembed-bitcode` flag - Built with `-fembed-bitcode` flag
@@ -55,7 +55,10 @@ FFmpeg for Android, iOS and tvOS
- Supports `tvOS SDK 9.2` or later - Supports `tvOS SDK 9.2` or later
### 2. Using ### 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), [JCenter](https://bintray.com/bintray/jcenter) 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. 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 +68,14 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
<thead> <thead>
<tr> <tr>
<th align="center"></th> <th align="center"></th>
<th align="center">min</th> <th align="center"><sup>min</sup></th>
<th align="center">min-gpl</th> <th align="center"><sup>min-gpl</sup></th>
<th align="center">https</th> <th align="center"><sup>https</sup></th>
<th align="center">https-gpl</th> <th align="center"><sup>https-gpl</sup></th>
<th align="center">audio</th> <th align="center"><sup>audio</sup></th>
<th align="center">video</th> <th align="center"><sup>video</sup></th>
<th align="center">full</th> <th align="center"><sup>full</sup></th>
<th align="center">full-gpl</th> <th align="center"><sup>full-gpl</sup></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -82,10 +85,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>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></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>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>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>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>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>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>
<tr> <tr>
<td align="center"><sup>android system libraries</sup></td> <td align="center"><sup>android system libraries</sup></td>
@@ -93,11 +96,11 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
</tr> </tr>
<tr> <tr>
<td align="center"><sup>ios system libraries</sup></td> <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>
<tr> <tr>
<td align="center"><sup>tvos system libraries</sup></td> <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> </tr>
</tbody> </tbody>
</table> </table>
@@ -108,7 +111,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` - `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 - 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 +119,17 @@ 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 - Since `v4.3.1`, `iOS` and `tvOS` releases started to use `iconv` system library instead of `iconv` external library
#### 2.1 Android - `vo-amrwbenc` is supported since `v4.4`
1. Add MobileFFmpeg dependency to your `build.gradle` in `mobile-ffmpeg-<package name>` format
#### 2.2 Android
1. Add MobileFFmpeg dependency to your `build.gradle` in `mobile-ffmpeg-<package name>` pattern.
``` ```
dependencies { dependencies {
implementation 'com.arthenica:mobile-ffmpeg-full:4.3.2' implementation 'com.arthenica:mobile-ffmpeg-full:4.4'
} }
``` ```
2. Execute FFmpeg commands. 2. Execute synchronous FFmpeg commands.
``` ```
import com.arthenica.mobileffmpeg.Config; import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFmpeg; import com.arthenica.mobileffmpeg.FFmpeg;
@@ -141,7 +146,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.Config;
import com.arthenica.mobileffmpeg.FFprobe; import com.arthenica.mobileffmpeg.FFprobe;
@@ -156,7 +181,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(); int rc = Config.getLastReturnCode();
@@ -170,26 +195,26 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
} }
``` ```
5. Stop an ongoing FFmpeg operation. 6. Stop ongoing FFmpeg operations.
``` - Stop all executions
FFmpeg.cancel(); ```
``` 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>"); 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>"); 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. 9. Enable log callback.
``` ```
Config.enableLogCallback(new LogCallback() { Config.enableLogCallback(new LogCallback() {
@@ -207,31 +232,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); 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); Config.setFontDirectory(this, "<folder with fonts>", Collections.EMPTY_MAP);
``` ```
#### 2.2 iOS / tvOS #### 2.3 iOS / tvOS
1. Add MobileFFmpeg dependency to your `Podfile` in `mobile-ffmpeg-<package name>` format 1. Add MobileFFmpeg dependency to your `Podfile` in `mobile-ffmpeg-<package name>` pattern.
- iOS - iOS
``` ```
pod 'mobile-ffmpeg-full', '~> 4.3.2' pod 'mobile-ffmpeg-full', '~> 4.4'
``` ```
- tvOS - tvOS
``` ```
pod 'mobile-ffmpeg-tvos-full', '~> 4.3.2' pod 'mobile-ffmpeg-tvos-full', '~> 4.4'
``` ```
2. Execute FFmpeg commands. 2. Execute synchronous FFmpeg commands.
``` ```
#import <mobileffmpeg/MobileFFmpegConfig.h> #import <mobileffmpeg/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFmpeg.h> #import <mobileffmpeg/MobileFFmpeg.h>
@@ -247,7 +285,25 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
} }
``` ```
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/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFprobe.h> #import <mobileffmpeg/MobileFFprobe.h>
@@ -263,7 +319,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]; int rc = [MobileFFmpegConfig getLastReturnCode];
NSString *output = [MobileFFmpegConfig getLastCommandOutput]; NSString *output = [MobileFFmpegConfig getLastCommandOutput];
@@ -277,32 +333,33 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
} }
``` ```
5. Stop an ongoing FFmpeg operation. 6. Stop ongoing FFmpeg operations.
``` - Stop all executions
[MobileFFmpeg cancel]; ```
``` [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>"]; 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]; [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. 9. Enable log callback.
``` ```
[MobileFFmpegConfig setLogDelegate:self]; [MobileFFmpegConfig setLogDelegate:self];
- (void)logCallback: (int)level :(NSString*)message { - (void)logCallback:(long)executionId :(int)level :(NSString*)message {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", message); NSLog(@"%@", message);
}); });
@@ -320,33 +377,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]; [MobileFFmpegConfig setLogLevel:AV_LOG_FATAL];
``` ```
12. Register custom fonts directory. 14. Register custom fonts directory.
``` ```
[MobileFFmpegConfig setFontDirectory:@"<folder with fonts>" with:nil]; [MobileFFmpegConfig setFontDirectory:@"<folder with fonts>" with:nil];
``` ```
#### 2.3 Manual Installation #### 2.4 Manual Installation
##### 2.3.1 Android ##### 2.4.1 Android
You can import `MobileFFmpeg` aar packages in `Android Studio` using the `File` -> `New` -> `New Module` -> `Import .JAR/.AAR Package` menu. 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. 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. 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. 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 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. `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, 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"> <img src="https://github.com/tanersener/mobile-ffmpeg/blob/master/docs/assets/android_test_app.gif" width="240">
@@ -362,6 +433,8 @@ Exact version number is obtained using `git describe --tags`.
| MobileFFmpeg Version | FFmpeg Version | Release Date | | 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.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](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.1.LTS](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3.1.LTS) | 4.3-dev-1944 | Jan 25, 2020 |
@@ -398,6 +471,7 @@ 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 | | 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 | | Xcode Support | 10.1 | 7.3.1 |
| iOS SDK | 12.1 | 9.3 | | iOS SDK | 12.1 | 9.3 |
| 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 | | 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 SDK | 10.2 | 9.2 |
| tvOS Architectures | arm64<br/>x86-64 | arm64<br/>x86-64 | | tvOS Architectures | arm64<br/>x86-64 | arm64<br/>x86-64 |
@@ -538,34 +612,47 @@ Support this project with your organization. Your logo will show up here with a
### 8. License ### 8. License
This project is licensed under the LGPL v3.0. However, if source code is built using optional `--enable-gpl` flag or `MobileFFmpeg` is licensed under the LGPL v3.0. However, if source code is built using the optional `--enable-gpl` flag
prebuilt binaries with `-gpl` postfix are used then MobileFFmpeg is subject to the GPL v3.0 license. 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 `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 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 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. 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. Please visit [License](https://github.com/tanersener/mobile-ffmpeg/wiki/License) page for the details.
### 9. Contributing ### 9. Patents
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).
`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. 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 Please note that `master` branch includes only the latest released source code. Changes planned for the next release
are implemented under the `development` branch. So, if you want to create a pull request, please open it against the are implemented under the `development` branch. Therefore, if you want to create a pull request, please open it against
`development`. the `development`.
### 10. See Also ### 11. See Also
- [libav gas-preprocessor](https://github.com/libav/gas-preprocessor/raw/master/gas-preprocessor.pl) - [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 API Documentation](https://ffmpeg.org/doxygen/4.0/index.html)
- [FFmpeg Wiki](https://trac.ffmpeg.org/wiki/WikiStart) - [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) - [FFmpeg External Library Licenses](https://www.ffmpeg.org/doxygen/4.0/md_LICENSE.html)
+121 -74
View File
@@ -41,34 +41,36 @@ LIBRARY_TWOLAME=29
LIBRARY_SDL=30 LIBRARY_SDL=30
LIBRARY_TESSERACT=31 LIBRARY_TESSERACT=31
LIBRARY_OPENH264=32 LIBRARY_OPENH264=32
LIBRARY_GIFLIB=33 LIBRARY_VO_AMRWBENC=33
LIBRARY_JPEG=34 LIBRARY_GIFLIB=34
LIBRARY_LIBOGG=35 LIBRARY_JPEG=35
LIBRARY_LIBPNG=36 LIBRARY_LIBOGG=36
LIBRARY_LIBUUID=37 LIBRARY_LIBPNG=37
LIBRARY_NETTLE=38 LIBRARY_LIBUUID=38
LIBRARY_TIFF=39 LIBRARY_NETTLE=39
LIBRARY_EXPAT=40 LIBRARY_TIFF=40
LIBRARY_SNDFILE=41 LIBRARY_EXPAT=41
LIBRARY_LEPTONICA=42 LIBRARY_SNDFILE=42
LIBRARY_LIBSAMPLERATE=43 LIBRARY_LEPTONICA=43
LIBRARY_ZLIB=44 LIBRARY_LIBSAMPLERATE=44
LIBRARY_MEDIA_CODEC=45 LIBRARY_ZLIB=45
LIBRARY_MEDIA_CODEC=46
LIBRARY_CPU_FEATURES=47
# ENABLE ARCH # ENABLE ARCH
ENABLED_ARCHITECTURES=(1 1 1 1 1) ENABLED_ARCHITECTURES=(1 1 1 1 1)
# ENABLE LIBRARIES # ENABLE LIBRARIES
ENABLED_LIBRARIES=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) ENABLED_LIBRARIES=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1)
export BASEDIR=$(pwd) export BASEDIR=$(pwd)
export MOBILE_FFMPEG_TMPDIR="${BASEDIR}/.tmp"
# USING API LEVEL 24 / Android 7.0 (NOUGAT) # USING API LEVEL 24 / Android 7.0 (NOUGAT)
export API=24 export API=24
RECONF_LIBRARIES=() RECONF_LIBRARIES=()
REBUILD_LIBRARIES=() REBUILD_LIBRARIES=()
REDOWNLOAD_LIBRARIES=()
get_mobile_ffmpeg_version() { get_mobile_ffmpeg_version() {
local MOBILE_FFMPEG_VERSION=$(grep '#define MOBILE_FFMPEG_VERSION' ${BASEDIR}/android/app/src/main/cpp/mobileffmpeg.h | grep -Eo '\".*\"' | sed -e 's/\"//g') local MOBILE_FFMPEG_VERSION=$(grep '#define MOBILE_FFMPEG_VERSION' ${BASEDIR}/android/app/src/main/cpp/mobileffmpeg.h | grep -Eo '\".*\"' | sed -e 's/\"//g')
@@ -99,7 +101,7 @@ When compilation ends an Android Archive (AAR) file is created under the prebuil
echo -e "Licensing options:" echo -e "Licensing options:"
echo -e " --enable-gpl\t\t\tallow use of GPL libraries, resulting libs will be licensed under GPLv3.0 [no]\n" echo -e " --enable-gpl\t\t\tallow use of GPL libraries, created libs will be licensed under GPLv3.0 [no]\n"
echo -e "Platforms:" echo -e "Platforms:"
@@ -141,6 +143,7 @@ When compilation ends an Android Archive (AAR) file is created under the prebuil
echo -e " --enable-speex\t\tbuild with speex [no]" echo -e " --enable-speex\t\tbuild with speex [no]"
echo -e " --enable-tesseract\t\tbuild with tesseract [no]" echo -e " --enable-tesseract\t\tbuild with tesseract [no]"
echo -e " --enable-twolame\t\tbuild with twolame [no]" echo -e " --enable-twolame\t\tbuild with twolame [no]"
echo -e " --enable-vo-amrwbenc\t\tbuild with vo-amrwbenc [no]"
echo -e " --enable-wavpack\t\tbuild with wavpack [no]\n" echo -e " --enable-wavpack\t\tbuild with wavpack [no]\n"
echo -e "GPL libraries:" echo -e "GPL libraries:"
@@ -154,7 +157,9 @@ When compilation ends an Android Archive (AAR) file is created under the prebuil
echo -e "Advanced options:" echo -e "Advanced options:"
echo -e " --reconf-LIBRARY\t\trun autoreconf before building LIBRARY [no]" echo -e " --reconf-LIBRARY\t\trun autoreconf before building LIBRARY [no]"
echo -e " --redownload-LIBRARY\t\tdownload LIBRARY even it is detected as already downloaded [no]"
echo -e " --rebuild-LIBRARY\t\tbuild LIBRARY even it is detected as already built [no]\n" echo -e " --rebuild-LIBRARY\t\tbuild LIBRARY even it is detected as already built [no]\n"
echo -e " --no-archive\t\tdo not build Android archive [no]\n"
} }
display_version() { display_version() {
@@ -211,7 +216,7 @@ reconf_library() {
local RECONF_VARIABLE=$(echo "RECONF_$1" | sed "s/\-/\_/g") local RECONF_VARIABLE=$(echo "RECONF_$1" | sed "s/\-/\_/g")
local library_supported=0 local library_supported=0
for library in {1..44}; do for library in {1..45}; do
library_name=$(get_library_name $((library - 1))) library_name=$(get_library_name $((library - 1)))
if [[ $1 != "ffmpeg" ]] && [[ ${library_name} == $1 ]]; then if [[ $1 != "ffmpeg" ]] && [[ ${library_name} == $1 ]]; then
@@ -230,8 +235,8 @@ rebuild_library() {
local REBUILD_VARIABLE=$(echo "REBUILD_$1" | sed "s/\-/\_/g") local REBUILD_VARIABLE=$(echo "REBUILD_$1" | sed "s/\-/\_/g")
local library_supported=0 local library_supported=0
for library in {1..44}; do for library in {0..47}; do
library_name=$(get_library_name $((library - 1))) library_name=$(get_library_name ${library})
if [[ $1 != "ffmpeg" ]] && [[ ${library_name} == $1 ]]; then if [[ $1 != "ffmpeg" ]] && [[ ${library_name} == $1 ]]; then
export ${REBUILD_VARIABLE}=1 export ${REBUILD_VARIABLE}=1
@@ -245,6 +250,31 @@ rebuild_library() {
fi fi
} }
redownload_library() {
local REDOWNLOAD_VARIABLE=$(echo "REDOWNLOAD_$1" | sed "s/\-/\_/g")
local library_supported=0
for library in {0..47}; do
library_name=$(get_library_name ${library})
if [[ ${library_name} == $1 ]]; then
export ${REDOWNLOAD_VARIABLE}=1
REDOWNLOAD_LIBRARIES+=($1)
library_supported=1
fi
done
if [[ "ffmpeg" == $1 ]]; then
export ${REDOWNLOAD_VARIABLE}=1
REDOWNLOAD_LIBRARIES+=($1)
library_supported=1
fi
if [[ ${library_supported} -eq 0 ]]; then
echo -e "INFO: --redownload flag detected for library $1 is not supported.\n" 1>>${BASEDIR}/build.log 2>&1
fi
}
enable_library() { enable_library() {
set_library $1 1 set_library $1 1
} }
@@ -384,6 +414,9 @@ set_library() {
ENABLED_LIBRARIES[LIBRARY_TWOLAME]=$2 ENABLED_LIBRARIES[LIBRARY_TWOLAME]=$2
ENABLED_LIBRARIES[LIBRARY_SNDFILE]=$2 ENABLED_LIBRARIES[LIBRARY_SNDFILE]=$2
;; ;;
vo-amrwbenc)
ENABLED_LIBRARIES[LIBRARY_VO_AMRWBENC]=$2
;;
wavpack) wavpack)
ENABLED_LIBRARIES[LIBRARY_WAVPACK]=$2 ENABLED_LIBRARIES[LIBRARY_WAVPACK]=$2
;; ;;
@@ -481,19 +514,7 @@ print_enabled_libraries() {
let enabled=0 let enabled=0
# FIRST BUILT-IN LIBRARIES for library in {45..47} {0..33}; do
for library in {44..45}; do
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
if [[ ${enabled} -ge 1 ]]; then
echo -n ", "
fi
echo -n $(get_library_name $library)
enabled=$((${enabled} + 1))
fi
done
# THEN EXTERNAL LIBRARIES
for library in {0..32}; do
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
if [[ ${enabled} -ge 1 ]]; then if [[ ${enabled} -ge 1 ]]; then
echo -n ", " echo -n ", "
@@ -550,6 +571,26 @@ print_rebuild_requested_libraries() {
fi fi
} }
print_redownload_requested_libraries() {
local counter=0
for REDOWNLOAD_LIBRARY in "${REDOWNLOAD_LIBRARIES[@]}"; do
if [[ ${counter} -eq 0 ]]; then
echo -n "Redownload: "
else
echo -n ", "
fi
echo -n ${REDOWNLOAD_LIBRARY}
counter=$((${counter} + 1))
done
if [[ ${counter} -gt 0 ]]; then
echo ""
fi
}
build_application_mk() { build_application_mk() {
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]]; then if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]]; then
local LTS_BUILD_FLAG="-DMOBILE_FFMPEG_LTS " local LTS_BUILD_FLAG="-DMOBILE_FFMPEG_LTS "
@@ -559,8 +600,6 @@ build_application_mk() {
local APP_STL="c++_shared" local APP_STL="c++_shared"
else else
local APP_STL="none" local APP_STL="none"
${SED_INLINE} 's/c++_shared //g' ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
fi fi
local BUILD_DATE="-DMOBILE_FFMPEG_BUILD_DATE=$(date +%Y%m%d 2>>${BASEDIR}/build.log)" local BUILD_DATE="-DMOBILE_FFMPEG_BUILD_DATE=$(date +%Y%m%d 2>>${BASEDIR}/build.log)"
@@ -582,13 +621,23 @@ APP_LDFLAGS := -Wl,--hash-style=both
EOF EOF
} }
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
echo "ANDROID_NDK_ROOT not defined"
exit 1
fi
if [[ -z ${ANDROID_HOME} ]]; then
echo "ANDROID_HOME not defined"
exit 1
fi
# ENABLE COMMON FUNCTIONS # ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh . ${BASEDIR}/build/android-common.sh
DETECTED_NDK_VERSION=$(grep -Eo Revision.* ${ANDROID_NDK_ROOT}/source.properties | sed 's/Revision//g;s/=//g;s/ //g') DETECTED_NDK_VERSION=$(grep -Eo Revision.* ${ANDROID_NDK_ROOT}/source.properties | sed 's/Revision//g;s/=//g;s/ //g')
echo -e "\nINFO: Using Android NDK v${DETECTED_NDK_VERSION} provided at ${ANDROID_NDK_ROOT}\n" 1>>${BASEDIR}/build.log 2>&1 echo -e "\nINFO: Using Android NDK v${DETECTED_NDK_VERSION} provided at ${ANDROID_NDK_ROOT}\n" 1>>${BASEDIR}/build.log 2>&1
echo -e "INFO: Build options: $@\n" 1>>${BASEDIR}/build.log 2>&1 echo -e "INFO: Build options: $*\n" 1>>${BASEDIR}/build.log 2>&1
# CLEAR OLD NATIVE LIBS # CLEAR OLD NATIVE LIBS
rm -rf ${BASEDIR}/android/libs 1>>${BASEDIR}/build.log 2>&1 rm -rf ${BASEDIR}/android/libs 1>>${BASEDIR}/build.log 2>&1
@@ -597,6 +646,7 @@ rm -rf ${BASEDIR}/android/obj 1>>${BASEDIR}/build.log 2>&1
GPL_ENABLED="no" GPL_ENABLED="no"
DISPLAY_HELP="" DISPLAY_HELP=""
BUILD_LTS="" BUILD_LTS=""
BUILD_FULL=""
BUILD_TYPE_ID="" BUILD_TYPE_ID=""
BUILD_VERSION=$(git describe --tags 2>>${BASEDIR}/build.log) BUILD_VERSION=$(git describe --tags 2>>${BASEDIR}/build.log)
@@ -648,12 +698,16 @@ while [ ! $# -eq 0 ]; do
rebuild_library ${BUILD_LIBRARY} rebuild_library ${BUILD_LIBRARY}
;; ;;
--redownload-*)
DOWNLOAD_LIBRARY=$(echo $1 | sed -e 's/^--[A-Za-z]*-//g')
redownload_library ${DOWNLOAD_LIBRARY}
;;
--no-archive)
NO_ARCHIVE="1"
;;
--full) --full)
for library in {0..45}; do BUILD_FULL="1"
if [[ $library -ne 18 ]] && [[ $library -ne 19 ]] && [[ $library -ne 20 ]] && [[ $library -ne 21 ]] && [[ $library -ne 22 ]]; then
enable_library $(get_library_name $library)
fi
done
;; ;;
--enable-gpl) --enable-gpl)
GPL_ENABLED="yes" GPL_ENABLED="yes"
@@ -676,22 +730,14 @@ while [ ! $# -eq 0 ]; do
done done
# DETECT BUILD TYPE # DETECT BUILD TYPE
rm -f ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
rm -f ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1 rm -f ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1
if [[ ! -z ${BUILD_LTS} ]]; then if [[ ! -z ${BUILD_LTS} ]]; then
enable_lts_build enable_lts_build
BUILD_TYPE_ID+="LTS " BUILD_TYPE_ID+="LTS "
cp ${BASEDIR}/tools/ndk/Android.lts.mk ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
cp ${BASEDIR}/tools/release/android/build.lts.gradle ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1 cp ${BASEDIR}/tools/release/android/build.lts.gradle ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1
else else
cp ${BASEDIR}/tools/ndk/Android.mk ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
cp ${BASEDIR}/tools/release/android/build.gradle ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1 cp ${BASEDIR}/tools/release/android/build.gradle ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1
if [[ -z ${BUILD_FORCE} ]] && [[ ${ENABLED_ARCHITECTURES[${ARCH_ARM_V7A}]} -eq 1 ]]; then
echo -e "INFO: Disabled arm-v7a architecture which is not included in Main releases.\n" 1>>${BASEDIR}/build.log 2>&1
disable_arch "arm-v7a"
fi
fi fi
if [[ ! -z ${DISPLAY_HELP} ]]; then if [[ ! -z ${DISPLAY_HELP} ]]; then
@@ -699,14 +745,16 @@ if [[ ! -z ${DISPLAY_HELP} ]]; then
exit 0 exit 0
fi fi
if [[ -z ${ANDROID_NDK_ROOT} ]]; then if [[ -n ${BUILD_FULL} ]]; then
echo "ANDROID_NDK_ROOT not defined" for library in {0..46}; do
exit 1 if [ ${GPL_ENABLED} == "yes" ]; then
fi enable_library $(get_library_name $library)
else
if [[ -z ${ANDROID_HOME} ]]; then if [[ $library -ne 18 ]] && [[ $library -ne 19 ]] && [[ $library -ne 20 ]] && [[ $library -ne 21 ]] && [[ $library -ne 22 ]]; then
echo "ANDROID_HOME not defined" enable_library $(get_library_name $library)
exit 1 fi
fi
done
fi fi
if [[ -z ${BUILD_VERSION} ]]; then if [[ -z ${BUILD_VERSION} ]]; then
@@ -718,20 +766,13 @@ echo -e "\nBuilding mobile-ffmpeg ${BUILD_TYPE_ID}library for Android\n"
echo -e -n "INFO: Building mobile-ffmpeg ${BUILD_VERSION} ${BUILD_TYPE_ID}library for Android: " 1>>${BASEDIR}/build.log 2>&1 echo -e -n "INFO: Building mobile-ffmpeg ${BUILD_VERSION} ${BUILD_TYPE_ID}library for Android: " 1>>${BASEDIR}/build.log 2>&1
echo -e $(date) 1>>${BASEDIR}/build.log 2>&1 echo -e $(date) 1>>${BASEDIR}/build.log 2>&1
# PERFORM THIS CHECK ONLY ON LTS
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${ENABLED_ARCHITECTURES[0]} -eq 0 ]] && [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
ENABLED_ARCHITECTURES[ARCH_ARM_V7A]=1
echo -e "(*) arm-v7a architecture enabled since arm-v7a-neon will be built\n"
echo -e "(*) arm-v7a architecture enabled since arm-v7a-neon will be built\n" 1>>${BASEDIR}/build.log 2>&1
fi
print_enabled_architectures print_enabled_architectures
print_enabled_libraries print_enabled_libraries
print_reconfigure_requested_libraries print_reconfigure_requested_libraries
print_rebuild_requested_libraries print_rebuild_requested_libraries
print_redownload_requested_libraries
# CHECKING GPL LIBRARIES # CHECK GPL LIBRARIES
for gpl_library in {18,19,20,21,22}; do for gpl_library in {18,19,20,21,22}; do
if [[ ${ENABLED_LIBRARIES[$gpl_library]} -eq 1 ]]; then if [[ ${ENABLED_LIBRARIES[$gpl_library]} -eq 1 ]]; then
library_name=$(get_library_name ${gpl_library}) library_name=$(get_library_name ${gpl_library})
@@ -756,7 +797,7 @@ export ORIGINAL_API=${API}
for run_arch in {0..4}; do for run_arch in {0..4}; do
if [[ ${ENABLED_ARCHITECTURES[$run_arch]} -eq 1 ]]; then if [[ ${ENABLED_ARCHITECTURES[$run_arch]} -eq 1 ]]; then
if [[ (${run_arch} -eq ${ARCH_ARM64_V8A} || ${run_arch} -eq ${ARCH_X86_64}) && ${API} < 21 ]]; then if [[ (${run_arch} -eq ${ARCH_ARM64_V8A} || ${run_arch} -eq ${ARCH_X86_64}) && ${API} -lt 21 ]]; then
# 64 bit ABIs supported after API 21 # 64 bit ABIs supported after API 21
export API=21 export API=21
@@ -771,8 +812,8 @@ for run_arch in {0..4}; do
. ${BASEDIR}/build/main-android.sh "${ENABLED_LIBRARIES[@]}" || exit 1 . ${BASEDIR}/build/main-android.sh "${ENABLED_LIBRARIES[@]}" || exit 1
# CLEAR FLAGS # CLEAR FLAGS
for library in {1..46}; do for library in {0..47}; do
library_name=$(get_library_name $((library - 1))) library_name=$(get_library_name ${library})
unset $(echo "OK_${library_name}" | sed "s/\-/\_/g") unset $(echo "OK_${library_name}" | sed "s/\-/\_/g")
unset $(echo "DEPENDENCY_REBUILT_${library_name}" | sed "s/\-/\_/g") unset $(echo "DEPENDENCY_REBUILT_${library_name}" | sed "s/\-/\_/g")
done done
@@ -781,15 +822,21 @@ done
export API=${ORIGINAL_API} export API=${ORIGINAL_API}
rm -f ${BASEDIR}/android/build/.neon 1>>${BASEDIR}/build.log 2>&1 rm -f ${BASEDIR}/android/build/.armv7 1>>${BASEDIR}/build.log 2>&1
rm -f ${BASEDIR}/android/build/.armv7neon 1>>${BASEDIR}/build.log 2>&1
ANDROID_ARCHITECTURES="" ANDROID_ARCHITECTURES=""
if [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then if [[ ${ENABLED_ARCHITECTURES[0]} -eq 1 ]] || [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
ANDROID_ARCHITECTURES+="$(get_android_arch 0) " ANDROID_ARCHITECTURES+="$(get_android_arch 0) "
fi
if [[ ${ENABLED_ARCHITECTURES[0]} -eq 1 ]]; then
mkdir -p ${BASEDIR}/android/build 1>>${BASEDIR}/build.log 2>&1 mkdir -p ${BASEDIR}/android/build 1>>${BASEDIR}/build.log 2>&1
cat >"${BASEDIR}/android/build/.neon" <<EOF cat >"${BASEDIR}/android/build/.armv7" <<EOF
EOF
fi
if [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
mkdir -p ${BASEDIR}/android/build 1>>${BASEDIR}/build.log 2>&1
cat >"${BASEDIR}/android/build/.armv7neon" <<EOF
EOF EOF
elif [[ ${ENABLED_ARCHITECTURES[0]} -eq 1 ]]; then
ANDROID_ARCHITECTURES+="$(get_android_arch 0) "
fi fi
if [[ ${ENABLED_ARCHITECTURES[2]} -eq 1 ]]; then if [[ ${ENABLED_ARCHITECTURES[2]} -eq 1 ]]; then
ANDROID_ARCHITECTURES+="$(get_android_arch 2) " ANDROID_ARCHITECTURES+="$(get_android_arch 2) "
@@ -801,7 +848,7 @@ if [[ ${ENABLED_ARCHITECTURES[4]} -eq 1 ]]; then
ANDROID_ARCHITECTURES+="$(get_android_arch 4) " ANDROID_ARCHITECTURES+="$(get_android_arch 4) "
fi fi
if [[ ! -z ${ANDROID_ARCHITECTURES} ]]; then if [[ ! -z ${ANDROID_ARCHITECTURES} ]] && [ -z ${NO_ARCHIVE} ]; then
echo -n -e "\nmobile-ffmpeg: " echo -n -e "\nmobile-ffmpeg: "
+9 -6
View File
@@ -1,10 +1,13 @@
*.iml *.iml
.gradle .gradle/
local.properties local.properties
.DS_Store .DS_Store
build /build/
/app/build/
/test-app/build/
/app/.cxx/
captures captures
.externalNativeBuild .externalNativeBuild/
.idea .idea/
obj /obj/
libs /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 # This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project. # doxygen (www.doxygen.org) for a project.
@@ -17,10 +17,10 @@
# Project related configuration options # Project related configuration options
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the config file # This tag specifies the encoding used for all characters in the configuration
# that follow. The default is UTF-8 which is also the encoding used for all text # file that follow. The default is UTF-8 which is also the encoding used for all
# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # text before the first occurrence of this tag. Doxygen uses libiconv (or the
# built into libc) for the transcoding. See # iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8. # 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 # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 4.3.2 PROJECT_NUMBER = 4.4
# Using the PROJECT_BRIEF tag one can provide an optional one line description # 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 # 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 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 # 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 # descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this. # documentation (similar to Javadoc). Set to NO to disable this.
@@ -189,6 +197,16 @@ SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = 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 # 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 # 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 # 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 # "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 # 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. # 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 = 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 # 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 # 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 # 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 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 # 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 # 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 # 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 # 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, # language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# Fortran. In the later case the parser tries to guess whether the code is fixed # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# or free formatted code, this is the default for Fortran type files), VHDL. For # tries to guess whether the code is fixed or free formatted code, this is the
# instance to make doxygen treat .inc files as Fortran files (default is PHP), # default for Fortran type files). For instance to make doxygen treat .inc files
# and .f files as C (default is Fortran), use: inc=Fortran f=C. # 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. # 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 # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable # 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 # 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 # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues. # 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 # to that level are automatically included in the table of contents, even if
# they do not have an id attribute. # they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings. # 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. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
TOC_INCLUDE_HEADINGS = 0 TOC_INCLUDE_HEADINGS = 0
@@ -444,6 +469,12 @@ EXTRACT_ALL = YES
EXTRACT_PRIVATE = 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 # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation. # scope will be included in the documentation.
# The default value is: NO. # The default value is: NO.
@@ -498,8 +529,8 @@ HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # 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 # declarations. If set to NO, these declarations will be included in the
# included in the documentation. # documentation.
# The default value is: NO. # The default value is: NO.
HIDE_FRIEND_COMPOUNDS = 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 # 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 # 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 # 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. # The default value is: system dependent.
CASE_SENSE_NAMES = NO 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 # 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 # are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong or incomplete # 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. # The default value is: NO.
WARN_NO_PARAMDOC = 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, # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. # *.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 \ FILE_PATTERNS = *.c \
*.cc \ *.cc \
@@ -889,7 +923,7 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to # 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 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 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the # (namespaces, classes, functions, etc.) that should be excluded from the
@@ -1011,7 +1045,7 @@ INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES STRIP_CODE_COMMENTS = YES
# If the REFERENCED_BY_RELATION tag is set to YES then for each documented # 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. # The default value is: NO.
REFERENCED_BY_RELATION = NO REFERENCED_BY_RELATION = NO
@@ -1048,7 +1082,7 @@ SOURCE_TOOLTIPS = YES
# #
# To use it do the following: # To use it do the following:
# - Install the latest version of global # - 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 # - Make sure the INPUT points to the root of the source tree
# - Run doxygen as normal # - 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 # 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 # 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 # 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. # like the Qt help browser.
# The default value is: YES. # The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to 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 # 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 # generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see: https://developer.apple.com/tools/xcode/), introduced with # environment (see: https://developer.apple.com/xcode/), introduced with OSX
# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # 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 # Makefile in the HTML output directory. Running make will produce the docset in
# that directory and running make install will install 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 # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# for more information. # genXcode/_index.html for more information.
# The default value is: NO. # The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES. # 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 # 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 # 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 # 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. # Windows.
# #
# The HTML Help Workshop contains a compiler that can convert all HTML output # 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 # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace # 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. # The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES. # 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 # 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 # 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. # The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES. # 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 # 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 # 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. # This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_NAME = QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # 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 # 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. # This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_ATTRS = QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
# project's filter section matches. Qt Help Project / Filter Attributes (see: # 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. # This tag requires that the tag GENERATE_QHP is set to YES.
QHP_SECT_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS =
@@ -1493,6 +1530,17 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO 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 # 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 # 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 # doxygen run you need to manually remove any form_*.png images from the HTML
@@ -1513,8 +1561,14 @@ FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES 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 # 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 # 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 # 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 # 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 # 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. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment. # 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. # This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/
@@ -1584,7 +1638,7 @@ MATHJAX_CODEFILE =
SEARCHENGINE = YES SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be # 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 # 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 # 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 # 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 # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked. # invoked.
# #
# Note that when enabling USE_PDFLATEX this option is only used for generating # Note that when not enabling USE_PDFLATEX the default is latex when enabling
# bitmaps for formulas in the HTML output, but not in the Makefile that is # USE_PDFLATEX the default is pdflatex and when in the later case latex is
# written to the output directory. # chosen this is overwritten by pdflatex. For specific output languages the
# The default file is: latex. # 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. # This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME = latex LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX. # 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. # The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES. # This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex 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 # 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 # documents. This may be useful for small projects and may help to save some
# trees in general. # trees in general.
@@ -1817,6 +1885,14 @@ LATEX_BIB_STYLE = plain
LATEX_TIMESTAMP = NO 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 # Configuration options related to the RTF output
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@@ -1856,9 +1932,9 @@ COMPACT_RTF = NO
RTF_HYPERLINKS = NO RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's config # Load stylesheet definitions from file. Syntax is similar to doxygen's
# file, i.e. a series of assignments. You only have to provide replacements, # configuration file, i.e. a series of assignments. You only have to provide
# missing definitions are set to their default value. # replacements, missing definitions are set to their default value.
# #
# See also section "Doxygen usage" for information on how to generate the # See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses. # default style sheet that doxygen normally uses.
@@ -1867,8 +1943,8 @@ RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE = RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an RTF document. Syntax is # 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 # similar to doxygen's configuration file. A template extensions file can be
# using doxygen -e rtf extensionFile. # generated using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES. # This tag requires that the tag GENERATE_RTF is set to YES.
RTF_EXTENSIONS_FILE = RTF_EXTENSIONS_FILE =
@@ -1954,6 +2030,13 @@ XML_OUTPUT = xml
XML_PROGRAMLISTING = YES 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 # Configuration options related to the DOCBOOK output
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@@ -2155,12 +2238,6 @@ EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = 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 # Configuration options related to the dot tool
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@@ -2174,15 +2251,6 @@ PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = YES 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 # 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 # 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. # DIA_PATH tag allows you to specify the directory where the dia binary resides.
+10 -12
View File
@@ -1,14 +1,14 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
compileSdkVersion 29 compileSdkVersion 30
ndkVersion "21.0.6113669" ndkVersion "21.3.6528147"
defaultConfig { defaultConfig {
minSdkVersion 24 minSdkVersion 24
targetSdkVersion 29 targetSdkVersion 30
versionCode 240432 versionCode 240440
versionName "4.3.2" versionName "4.4"
project.archivesBaseName = "mobile-ffmpeg" project.archivesBaseName = "mobile-ffmpeg"
consumerProguardFiles 'proguard-rules.pro' consumerProguardFiles 'proguard-rules.pro'
} }
@@ -31,13 +31,11 @@ android {
} }
} }
task javadoc(type: Javadoc) {
title = 'MobileFFmpeg'
destinationDir = file("${projectDir}/../../docs/android/javadoc")
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
source = android.sourceSets.main.java.srcDirs
}
dependencies { dependencies {
testImplementation "androidx.test.ext:junit:1.1.1" testImplementation "androidx.test.ext:junit:1.1.1"
testImplementation "org.json:json:20190722"
}
if (System.properties.containsKey('releaseMobileFFmpeg')) {
apply from: "${rootProject.projectDir}/../tools/release/android/release.template.gradle"
} }
+3 -2
View File
@@ -7,8 +7,9 @@
-keep class com.arthenica.mobileffmpeg.Config { -keep class com.arthenica.mobileffmpeg.Config {
native <methods>; native <methods>;
void log(int, byte[]); void log(long, int, byte[]);
void statistics(int, float, float, long , int, double, double); void statistics(long, int, float, float, long , int, double, double);
void closeParcelFileDescriptor(int);
} }
-keep class com.arthenica.mobileffmpeg.AbiDetect { -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; FILE *report_file;
int report_file_level = AV_LOG_DEBUG; int report_file_level = AV_LOG_DEBUG;
__thread int hide_banner = 0; __thread int hide_banner = 0;
__thread int longjmp_value = 0; __thread volatile int longjmp_value = 0;
enum show_muxdemuxers { enum show_muxdemuxers {
SHOW_DEFAULT, SHOW_DEFAULT,
@@ -51,6 +51,8 @@
#include "libavformat/avformat.h" #include "libavformat/avformat.h"
#include "libswscale/swscale.h" #include "libswscale/swscale.h"
#include "saf_wrapper.h"
#ifdef _WIN32 #ifdef _WIN32
#undef main /* We don't want SDL to override our main() */ #undef main /* We don't want SDL to override our main() */
#endif #endif
@@ -334,6 +336,7 @@ typedef struct OptionParseContext {
* Parse an options group and write results into optctx. * Parse an options group and write results into optctx.
* *
* @param optctx an app-specific options context. NULL for global options group * @param optctx an app-specific options context. NULL for global options group
* @param g option group
*/ */
int parse_optgroup(void *optctx, OptionGroup *g); 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 * CHANGES 01.2020
* - ffprobe support changes * - ffprobe support changes
* *
* CHANGES 12.2019 * CHANGES 12.2019
* - Concurrent execution support * - concurrent execution support
* *
* CHANGES 08.2018 * CHANGES 08.2018
* -------------------------------------------------------- * --------------------------------------------------------
@@ -239,6 +244,16 @@ __thread int restore_tty;
static void free_input_threads(void); static void free_input_threads(void);
#endif #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: /* sub2video hack:
Convert subtitles to video with alpha to insert them in filter graphs. Convert subtitles to video with alpha to insert them in filter graphs.
This is a temporary solution until libavfilter gets real subtitles support. This is a temporary solution until libavfilter gets real subtitles support.
@@ -402,12 +417,12 @@ void term_exit(void)
term_exit_sigsafe(); term_exit_sigsafe();
} }
volatile int received_sigterm = 0; static volatile int received_sigterm = 0;
volatile int received_nb_signals = 0; static volatile int received_nb_signals = 0;
__thread atomic_int transcode_init_done = ATOMIC_VAR_INIT(0); __thread atomic_int transcode_init_done = ATOMIC_VAR_INIT(0);
__thread volatile int ffmpeg_exited = 0; __thread volatile int ffmpeg_exited = 0;
__thread int main_ffmpeg_return_code = 0; __thread volatile int main_ffmpeg_return_code = 0;
extern __thread int longjmp_value; extern __thread volatile int longjmp_value;
static void static void
sigterm_handler(int sig) sigterm_handler(int sig)
@@ -476,17 +491,27 @@ void term_init(void)
tcsetattr (0, TCSANOW, &tty); tcsetattr (0, TCSANOW, &tty);
} }
signal(SIGQUIT, sigterm_handler); /* Quit (POSIX). */ if (handleSIGQUIT == 1) {
signal(SIGQUIT, sigterm_handler); /* Quit (POSIX). */
}
} }
#endif #endif
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */ if (handleSIGINT == 1) {
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */ signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
}
if (handleSIGTERM == 1) {
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
}
#ifdef SIGXCPU #ifdef SIGXCPU
signal(SIGXCPU, sigterm_handler); if (handleSIGXCPU == 1) {
signal(SIGXCPU, sigterm_handler);
}
#endif #endif
#ifdef SIGPIPE #ifdef SIGPIPE
signal(SIGPIPE, SIG_IGN); /* Broken pipe (POSIX). */ if (handleSIGPIPE == 1) {
signal(SIGPIPE, SIG_IGN); /* Broken pipe (POSIX). */
}
#endif #endif
#if HAVE_SETCONSOLECTRLHANDLER #if HAVE_SETCONSOLECTRLHANDLER
SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE); SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE);
@@ -701,6 +726,8 @@ static void ffmpeg_cleanup(int ret)
if (received_sigterm) { if (received_sigterm) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n", av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n",
(int) received_sigterm); (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)) { } else if (ret && atomic_load(&transcode_init_done)) {
av_log(NULL, AV_LOG_INFO, "Conversion failed!\n"); 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) { if (ifilter->filter) {
/* THIS VALIDATION IS REQUIRED TO COMPLETE CANCELLATION */ /* 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); ret = av_buffersrc_close(ifilter->filter, pts, AV_BUFFERSRC_FLAG_PUSH);
} }
if (ret < 0) if (ret < 0)
@@ -4829,7 +4856,7 @@ static int transcode(void)
goto fail; goto fail;
#endif #endif
while (!received_sigterm) { while (!received_sigterm && !cancelRequested(executionId)) {
int64_t cur_time= av_gettime_relative(); int64_t cur_time= av_gettime_relative();
/* if 'q' pressed, exits */ /* if 'q' pressed, exits */
@@ -5029,9 +5056,13 @@ void set_report_callback(void (*callback)(int, float, float, int64_t, int, doubl
report_callback = callback; 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; __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]) if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
exit_program(69); 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 { } else {
main_ffmpeg_return_code = longjmp_value; main_ffmpeg_return_code = (received_nb_signals || cancelRequested(executionId)) ? 255 : longjmp_value;
} }
return main_ffmpeg_return_code; 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 * CHANGES 01.2020
* - ffprobe support changes * - 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 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(void *optctx, const char *opt, const char *arg);
int opt_map_channel(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 struct AVHashContext *hash;
__thread int main_ffprobe_return_code = 0; __thread volatile int main_ffprobe_return_code = 0;
extern __thread int longjmp_value; extern __thread volatile int longjmp_value;
static const struct { static const struct {
double bin_val; double bin_val;
+205 -112
View File
@@ -17,34 +17,6 @@
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>. * 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 <pthread.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -59,9 +31,10 @@
/** Callback data structure */ /** Callback data structure */
struct CallbackData { struct CallbackData {
int type; // 1 (log callback) or 2 (statistics callback) int type; // 1 (log callback) or 2 (statistics callback)
long executionId; // execution id
int logLevel; // log level int logLevel; // log level
char *logData; // log data AVBPrint logData; // log data
int statisticsFrameNumber; // statistics frame number int statisticsFrameNumber; // statistics frame number
float statisticsFps; // statistics fps float statisticsFps; // statistics fps
@@ -74,14 +47,19 @@ struct CallbackData {
struct CallbackData *next; 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 */ /** Redirection control variables */
pthread_mutex_t lockMutex; static pthread_mutex_t lockMutex;
pthread_mutex_t monitorMutex; static pthread_mutex_t monitorMutex;
pthread_cond_t monitorCondition; static pthread_cond_t monitorCondition;
/** Last command output variables */ /** Last command output variables */
pthread_mutex_t logMutex; static pthread_mutex_t logMutex;
static char *lastCommandOutput; static AVBPrint lastCommandOutput;
pthread_t callbackThread; pthread_t callbackThread;
int redirectionEnabled; int redirectionEnabled;
@@ -101,6 +79,9 @@ static jmethodID logMethod;
/** Global reference of statistics redirection method in Java */ /** Global reference of statistics redirection method in Java */
static jmethodID statisticsMethod; static jmethodID statisticsMethod;
/** Global reference of closeParcelFileDescriptor method in Java */
static jmethodID closeParcelFileDescriptorMethod;
/** Global reference of String class in Java */ /** Global reference of String class in Java */
static jclass stringClass; static jclass stringClass;
@@ -113,6 +94,19 @@ const char *configClassName = "com/arthenica/mobileffmpeg/Config";
/** Full name of String class */ /** Full name of String class */
const char *stringClassName = "java/lang/String"; 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. */ /** Prototypes of native functions defined by Config class. */
JNINativeMethod configMethods[] = { JNINativeMethod configMethods[] = {
{"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection}, {"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection},
@@ -121,20 +115,19 @@ JNINativeMethod configMethods[] = {
{"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel}, {"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel},
{"getNativeFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion}, {"getNativeFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion},
{"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeVersion}, {"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeVersion},
{"nativeFFmpegExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute}, {"nativeFFmpegExecute", "(J[Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute},
{"nativeFFmpegCancel", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel}, {"nativeFFmpegCancel", "(J)V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel},
{"nativeFFprobeExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFprobeExecute}, {"nativeFFprobeExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFprobeExecute},
{"registerNewNativeFFmpegPipe", "(Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_registerNewNativeFFmpegPipe}, {"registerNewNativeFFmpegPipe", "(Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_registerNewNativeFFmpegPipe},
{"getNativeBuildDate", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeBuildDate}, {"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 */ /** Forward declaration for function defined in fftools_ffmpeg.c */
int ffmpeg_execute(int argc, char **argv); 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) { static const char *avutil_log_get_level_str(int level) {
switch (level) { switch (level) {
case AV_LOG_STDERR: case AV_LOG_STDERR:
@@ -233,7 +226,16 @@ void logInit() {
pthread_mutex_init(&logMutex, &attributes); pthread_mutex_init(&logMutex, &attributes);
pthread_mutexattr_destroy(&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() { void mutexUnInit() {
@@ -249,6 +251,10 @@ void logUnInit() {
pthread_mutex_destroy(&logMutex); pthread_mutex_destroy(&logMutex);
} }
void executionMapLockUnInit() {
pthread_mutex_destroy(&executionMapMutex);
}
void mutexLock() { void mutexLock() {
pthread_mutex_lock(&lockMutex); pthread_mutex_lock(&lockMutex);
} }
@@ -257,6 +263,10 @@ void lastCommandOutputLock() {
pthread_mutex_lock(&logMutex); pthread_mutex_lock(&logMutex);
} }
void executionMapLock() {
pthread_mutex_lock(&executionMapMutex);
}
void mutexUnlock() { void mutexUnlock() {
pthread_mutex_unlock(&lockMutex); pthread_mutex_unlock(&lockMutex);
} }
@@ -265,50 +275,24 @@ void lastCommandOutputUnlock() {
pthread_mutex_unlock(&logMutex); pthread_mutex_unlock(&logMutex);
} }
void executionMapUnlock() {
pthread_mutex_unlock(&executionMapMutex);
}
void clearLastCommandOutput() { void clearLastCommandOutput() {
lastCommandOutputLock(); lastCommandOutputLock();
av_bprint_clear(&lastCommandOutput);
if (lastCommandOutput != NULL) {
av_free(lastCommandOutput);
lastCommandOutput = NULL;
}
lastCommandOutputUnlock(); lastCommandOutputUnlock();
} }
void appendLastCommandOutput(const char *logMessage) { void appendLastCommandOutput(AVBPrint *logMessage) {
size_t length = 0; if (logMessage->len <= 0) {
char *tempLastCommandOutput = NULL;
size_t logMessageLength = strlen(logMessage);
if (logMessageLength <= 0) {
return; return;
} }
lastCommandOutputLock(); lastCommandOutputLock();
av_bprintf(&lastCommandOutput, "%s", logMessage->str);
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;
}
lastCommandOutputUnlock(); lastCommandOutputUnlock();
if (tempLastCommandOutput != NULL) {
av_free(tempLastCommandOutput);
}
} }
void monitorWait(int milliSeconds) { void monitorWait(int milliSeconds) {
@@ -325,6 +309,8 @@ void monitorWait(int milliSeconds) {
ts.tv_nsec = tp.tv_usec * 1000; ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_sec += milliSeconds / 1000; ts.tv_sec += milliSeconds / 1000;
ts.tv_nsec += (milliSeconds % 1000)*1000000; ts.tv_nsec += (milliSeconds % 1000)*1000000;
ts.tv_sec += ts.tv_nsec / 1000000000L;
ts.tv_nsec = ts.tv_nsec % 1000000000L;
pthread_mutex_lock(&monitorMutex); pthread_mutex_lock(&monitorMutex);
pthread_cond_timedwait(&monitorCondition, &monitorMutex, &ts); pthread_cond_timedwait(&monitorCondition, &monitorMutex, &ts);
@@ -343,15 +329,15 @@ void monitorNotify() {
* @param level log level * @param level log level
* @param data log data * @param data log data
*/ */
void logCallbackDataAdd(int level, const char *data) { void logCallbackDataAdd(int level, AVBPrint *data) {
// CREATE DATA STRUCT FIRST // CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData)); struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 1; newData->type = 1;
newData->executionId = executionId;
newData->logLevel = level; newData->logLevel = level;
size_t dataSize = strlen(data) + 1; av_bprint_init(&newData->logData, 0, AV_BPRINT_SIZE_UNLIMITED);
newData->logData = (char*)av_malloc(dataSize); av_bprintf(&newData->logData, "%s", data->str);
memcpy(newData->logData, data, dataSize);
newData->next = NULL; newData->next = NULL;
mutexLock(); mutexLock();
@@ -385,6 +371,7 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
// CREATE DATA STRUCT FIRST // CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData)); struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 2; newData->type = 2;
newData->executionId = executionId;
newData->statisticsFrameNumber = frameNumber; newData->statisticsFrameNumber = frameNumber;
newData->statisticsFps = fps; newData->statisticsFps = fps;
newData->statisticsQuality = quality; newData->statisticsQuality = quality;
@@ -418,6 +405,20 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
monitorNotify(); 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. * Removes head of callback data list.
*/ */
@@ -450,6 +451,41 @@ struct CallbackData *callbackDataRemove() {
return currentData; 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. * Callback function for FFmpeg logs.
* *
@@ -459,7 +495,7 @@ struct CallbackData *callbackDataRemove() {
* @param vargs arguments * @param vargs arguments
*/ */
void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs) { 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]; AVBPrint part[4];
int print_prefix = 1; int print_prefix = 1;
@@ -473,21 +509,27 @@ void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format
return; return;
} }
av_bprint_init(&fullLine, 0, AV_BPRINT_SIZE_UNLIMITED);
avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix); avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix);
avutil_log_sanitize(part[0].str); avutil_log_sanitize(part[0].str);
avutil_log_sanitize(part[1].str); avutil_log_sanitize(part[1].str);
avutil_log_sanitize(part[2].str); avutil_log_sanitize(part[2].str);
avutil_log_sanitize(part[3].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); if (fullLine.len > 0) {
appendLastCommandOutput(line); logCallbackDataAdd(level, &fullLine);
appendLastCommandOutput(&fullLine);
}
av_bprint_finalize(part, NULL); av_bprint_finalize(part, NULL);
av_bprint_finalize(part+1, NULL); av_bprint_finalize(part+1, NULL);
av_bprint_finalize(part+2, NULL); av_bprint_finalize(part+2, NULL);
av_bprint_finalize(part+3, NULL); av_bprint_finalize(part+3, NULL);
av_bprint_finalize(&fullLine, NULL);
} }
/** /**
@@ -533,25 +575,25 @@ void *callbackThreadFunction() {
// LOG CALLBACK // LOG CALLBACK
size_t size = strlen(callbackData->logData); int size = callbackData->logData.len;
jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size); jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, (jbyte *)callbackData->logData); (*env)->SetByteArrayRegion(env, byteArray, 0, size, callbackData->logData.str);
(*env)->CallStaticVoidMethod(env, configClass, logMethod, callbackData->logLevel, byteArray); (*env)->CallStaticVoidMethod(env, configClass, logMethod, (jlong) callbackData->executionId, callbackData->logLevel, byteArray);
(*env)->DeleteLocalRef(env, byteArray); (*env)->DeleteLocalRef(env, byteArray);
// CLEAN LOG DATA // CLEAN LOG DATA
av_free(callbackData->logData); av_bprint_finalize(&callbackData->logData, NULL);
} else { } else {
// STATISTICS CALLBACK // STATISTICS CALLBACK
(*env)->CallStaticVoidMethod(env, configClass, statisticsMethod, (*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
callbackData->statisticsFrameNumber, callbackData->statisticsFps, (jlong) callbackData->executionId, callbackData->statisticsFrameNumber,
callbackData->statisticsQuality, callbackData->statisticsSize, callbackData->statisticsFps, callbackData->statisticsQuality,
callbackData->statisticsTime, callbackData->statisticsBitrate, callbackData->statisticsSize, callbackData->statisticsTime,
callbackData->statisticsSpeed); callbackData->statisticsBitrate, callbackData->statisticsSpeed);
} }
@@ -604,24 +646,27 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
(*env)->GetJavaVM(env, &globalVm); (*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) { if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "log"); LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "log");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE; return JNI_FALSE;
} }
statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(IFFJIDD)V"); statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(JIFFJIDD)V");
if (logMethod == NULL) { if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "statistics"); LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "statistics");
(*globalVm)->DetachCurrentThread(globalVm); return JNI_FALSE;
}
closeParcelFileDescriptorMethod = (*env)->GetStaticMethodID(env, localConfigClass, "closeParcelFileDescriptor", "(I)V");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "closeParcelFileDescriptor");
return JNI_FALSE; return JNI_FALSE;
} }
stringConstructor = (*env)->GetMethodID(env, localStringClass, "<init>", "([BLjava/lang/String;)V"); stringConstructor = (*env)->GetMethodID(env, localStringClass, "<init>", "([BLjava/lang/String;)V");
if (stringConstructor == NULL) { if (stringConstructor == NULL) {
LOGE("OnLoad thread failed to GetMethodID for %s.\n", "<init>"); LOGE("OnLoad thread failed to GetMethodID for %s.\n", "<init>");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE; return JNI_FALSE;
} }
@@ -635,9 +680,14 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
callbackDataHead = NULL; callbackDataHead = NULL;
callbackDataTail = NULL; callbackDataTail = NULL;
for(int i = 0; i<EXECUTION_MAP_SIZE; i++) {
executionMap[i] = 0;
}
mutexInit(); mutexInit();
monitorInit(); monitorInit();
logInit(); logInit();
executionMapLockInit();
return JNI_VERSION_1_6; return JNI_VERSION_1_6;
} }
@@ -649,8 +699,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
* @param object reference to the class on which this method is invoked * @param object reference to the class on which this method is invoked
* @param level log level * @param level log level
*/ */
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint 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 +710,7 @@ JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(
* @param object reference to the class on which this method is invoked * @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) { JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object) {
return av_log_get_level(); return configuredLogLevel;
} }
/** /**
@@ -741,14 +791,18 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersio
* *
* @param env pointer to native method interface * @param env pointer to native method interface
* @param object reference to the class on which this method is invoked * @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 * @param stringArray reference to the object holding FFmpeg command arguments
* @return zero on successful execution, non-zero on error * @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; jstring *tempArray = NULL;
int argumentCount = 1; int argumentCount = 1;
char **argv = NULL; char **argv = NULL;
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW EXECUTION
av_log_set_level(configuredLogLevel);
if (stringArray != NULL) { if (stringArray != NULL) {
int programArgumentCount = (*env)->GetArrayLength(env, stringArray); int programArgumentCount = (*env)->GetArrayLength(env, stringArray);
argumentCount = programArgumentCount + 1; argumentCount = programArgumentCount + 1;
@@ -777,9 +831,16 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecut
// LAST COMMAND OUTPUT SHOULD BE CLEARED BEFORE STARTING A NEW EXECUTION // LAST COMMAND OUTPUT SHOULD BE CLEARED BEFORE STARTING A NEW EXECUTION
clearLastCommandOutput(); clearLastCommandOutput();
// REGISTER THE ID BEFORE STARTING EXECUTION
executionId = (long) id;
addExecution((long) id);
// RUN // RUN
int retCode = ffmpeg_execute(argumentCount, argv); int retCode = ffmpeg_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP
removeExecution((long) id);
// CLEANUP // CLEANUP
if (tempArray != NULL) { if (tempArray != NULL) {
for (int i = 0; i < (argumentCount - 1); i++) { for (int i = 0; i < (argumentCount - 1); i++) {
@@ -799,9 +860,10 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecut
* *
* @param env pointer to native method interface * @param env pointer to native method interface
* @param object reference to the class on which this method is invoked * @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) { JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *env, jclass object, jlong id) {
cancel_operation(); cancel_operation(id);
} }
/** /**
@@ -844,7 +906,11 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
const char *variableNameString = (*env)->GetStringUTFChars(env, variableName, 0); const char *variableNameString = (*env)->GetStringUTFChars(env, variableName, 0);
const char *variableValueString = (*env)->GetStringUTFChars(env, variableValue, 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 +921,43 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
* @return output of the last executed command * @return output of the last executed command
*/ */
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput(JNIEnv *env, jclass object) { JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput(JNIEnv *env, jclass object) {
if (lastCommandOutput != NULL) { int size = lastCommandOutput.len;
int size = strlen(lastCommandOutput); if (size > 0) {
jbyteArray byteArray = (*env)->NewByteArray(env, size);
if (size > 0) { (*env)->SetByteArrayRegion(env, byteArray, 0, size, lastCommandOutput.str);
jbyteArray byteArray = (*env)->NewByteArray(env, size); jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
(*env)->SetByteArrayRegion(env, byteArray, 0, size, lastCommandOutput); return (jstring) (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName);
jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
return (jstring) (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName);
}
} }
return (*env)->NewStringUTF(env, ""); 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;
}
}
/**
* used by saf_wrapper; is expected to be called from a Java thread, therefore we don't need attach/detach
*/
void closeParcelFileDescriptor(int fd) {
JNIEnv *env = NULL;
(*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
(*env)->CallStaticVoidMethod(env, configClass, closeParcelFileDescriptorMethod, fd);
}
+12 -5
View File
@@ -27,7 +27,7 @@
#include "libavutil/ffversion.h" #include "libavutil/ffversion.h"
/** Library version string */ /** Library version string */
#define MOBILE_FFMPEG_VERSION "4.3.2" #define MOBILE_FFMPEG_VERSION "4.4"
/** Defines tag used for Android logging. */ /** Defines tag used for Android logging. */
#define LIB_NAME "mobile-ffmpeg" #define LIB_NAME "mobile-ffmpeg"
@@ -92,16 +92,16 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersio
/* /*
* Class: com_arthenica_mobileffmpeg_Config * Class: com_arthenica_mobileffmpeg_Config
* Method: nativeFFmpegExecute * 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 * Class: com_arthenica_mobileffmpeg_Config
* Method: nativeFFmpegCancel * 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 * 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); 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 */ #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 env pointer to native method interface
* @param object reference to the class on which this method is invoked * @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) { JNIEXPORT jboolean JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_isNativeLTSBuild(JNIEnv *env, jclass object) {
#if defined(MOBILE_FFMPEG_LTS) #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 */ /** Forward declaration for function defined in mobileffmpeg.c */
void clearLastCommandOutput(); void clearLastCommandOutput();
extern int configuredLogLevel;
/** /**
* Synchronously executes FFprobe natively with arguments provided. * Synchronously executes FFprobe natively with arguments provided.
* *
@@ -45,6 +47,9 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFprobeExecu
int argumentCount = 1; int argumentCount = 1;
char **argv = NULL; char **argv = NULL;
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW EXECUTION
av_log_set_level(configuredLogLevel);
if (stringArray != NULL) { if (stringArray != NULL) {
int programArgumentCount = (*env)->GetArrayLength(env, stringArray); int programArgumentCount = (*env)->GetArrayLength(env, stringArray);
argumentCount = programArgumentCount + 1; argumentCount = programArgumentCount + 1;
+134
View File
@@ -0,0 +1,134 @@
/*
* 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/>.
*/
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include "config.h"
#include "libavformat/avformat.h"
#include "libavutil/avstring.h"
#include "saf_wrapper.h"
/** JNI wrapper in mobileffmpeg.c */
void closeParcelFileDescriptor(int fd);
// in these wrappers, we call the original functions, so we remove the shadow defines
#undef avio_closep
#undef avformat_close_input
#undef avio_open
#undef avio_open2
#undef avformat_open_input
static int fd_read_packet(void* opaque, uint8_t* buf, int buf_size) {
int fd = (int)opaque;
return read(fd, buf, buf_size);
}
static int fd_write_packet(void* opaque, uint8_t* buf, int buf_size) {
int fd = (int)opaque;
return write(fd, buf, buf_size);
}
static int64_t fd_seek(void *opaque, int64_t offset, int whence) {
int fd = (int)opaque;
if (fd < 0) {
return AVERROR(EINVAL);
}
int64_t ret;
if (whence == AVSEEK_SIZE) {
struct stat st;
ret = fstat(fd, &st);
return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size);
}
ret = lseek(fd, offset, whence);
return ret < 0 ? AVERROR(errno) : ret;
}
/*
* returns NULL if the filename is not of expected format (e.g. 'saf:72/video.md4')
*/
static AVIOContext *create_fd_avio_context(const char *filename, int flags) {
union {int fd; void* opaque;} fdunion;
fdunion.fd = -1;
const char *fd_ptr = NULL;
if (av_strstart(filename, "saf:", &fd_ptr)) {
char *final;
fdunion.fd = strtol(fd_ptr, &final, 10);
if (fd_ptr == final) { /* No digits found */
fdunion.fd = -1;
}
}
if (fdunion.fd >= 0) {
int write_flag = flags & AVIO_FLAG_WRITE ? 1 : 0;
return avio_alloc_context(av_malloc(4096), 4096, write_flag, fdunion.opaque, fd_read_packet, write_flag ? fd_write_packet : NULL, fd_seek);
}
return NULL;
}
static void close_fd_avio_context(AVIOContext *ctx) {
if (fd_seek(ctx->opaque, 0, AVSEEK_SIZE) >= 0) {
int fd = (int)ctx->opaque;
closeParcelFileDescriptor(fd);
}
ctx->opaque = NULL;
}
int android_avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options) {
if (!(*ps) && !(*ps = avformat_alloc_context()))
return AVERROR(ENOMEM);
(*ps)->pb = create_fd_avio_context(filename, AVIO_FLAG_READ);
return avformat_open_input(ps, filename, fmt, options);
}
int android_avio_open2(AVIOContext **s, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options) {
AVIOContext *fd_context = create_fd_avio_context(filename, flags);
if (fd_context) {
*s = fd_context;
return 0;
}
return avio_open2(s, filename, flags, int_cb, options);
}
int android_avio_open(AVIOContext **s, const char *url, int flags) {
return android_avio_open2(s, url, flags, NULL, NULL);
}
int android_avio_closep(AVIOContext **s) {
close_fd_avio_context(*s);
return avio_closep(s);
}
void android_avformat_close_input(AVFormatContext **ps) {
if (*ps && (*ps)->pb) {
close_fd_avio_context((*ps)->pb);
}
avformat_close_input(ps);
}
+46
View File
@@ -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/>.
*/
#ifndef MOBILE_FFMPEG_SAF_WRAPPER_H
#define MOBILE_FFMPEG_SAF_WRAPPER_H
/*
* These wrappers are intended to be used instead of the ffmpeg apis.
* You don't even need to change the source to call them.
* Instead, we redefine the public api names so that the wrapper be used.
*/
int android_avio_closep(AVIOContext **s);
#define avio_closep android_avio_closep
void android_avformat_close_input(AVFormatContext **s);
#define avformat_close_input android_avformat_close_input
int android_avio_open(AVIOContext **s, const char *url, int flags);
#define avio_open android_avio_open
int android_avio_open2(AVIOContext **s, const char *url, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options);
#define avio_open2 android_avio_open2
int android_avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options);
#define avformat_open_input android_avformat_open_input
#endif //MOBILE_FFMPEG_SAF_WRAPPER_H
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018 Taner Sener * Copyright (c) 2018-2020 Taner Sener
* *
* This file is part of MobileFFmpeg. * This file is part of MobileFFmpeg.
* *
@@ -19,11 +19,8 @@
package com.arthenica.mobileffmpeg; package com.arthenica.mobileffmpeg;
import android.os.Build;
/** /**
* <p>This class is used to detect running ABI name using Android's <code>cpufeatures</code> * <p>This class is used to detect running ABI name using Google <code>cpu-features</code> library.
* library.
* *
* @author Taner Sener * @author Taner Sener
* @since v1.0 * @since v1.0
@@ -33,10 +30,6 @@ public class AbiDetect {
static { static {
armV7aNeonLoaded = false; 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"); System.loadLibrary("mobileffmpeg_abidetect");
/* ALL LIBRARIES LOADED AT STARTUP */ /* ALL LIBRARIES LOADED AT STARTUP */
@@ -90,7 +83,7 @@ public class AbiDetect {
/** /**
* <p>Returns whether MobileFFmpeg release is a long term release or not. * <p>Returns whether MobileFFmpeg release is a long term release or not.
* *
* @return YES or NO * @return yes or no
*/ */
native static boolean isNativeLTSBuild(); 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 android.content.Context.CAMERA_SERVICE;
import static com.arthenica.mobileffmpeg.Config.TAG; import static com.arthenica.mobileffmpeg.Config.TAG;
/**
* Utility class for camera devices.
*
* @author Taner Sener
*/
class CameraSupport { class CameraSupport {
/** /**
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018 Taner Sener * Copyright (c) 2018-2020 Taner Sener
* *
* This file is part of MobileFFmpeg. * This file is part of MobileFFmpeg.
* *
@@ -20,13 +20,19 @@
package com.arthenica.mobileffmpeg; package com.arthenica.mobileffmpeg;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -78,25 +84,48 @@ public class Config {
private static int lastCreatedPipeIndex; private static int lastCreatedPipeIndex;
private static final List<FFmpegExecution> executions;
private static SparseArray<ParcelFileDescriptor> pfdmap = new SparseArray<>();
static { static {
Log.i(Config.TAG, "Loading mobile-ffmpeg."); 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) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
/* LOADING LIBRARIES MANUALLY ON API < 21 */
final List<String> externalLibrariesEnabled = getExternalLibraries(); final List<String> externalLibrariesEnabled = getExternalLibraries();
if (externalLibrariesEnabled.contains("tesseract") || externalLibrariesEnabled.contains("x265") || externalLibrariesEnabled.contains("snappy") || externalLibrariesEnabled.contains("openh264") || externalLibrariesEnabled.contains("rubberband")) { if (externalLibrariesEnabled.contains("tesseract") || externalLibrariesEnabled.contains("x265") || externalLibrariesEnabled.contains("snappy") || externalLibrariesEnabled.contains("openh264") || externalLibrariesEnabled.contains("rubberband")) {
// libc++_shared.so included only when tesseract or x265 is enabled
System.loadLibrary("c++_shared"); System.loadLibrary("c++_shared");
} }
System.loadLibrary("cpufeatures");
System.loadLibrary("avutil"); if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
System.loadLibrary("swscale"); try {
System.loadLibrary("swresample"); System.loadLibrary("avutil_neon");
System.loadLibrary("avcodec"); System.loadLibrary("swscale_neon");
System.loadLibrary("avformat"); System.loadLibrary("swresample_neon");
System.loadLibrary("avfilter"); System.loadLibrary("avcodec_neon");
System.loadLibrary("avdevice"); 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 */ /* ALL MOBILE-FFMPEG LIBRARIES LOADED AT STARTUP */
@@ -104,30 +133,23 @@ public class Config {
FFmpeg.class.getName(); FFmpeg.class.getName();
FFprobe.class.getName(); FFprobe.class.getName();
/* boolean nativeMobileFFmpegLoaded = false;
* NEON supported arm-v7a library has a different name if (!nativeFFmpegTriedAndFailed && AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
*/ try {
boolean nativeLibraryLoaded = false;
if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
if (AbiDetect.isNativeLTSBuild()) {
/* /*
* 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");
System.loadLibrary("mobileffmpeg_armv7a_neon"); nativeMobileFFmpegLoaded = true;
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 {
AbiDetect.setArmV7aNeonLoaded(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"); System.loadLibrary("mobileffmpeg");
} }
@@ -141,6 +163,8 @@ public class Config {
enableRedirection(); enableRedirection();
lastCreatedPipeIndex = 0; lastCreatedPipeIndex = 0;
executions = Collections.synchronizedList(new ArrayList<FFmpegExecution>());
} }
/** /**
@@ -214,10 +238,11 @@ public class Config {
/** /**
* <p>Log redirection method called by JNI/native part. * <p>Log redirection method called by JNI/native part.
* *
* @param levelValue log level as defined in {@link Level} * @param executionId id of the execution that generated this log, 0 by default
* @param logMessage redirected log message * @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 Level level = Level.from(levelValue);
final String text = new String(logMessage); final String text = new String(logMessage);
@@ -229,7 +254,7 @@ public class Config {
if (logCallbackFunction != null) { if (logCallbackFunction != null) {
try { try {
logCallbackFunction.apply(new LogMessage(level, text)); logCallbackFunction.apply(new LogMessage(executionId, level, text));
} catch (final Exception e) { } catch (final Exception e) {
Log.e(Config.TAG, "Exception thrown inside LogCallback block", e); Log.e(Config.TAG, "Exception thrown inside LogCallback block", e);
} }
@@ -274,6 +299,7 @@ public class Config {
/** /**
* <p>Statistics redirection method called by JNI/native part. * <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 videoFrameNumber last processed frame number for videos
* @param videoFps frames processed per second for videos * @param videoFps frames processed per second for videos
* @param videoQuality quality of the video stream * @param videoQuality quality of the video stream
@@ -282,10 +308,10 @@ public class Config {
* @param bitrate output bit rate in kbits/s * @param bitrate output bit rate in kbits/s
* @param speed processing speed = processed duration / operation duration * @param speed processing speed = processed duration / operation duration
*/ */
private static void statistics(final int videoFrameNumber, final float videoFps, private static void statistics(final long executionId, final int videoFrameNumber,
final float videoQuality, final long size, final int time, final float videoFps, final float videoQuality, final long size,
final double bitrate, final double speed) { final int time, final double bitrate, final double speed) {
final Statistics newStatistics = new Statistics(videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed); final Statistics newStatistics = new Statistics(executionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
lastReceivedStatistics.update(newStatistics); lastReceivedStatistics.update(newStatistics);
if (statisticsCallbackFunction != null) { if (statisticsCallbackFunction != null) {
@@ -497,7 +523,7 @@ public class Config {
* @return MobileFFmpeg version * @return MobileFFmpeg version
*/ */
public static String getVersion() { public static String getVersion() {
if (AbiDetect.isNativeLTSBuild()) { if (isLTSBuild()) {
return String.format("%s-lts", getNativeVersion()); return String.format("%s-lts", getNativeVersion());
} else { } else {
return getNativeVersion(); return getNativeVersion();
@@ -586,6 +612,48 @@ public class Config {
} while (buffer.length() > 0); } 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. * Updates return code value for the last executed command.
* *
@@ -595,6 +663,15 @@ public class Config {
lastReturnCode = newLastReturnCode; 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. * <p>Enables native redirection. Necessary for log and statistics callback functions.
*/ */
@@ -624,28 +701,31 @@ public class Config {
* *
* @return FFmpeg version * @return FFmpeg version
*/ */
native static String getNativeFFmpegVersion(); private native static String getNativeFFmpegVersion();
/** /**
* <p>Returns MobileFFmpeg library version natively. * <p>Returns MobileFFmpeg library version natively.
* *
* @return MobileFFmpeg version * @return MobileFFmpeg version
*/ */
native static String getNativeVersion(); private native static String getNativeVersion();
/** /**
* <p>Synchronously executes FFmpeg natively with arguments provided. * <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 * @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 * <p>Cancels an ongoing FFmpeg operation natively. This function does not wait for termination
* to complete and returns immediately. * 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. * <p>Synchronously executes FFprobe natively with arguments provided.
@@ -663,14 +743,14 @@ public class Config {
* @param ffmpegPipePath full path of ffmpeg pipe * @param ffmpegPipePath full path of ffmpeg pipe
* @return zero on successful creation, non-zero on error * @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. * <p>Returns MobileFFmpeg library build date natively.
* *
* @return MobileFFmpeg library build date * @return MobileFFmpeg library build date
*/ */
native static String getNativeBuildDate(); private native static String getNativeBuildDate();
/** /**
* <p>Sets an environment variable natively. * <p>Sets an environment variable natively.
@@ -679,13 +759,74 @@ public class Config {
* @param variableValue environment variable value * @param variableValue environment variable value
* @return zero on success, non-zero on error * @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. * <p>Returns log output of the last executed single command natively.
* *
* @return output of the last executed single command * @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);
/**
* <p>Convert Structured Access Framework Uri (<code></code>"content:…"</code>) for MobileFfmpeg.
*
* @return String can be passed to FFmpeg or FFprobe
*/
private static String getSafParameter(Context context, Uri uri, String openMode) {
String displayName = "unknown";
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
displayName = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
}
} catch (Throwable ex) {
Log.e(TAG, "failed to get column", ex);
}
int fd = -1;
try {
ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, openMode);
fd = parcelFileDescriptor.getFd();
pfdmap.put(fd, parcelFileDescriptor);
} catch (Throwable e) {
Log.e(TAG, "obtaining " + openMode + " ParcelFileDescriptor for " + uri, e);
}
// workaround for https://issuetracker.google.com/issues/162440528: ANDROID_CREATE_DOCUMENT generating file names like "transcode.mp3 (2)"
if (displayName.lastIndexOf('.') > 0 && displayName.lastIndexOf(' ') > displayName.lastIndexOf('.')) {
String extension = displayName.substring(displayName.lastIndexOf('.'), displayName.lastIndexOf(' '));
displayName += extension;
}
// spaces can break argument list parsing, see https://github.com/alexcohn/mobile-ffmpeg/pull/1#issuecomment-688643836
final char NBSP = (char)0xa0;
return "saf:" + fd + "/" + displayName.replace(' ', NBSP);
}
public static String getSafParameterForRead(Context context, Uri uri) {
return getSafParameter(context, uri, "r");
}
public static String getSafParameterForWrite(Context context, Uri uri) {
return getSafParameter(context, uri, "w");
}
private static void closeParcelFileDescriptor(int fd) {
try {
ParcelFileDescriptor pfd = pfdmap.get(fd);
if (pfd != null) {
pfd.close();
pfdmap.delete(fd);
}
} catch (Throwable e) {
Log.e(TAG, "closeParcelFileDescriptor " + fd, e);
}
}
} }
@@ -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. * This file is part of MobileFFmpeg.
* *
@@ -19,22 +19,34 @@
package com.arthenica.mobileffmpeg; package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 * <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and
* FFmpeg commands. * asynchronous {@link #executeAsync(String, ExecuteCallback)} methods to execute FFmpeg commands.
* <pre> * <pre>
* int rc = FFmpeg.execute("-i file1.mp4 -c:v libxvid file1.avi"); * 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)); * Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
* </pre> * </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 * @author Taner Sener
* @since v1.0 * @since v1.0
*/ */
public class FFmpeg { public class FFmpeg {
static final long DEFAULT_EXECUTION_ID = 0;
private static final AtomicLong executionIdCounter = new AtomicLong(3000);
static { static {
AbiDetect.class.getName(); AbiDetect.class.getName();
Config.class.getName(); Config.class.getName();
@@ -50,14 +62,43 @@ public class FFmpeg {
* <p>Synchronously executes FFmpeg with arguments provided. * <p>Synchronously executes FFmpeg with arguments provided.
* *
* @param arguments FFmpeg command options/arguments as string array * @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) { 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 command FFmpeg command
* @param delimiter delimiter used to split arguments * @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 * @since 3.0
* @deprecated argument splitting mechanism used in this method is pretty simple and prone to * @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 * errors. Consider using a more advanced method like {@link #execute(String)} or
@@ -82,18 +123,76 @@ public class FFmpeg {
* your command. * your command.
* *
* @param command FFmpeg 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) { public static int execute(final String command) {
return execute(parseArguments(command)); return execute(parseArguments(command));
} }
/** /**
* <p>Cancels an ongoing operation. This function does not wait for termination to complete and * <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
* returns immediately. * 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() { 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]); 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 * <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 * multiple operations (execute or getMediaInformation) at the same time, the response of this
@@ -83,14 +83,22 @@ public class FFprobe {
* @since 3.0 * @since 3.0
*/ */
public static MediaInformation getMediaInformation(final String path) { 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()); * <p>Returns media information for the given command.
} else { *
Log.i(Config.TAG, Config.getLastCommandOutput()); * <p>This method does not support executing multiple concurrent operations. If you execute
return null; * 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); 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; 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 * @author Taner Sener
* @since v2.1 * @since v2.1
@@ -20,21 +20,27 @@
package com.arthenica.mobileffmpeg; package com.arthenica.mobileffmpeg;
/** /**
* <p>Represents a redirected log message. * <p>Logs for running executions.
* *
* @author Taner Sener * @author Taner Sener
* @since v2.1 * @since v2.1
*/ */
public class LogMessage { public class LogMessage {
private final long executionId;
private final Level level; private final Level level;
private final String text; 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.level = level;
this.text = text; this.text = text;
} }
public long getExecutionId() {
return executionId;
}
public Level getLevel() { public Level getLevel() {
return level; return level;
} }
@@ -43,4 +49,21 @@ public class LogMessage {
return text; 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. * This file is part of MobileFFmpeg.
* *
@@ -19,11 +19,9 @@
package com.arthenica.mobileffmpeg; package com.arthenica.mobileffmpeg;
import java.util.ArrayList; import org.json.JSONObject;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* Media information class. * Media information class.
@@ -32,49 +30,38 @@ import java.util.Set;
*/ */
public class MediaInformation { public class MediaInformation {
/** private static final String KEY_MEDIA_PROPERTIES = "format";
* Format private static final String KEY_FILENAME = "filename";
*/ private static final String KEY_FORMAT = "format_name";
private String format; 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; public String getFilename() {
return getStringProperty(KEY_FILENAME);
/**
* 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<>();
} }
/** /**
@@ -83,34 +70,16 @@ public class MediaInformation {
* @return media format * @return media format
*/ */
public String getFormat() { 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) { public String getLongFormat() {
this.format = format; return getStringProperty(KEY_FORMAT_LONG);
}
/**
* Returns path.
*
* @return media path
*/
public String getPath() {
return path;
}
/**
* Sets path.
*
* @param path media path
*/
public void setPath(String path) {
this.path = path;
} }
/** /**
@@ -118,17 +87,8 @@ public class MediaInformation {
* *
* @return media duration in milliseconds * @return media duration in milliseconds
*/ */
public Long getDuration() { public String getDuration() {
return duration; return getStringProperty(KEY_DURATION);
}
/**
* Sets duration.
*
* @param duration media duration in milliseconds
*/
public void setDuration(Long duration) {
this.duration = duration;
} }
/** /**
@@ -136,17 +96,17 @@ public class MediaInformation {
* *
* @return media start time in milliseconds * @return media start time in milliseconds
*/ */
public Long getStartTime() { public String getStartTime() {
return startTime; 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) { public String getSize() {
this.startTime = startTime; return getStringProperty(KEY_SIZE);
} }
/** /**
@@ -154,63 +114,17 @@ public class MediaInformation {
* *
* @return media bitrate in kb/s * @return media bitrate in kb/s
*/ */
public Long getBitrate() { public String getBitrate() {
return bitrate; 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) { public JSONObject getTags() {
this.bitrate = bitrate; return getProperties(KEY_TAGS);
}
/**
* 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);
} }
/** /**
@@ -222,4 +136,75 @@ public class MediaInformation {
return streams; 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. * This file is part of MobileFFmpeg.
* *
@@ -21,505 +21,55 @@ package com.arthenica.mobileffmpeg;
import android.util.Log; import android.util.Log;
import com.arthenica.mobileffmpeg.util.Pair; import org.json.JSONArray;
import com.arthenica.mobileffmpeg.util.Trio; import org.json.JSONException;
import org.json.JSONObject;
import java.math.BigDecimal; import java.util.ArrayList;
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;
/** /**
* Helper class for {@link MediaInformation}. * Helper class for parsing {@link MediaInformation}.
* *
* @since 3.0 * @since 3.0
*/ */
public class MediaInformationParser { public class MediaInformationParser {
public static SimpleDateFormat DURATION_FORMAT; /**
* Extracts MediaInformation from the given ffprobe json output.
public static Date REFERENCE_DURATION; *
* @param ffprobeJsonOutput ffprobe json output
static { * @return created {@link MediaInformation} instance of null if a parsing error occurs
*/
public static MediaInformation from(final String ffprobeJsonOutput) {
try { try {
DURATION_FORMAT = new SimpleDateFormat("kk:mm:ss", Locale.getDefault()); return fromWithError(ffprobeJsonOutput);
REFERENCE_DURATION = DURATION_FORMAT.parse("00:00:00"); } catch (JSONException e) {
} catch (final ParseException e) { Log.e(Config.TAG, "MediaInformation parsing failed.", e);
Log.i(Config.TAG, "Preparing duration reference failed.", e); e.printStackTrace();
DURATION_FORMAT = null; return null;
REFERENCE_DURATION = 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 * @param ffprobeJsonOutput ffprobe json output
* @return parsed instance of null if a parsing error occurs * @return created {@link MediaInformation} instance
* @throws JSONException if a parsing error occurs
*/ */
public static MediaInformation from(final String rawCommandOutput) { public static MediaInformation fromWithError(final String ffprobeJsonOutput) throws JSONException {
final MediaInformation mediaInformation = new MediaInformation(); JSONObject jsonObject = new JSONObject(ffprobeJsonOutput);
JSONArray streamArray = jsonObject.optJSONArray("streams");
if (rawCommandOutput != null) { ArrayList<StreamInformation> arrayList = new ArrayList<>();
final String[] split = rawCommandOutput.split("\n"); for (int i = 0; streamArray != null && i < streamArray.length(); i++) {
boolean metadata = false; JSONObject streamObject = streamArray.optJSONObject(i);
boolean sidedata = false; if (streamObject != null) {
StreamInformation lastCreatedStream = null; arrayList.add(new StreamInformation(streamObject));
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();
} }
} }
return new Pair<>(key, value); return new MediaInformation(jsonObject, arrayList);
}
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;
}
} }
} }
@@ -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; package com.arthenica.mobileffmpeg;
/** /**
* <p>Represents statistics data. * <p>Statistics for running executions.
* *
* @author Taner Sener * @author Taner Sener
* @since v2.1 * @since v2.1
*/ */
public class Statistics { public class Statistics {
private long executionId;
private int videoFrameNumber; private int videoFrameNumber;
private float videoFps; private float videoFps;
private float videoQuality; private float videoQuality;
@@ -36,6 +37,7 @@ public class Statistics {
private double speed; private double speed;
public Statistics() { public Statistics() {
executionId = 0;
videoFrameNumber = 0; videoFrameNumber = 0;
videoFps = 0; videoFps = 0;
videoQuality = 0; videoQuality = 0;
@@ -45,7 +47,8 @@ public class Statistics {
speed = 0; 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.videoFrameNumber = videoFrameNumber;
this.videoFps = videoFps; this.videoFps = videoFps;
this.videoQuality = videoQuality; this.videoQuality = videoQuality;
@@ -57,35 +60,44 @@ public class Statistics {
public void update(final Statistics newStatistics) { public void update(final Statistics newStatistics) {
if (newStatistics != null) { if (newStatistics != null) {
this.executionId = newStatistics.getExecutionId();
if (newStatistics.getVideoFrameNumber() > 0) { if (newStatistics.getVideoFrameNumber() > 0) {
this.videoFrameNumber = newStatistics.getVideoFrameNumber(); this.videoFrameNumber = newStatistics.getVideoFrameNumber();
} }
if (newStatistics.getVideoFps() > 0){ if (newStatistics.getVideoFps() > 0) {
this.videoFps = newStatistics.getVideoFps(); this.videoFps = newStatistics.getVideoFps();
} }
if (newStatistics.getVideoQuality() > 0){ if (newStatistics.getVideoQuality() > 0) {
this.videoQuality = newStatistics.getVideoQuality(); this.videoQuality = newStatistics.getVideoQuality();
} }
if (newStatistics.getSize() > 0){ if (newStatistics.getSize() > 0) {
this.size = newStatistics.getSize(); this.size = newStatistics.getSize();
} }
if (newStatistics.getTime() > 0){ if (newStatistics.getTime() > 0) {
this.time = newStatistics.getTime(); this.time = newStatistics.getTime();
} }
if (newStatistics.getBitrate() > 0){ if (newStatistics.getBitrate() > 0) {
this.bitrate = newStatistics.getBitrate(); this.bitrate = newStatistics.getBitrate();
} }
if (newStatistics.getSpeed() > 0){ if (newStatistics.getSpeed() > 0) {
this.speed = newStatistics.getSpeed(); this.speed = newStatistics.getSpeed();
} }
} }
} }
public long getExecutionId() {
return executionId;
}
public void setExecutionId(long executionId) {
this.executionId = executionId;
}
public int getVideoFrameNumber() { public int getVideoFrameNumber() {
return videoFrameNumber; return videoFrameNumber;
} }
@@ -142,4 +154,30 @@ public class Statistics {
this.speed = speed; 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; 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 * @author Taner Sener
* @since v2.1 * @since v2.1
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018 Taner Sener * Copyright (c) 2018, 2020 Taner Sener
* *
* This file is part of MobileFFmpeg. * This file is part of MobileFFmpeg.
* *
@@ -19,9 +19,7 @@
package com.arthenica.mobileffmpeg; package com.arthenica.mobileffmpeg;
import java.util.HashMap; import org.json.JSONObject;
import java.util.Map;
import java.util.Set;
/** /**
* Stream information class. * Stream information class.
@@ -30,68 +28,32 @@ import java.util.Set;
*/ */
public class StreamInformation { public class StreamInformation {
/** private static final String KEY_INDEX = "index";
* Stream index private static final String KEY_TYPE = "codec_type";
*/ private static final String KEY_CODEC = "codec_name";
private Long index; private static final String KEY_CODEC_LONG = "codec_long_name";
private static final String KEY_FORMAT = "pix_fmt";
private String type; private static final String KEY_WIDTH = "width";
private String codec; private static final String KEY_HEIGHT = "height";
private String fullCodec; private static final String KEY_BIT_RATE = "bit_rate";
private String format; private static final String KEY_SAMPLE_RATE = "sample_rate";
private String fullFormat; private static final String KEY_SAMPLE_FORMAT = "sample_fmt";
private static final String KEY_CHANNEL_LAYOUT = "channel_layout";
private Long width; private static final String KEY_SAMPLE_ASPECT_RATIO = "sample_aspect_ratio";
private Long height; private static final String KEY_DISPLAY_ASPECT_RATIO = "display_aspect_ratio";
private static final String KEY_AVERAGE_FRAME_RATE = "avg_frame_rate";
private Long bitrate; private static final String KEY_REAL_FRAME_RATE = "r_frame_rate";
private Long sampleRate; private static final String KEY_TIME_BASE = "time_base";
private String sampleFormat; private static final String KEY_CODEC_TIME_BASE = "codec_time_base";
private String channelLayout; private static final String KEY_TAGS = "tags";
/** /**
* SAR * Stores all properties.
*/ */
private String sampleAspectRatio; private final JSONObject jsonObject;
/** public StreamInformation(final JSONObject jsonObject) {
* DAR this.jsonObject = jsonObject;
*/
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<>();
} }
/** /**
@@ -100,16 +62,7 @@ public class StreamInformation {
* @return stream index, starting from zero * @return stream index, starting from zero
*/ */
public Long getIndex() { public Long getIndex() {
return index; return getNumberProperty(KEY_INDEX);
}
/**
* Sets stream index.
*
* @param index stream index, starting from zero
*/
public void setIndex(Long index) {
this.index = index;
} }
/** /**
@@ -118,16 +71,7 @@ public class StreamInformation {
* @return stream type; audio or video * @return stream type; audio or video
*/ */
public String getType() { public String getType() {
return type; return getStringProperty(KEY_TYPE);
}
/**
* Sets stream type.
*
* @param type stream type; audio or video
*/
public void setType(String type) {
this.type = type;
} }
/** /**
@@ -136,16 +80,7 @@ public class StreamInformation {
* @return stream codec * @return stream codec
*/ */
public String getCodec() { public String getCodec() {
return codec; return getStringProperty(KEY_CODEC);
}
/**
* Sets stream codec.
*
* @param codec stream codec
*/
public void setCodec(String codec) {
this.codec = codec;
} }
/** /**
@@ -154,16 +89,7 @@ public class StreamInformation {
* @return stream codec with additional profile and mode information * @return stream codec with additional profile and mode information
*/ */
public String getFullCodec() { public String getFullCodec() {
return fullCodec; return getStringProperty(KEY_CODEC_LONG);
}
/**
* Sets full stream codec.
*
* @param fullCodec stream codec with additional profile and mode information
*/
public void setFullCodec(String fullCodec) {
this.fullCodec = fullCodec;
} }
/** /**
@@ -172,34 +98,7 @@ public class StreamInformation {
* @return stream format * @return stream format
*/ */
public String getFormat() { public String getFormat() {
return format; return getStringProperty(KEY_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;
} }
/** /**
@@ -208,16 +107,7 @@ public class StreamInformation {
* @return width in pixels * @return width in pixels
*/ */
public Long getWidth() { public Long getWidth() {
return width; return getNumberProperty(KEY_WIDTH);
}
/**
* Sets width.
*
* @param width width in pixels
*/
public void setWidth(Long width) {
this.width = width;
} }
/** /**
@@ -226,16 +116,7 @@ public class StreamInformation {
* @return height in pixels * @return height in pixels
*/ */
public Long getHeight() { public Long getHeight() {
return height; return getNumberProperty(KEY_HEIGHT);
}
/**
* Sets height.
*
* @param height height in pixels
*/
public void setHeight(Long height) {
this.height = height;
} }
/** /**
@@ -243,17 +124,8 @@ public class StreamInformation {
* *
* @return bitrate in kb/s * @return bitrate in kb/s
*/ */
public Long getBitrate() { public String getBitrate() {
return bitrate; return getStringProperty(KEY_BIT_RATE);
}
/**
* Sets bitrate.
*
* @param bitrate bitrate in kb/s
*/
public void setBitrate(Long bitrate) {
this.bitrate = bitrate;
} }
/** /**
@@ -261,17 +133,8 @@ public class StreamInformation {
* *
* @return sample rate in hz * @return sample rate in hz
*/ */
public Long getSampleRate() { public String getSampleRate() {
return sampleRate; return getStringProperty(KEY_SAMPLE_RATE);
}
/**
* Sets sample rate.
*
* @param sampleRate sample rate in hz
*/
public void setSampleRate(Long sampleRate) {
this.sampleRate = sampleRate;
} }
/** /**
@@ -280,16 +143,7 @@ public class StreamInformation {
* @return sample format * @return sample format
*/ */
public String getSampleFormat() { public String getSampleFormat() {
return sampleFormat; return getStringProperty(KEY_SAMPLE_FORMAT);
}
/**
* Sets sample format.
*
* @param sampleFormat sample format
*/
public void setSampleFormat(String sampleFormat) {
this.sampleFormat = sampleFormat;
} }
/** /**
@@ -298,16 +152,7 @@ public class StreamInformation {
* @return channel layout * @return channel layout
*/ */
public String getChannelLayout() { public String getChannelLayout() {
return channelLayout; return getStringProperty(KEY_CHANNEL_LAYOUT);
}
/**
* Sets channel layout.
*
* @param channelLayout channel layout
*/
public void setChannelLayout(String channelLayout) {
this.channelLayout = channelLayout;
} }
/** /**
@@ -316,16 +161,7 @@ public class StreamInformation {
* @return sample aspect ratio * @return sample aspect ratio
*/ */
public String getSampleAspectRatio() { public String getSampleAspectRatio() {
return sampleAspectRatio; return getStringProperty(KEY_SAMPLE_ASPECT_RATIO);
}
/**
* Sets sample aspect ratio.
*
* @param sampleAspectRatio sample aspect ratio
*/
public void setSampleAspectRatio(String sampleAspectRatio) {
this.sampleAspectRatio = sampleAspectRatio;
} }
/** /**
@@ -334,16 +170,7 @@ public class StreamInformation {
* @return display aspect ratio * @return display aspect ratio
*/ */
public String getDisplayAspectRatio() { public String getDisplayAspectRatio() {
return displayAspectRatio; return getStringProperty(KEY_DISPLAY_ASPECT_RATIO);
}
/**
* Sets display aspect ratio.
*
* @param displayAspectRatio display aspect ratio
*/
public void setDisplayAspectRatio(String displayAspectRatio) {
this.displayAspectRatio = displayAspectRatio;
} }
/** /**
@@ -352,16 +179,7 @@ public class StreamInformation {
* @return average frame rate in fps * @return average frame rate in fps
*/ */
public String getAverageFrameRate() { public String getAverageFrameRate() {
return averageFrameRate; return getStringProperty(KEY_AVERAGE_FRAME_RATE);
}
/**
* Sets average frame rate.
*
* @param averageFrameRate average frame rate in fps
*/
public void setAverageFrameRate(String averageFrameRate) {
this.averageFrameRate = averageFrameRate;
} }
/** /**
@@ -370,16 +188,7 @@ public class StreamInformation {
* @return real frame rate in tbr * @return real frame rate in tbr
*/ */
public String getRealFrameRate() { public String getRealFrameRate() {
return realFrameRate; return getStringProperty(KEY_REAL_FRAME_RATE);
}
/**
* Sets real frame rate.
*
* @param realFrameRate real frame rate in tbr
*/
public void setRealFrameRate(String realFrameRate) {
this.realFrameRate = realFrameRate;
} }
/** /**
@@ -388,16 +197,7 @@ public class StreamInformation {
* @return time base in tbn * @return time base in tbn
*/ */
public String getTimeBase() { public String getTimeBase() {
return timeBase; return getStringProperty(KEY_TIME_BASE);
}
/**
* Sets time base.
*
* @param timeBase time base in tbn
*/
public void setTimeBase(String timeBase) {
this.timeBase = timeBase;
} }
/** /**
@@ -406,74 +206,78 @@ public class StreamInformation {
* @return codec time base in tbc * @return codec time base in tbc
*/ */
public String getCodecTimeBase() { 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) { public JSONObject getTags() {
this.codecTimeBase = codecTimeBase; return getProperties(KEY_TAGS);
} }
/** /**
* Adds metadata. * Returns the stream property associated with the key.
* *
* @param key metadata key * @param key property key
* @param value metadata value * @return stream property as string or null if the key is not found
*/ */
public void addMetadata(String key, String value) { public String getStringProperty(final String key) {
this.metadata.put(key, value); 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 * @param key property key
* @return metadata value associated with this key * @return stream property as Long or null if the key is not found
*/ */
public String getMetadata(String key) { public Long getNumberProperty(String key) {
return this.metadata.get(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() { public JSONObject getProperties(String key) {
return this.metadata.entrySet(); 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 * @return all stream properties as a JSONObject or null if no properties are defined
* @param value side data value
*/ */
public void addSidedata(String key, String value) { public JSONObject getAllProperties() {
this.sidedata.put(key, value); return jsonObject;
}
/**
* 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();
} }
} }
@@ -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. * 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());
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.2' classpath 'com.android.tools.build:gradle:4.0.1'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
+2 -1
View File
@@ -1,5 +1,6 @@
#Thu Jul 30 09:25:25 BST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+74 -29
View File
@@ -1,14 +1,30 @@
LOCAL_PATH := $(call my-dir) MY_LOCAL_PATH := $(call my-dir)
$(call import-add-path, $(LOCAL_PATH)) $(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
else
FFMPEG_INCLUDES := $(MY_LOCAL_PATH)/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
endif
MY_ARM_MODE := arm MY_ARM_MODE := arm
MY_ARM_NEON := false MY_ARM_NEON := false
MY_PATH := ../app/src/main/cpp LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
# DEFINE ARCH FLAGS # DEFINE ARCH FLAGS
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
MY_ARCH_FLAGS := ARM_V7A MY_ARCH_FLAGS := ARM_V7A
MY_ARM_NEON := true MY_ARM_NEON := false
endif endif
ifeq ($(TARGET_ARCH_ABI), arm64-v8a) ifeq ($(TARGET_ARCH_ABI), arm64-v8a)
MY_ARCH_FLAGS := ARM64_V8A MY_ARCH_FLAGS := ARM64_V8A
@@ -21,40 +37,69 @@ ifeq ($(TARGET_ARCH_ABI), x86_64)
MY_ARCH_FLAGS := X86_64 MY_ARCH_FLAGS := X86_64
endif 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) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg_abidetect LOCAL_MODULE := mobileffmpeg_abidetect
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg_abidetect.c LOCAL_SRC_FILES := 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_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -DMOBILE_FFMPEG_${MY_ARCH_FLAGS}
LOCAL_C_INCLUDES := $(FFMPEG_INCLUDES)
LOCAL_LDLIBS := -llog -lz -landroid LOCAL_LDLIBS := -llog -lz -landroid
LOCAL_SHARED_LIBRARIES := cpufeatures LOCAL_STATIC_LIBRARIES := cpu-features
LOCAL_ARM_NEON := ${MY_ARM_NEON} LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY) include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS) $(call import-module, cpu-features)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg
ifeq ($(TARGET_PLATFORM),android-16) 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) 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 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 endif
LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include MY_SRC_FILES += saf_wrapper.c
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
+12
View File
@@ -0,0 +1,12 @@
ifeq ($(MY_ARMV7_NEON), true)
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/neon/cpu-features/lib
else
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/cpu-features/lib
endif
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_ARM_MODE := arm
MY_FFMPEG_LIB := ../../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/lib
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavcodec LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavcodec.so LOCAL_SRC_FILES := libavcodec.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavfilter LOCAL_MODULE := libavfilter
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavfilter.so LOCAL_SRC_FILES := libavfilter.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavdevice LOCAL_MODULE := libavdevice
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavdevice.so LOCAL_SRC_FILES := libavdevice.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavformat LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavformat.so LOCAL_SRC_FILES := libavformat.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavutil 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 $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libswresample LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswresample.so LOCAL_SRC_FILES := libswresample.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libswscale LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswscale.so LOCAL_SRC_FILES := libswscale.so
include $(PREBUILT_SHARED_LIBRARY) 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_ARM_MODE := arm
MY_FFMPEG_LIB := ../../../../prebuilt/android-$(TARGET_ARCH)/neon/ffmpeg/lib MY_ARM_NEON := true
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavcodec_neon LOCAL_MODULE := libavcodec_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavcodec.so LOCAL_SRC_FILES := libavcodec_neon.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavfilter_neon LOCAL_MODULE := libavfilter_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavfilter.so LOCAL_SRC_FILES := libavfilter_neon.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavdevice_neon LOCAL_MODULE := libavdevice_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavdevice.so LOCAL_SRC_FILES := libavdevice_neon.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavformat_neon LOCAL_MODULE := libavformat_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavformat.so LOCAL_SRC_FILES := libavformat_neon.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavutil_neon LOCAL_MODULE := libavutil_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) 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 $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libswresample_neon LOCAL_MODULE := libswresample_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswresample.so LOCAL_SRC_FILES := libswresample_neon.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libswscale_neon LOCAL_MODULE := libswscale_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswscale.so LOCAL_SRC_FILES := libswscale_neon.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
+3
View File
@@ -1 +1,4 @@
include ':app', ':test-app' include ':app', ':test-app'
include ':mobile-ffmpeg'
project(':mobile-ffmpeg').projectDir = new File('..')
rootProject.name='Mobile FFmpeg (android)'
+7 -7
View File
@@ -9,13 +9,13 @@ android {
keyPassword 'android' keyPassword 'android'
} }
} }
compileSdkVersion 29 compileSdkVersion 30
defaultConfig { defaultConfig {
applicationId "com.arthenica.mobileffmpeg.test" applicationId "com.arthenica.mobileffmpeg.test"
minSdkVersion 24 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 30
versionCode 240432 versionCode 160440
versionName "4.3.2" versionName "4.4.LTS"
} }
buildTypes { buildTypes {
debug { debug {
@@ -51,8 +51,8 @@ android.applicationVariants.all { variant ->
} }
dependencies { dependencies {
// implementation project(':app') implementation project(':app')
implementation 'com.arthenica:mobile-ffmpeg-full:4.3.2' // implementation 'com.arthenica:mobile-ffmpeg-full:4.4.LTS'
implementation 'com.arthenica:smart-exception-java:0.1.0' implementation 'com.arthenica:smart-exception-java:0.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 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.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage; import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.util.DialogUtil; import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback; import com.arthenica.mobileffmpeg.ExecuteCallback;
import java.io.File; import java.io.File;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@@ -103,7 +103,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
@Override @Override
public void apply(final LogMessage message) { public void apply(final LogMessage message) {
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -145,17 +145,17 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
clearLog(); 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 @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideProgressDialog(); hideProgressDialog();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -164,7 +164,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
android.util.Log.d(TAG, "Encode completed successfully."); android.util.Log.d(TAG, "Encode completed successfully.");
} else { } else {
Popup.show(requireContext(), "Encode failed. Please check log for the details."); 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; 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()); 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); 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() { 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()); 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("Creating audio sample with '%s'.", ffmpegCommand));
int result = FFmpeg.execute(ffmpegCommand); int result = FFmpeg.execute(ffmpegCommand);
if (result == 0) { if (result == 0) {
encodeButton.setEnabled(true); encodeButton.setEnabled(true);
android.util.Log.d(TAG, "AUDIO sample created"); android.util.Log.d(TAG, "AUDIO sample created");
} else { } 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."); 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": case "opus":
extension = "opus"; extension = "opus";
break; break;
case "amr": case "amr-nb":
extension = "amr";
break;
case "amr-wb":
extension = "amr"; extension = "amr";
break; break;
case "ilbc": case "ilbc":
@@ -260,7 +263,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
disableLogCallback(); disableLogCallback();
createAudioSample(); createAudioSample();
enableLogCallback(); enableLogCallback();
Popup.show(requireContext(), Tooltip.AUDIO_TEST_TOOLTIP_TEXT); Popup.show(requireContext(), getString(R.string.audio_test_tooltip_text));
} }
public void appendLog(final String logMessage) { public void appendLog(final String logMessage) {
@@ -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); return String.format("-hide_banner -y -i %s -c:a libvorbis -b:a 64k %s", audioSampleFile, audioOutputFile);
case "opus": case "opus":
return String.format("-hide_banner -y -i %s -c:a libopus -b:a 64k -vbr on -compression_level 10 %s", audioSampleFile, audioOutputFile); 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); 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": case "ilbc":
return String.format("-hide_banner -y -i %s -c:a ilbc -ar 8000 -b:a 15200 %s", audioSampleFile, audioOutputFile); return String.format("-hide_banner -y -i %s -c:a ilbc -ar 8000 -b:a 15200 %s", audioSampleFile, audioOutputFile);
case "speex": case "speex":
@@ -112,13 +112,15 @@ public class CommandTabFragment extends Fragment {
final String ffmpegCommand = String.format("%s", commandText.getText().toString()); 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, "Testing FFmpeg COMMAND synchronously.");
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process started with arguments\n\'%s\'", ffmpegCommand)); android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process started with arguments\n\'%s\'", ffmpegCommand));
int result = FFmpeg.execute(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) { if (result != 0) {
Popup.show(requireContext(), "Command failed. Please check output for the details."); 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); 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) { if (result != 0) {
Popup.show(requireContext(), "Command failed. Please check output for the details."); Popup.show(requireContext(), "Command failed. Please check output for the details.");
@@ -146,7 +148,7 @@ public class CommandTabFragment extends Fragment {
private void setActive() { private void setActive() {
Log.i(MainActivity.TAG, "Command Tab Activated"); Log.i(MainActivity.TAG, "Command Tab Activated");
enableLogCallback(); enableLogCallback();
Popup.show(requireContext(), Tooltip.COMMAND_TEST_TOOLTIP_TEXT); Popup.show(requireContext(), getString(R.string.command_test_tooltip_text));
} }
public void appendLog(final String logMessage) { public void appendLog(final String logMessage) {
@@ -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(), getString(R.string.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.MediaInformation;
import com.arthenica.mobileffmpeg.StreamInformation; import com.arthenica.mobileffmpeg.StreamInformation;
import java.util.Map; import org.json.JSONObject;
import java.util.Set;
import java.util.Iterator;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class HttpsTabFragment extends Fragment { public class HttpsTabFragment extends Fragment {
@@ -63,7 +64,7 @@ public class HttpsTabFragment extends Fragment {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
getInfo(); runGetMediaInformation();
} }
}); });
@@ -98,16 +99,16 @@ public class HttpsTabFragment extends Fragment {
}); });
} }
public void getInfo() { public void runGetMediaInformation() {
clearLog(); clearLog();
String testUrl = urlText.getText().toString(); String testUrl = urlText.getText().toString();
if (testUrl.isEmpty()) { if (testUrl.isEmpty()) {
testUrl = HTTPS_TEST_DEFAULT_URL; testUrl = HTTPS_TEST_DEFAULT_URL;
urlText.setText(testUrl); 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 { } 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 // HTTPS COMMAND ARGUMENTS
@@ -115,7 +116,7 @@ public class HttpsTabFragment extends Fragment {
if (information == null) { if (information == null) {
appendLog("Get media information failed\n"); appendLog("Get media information failed\n");
} else { } else {
appendLog("Media information for " + information.getPath() + "\n"); appendLog("Media information for " + information.getFilename() + "\n");
if (information.getFormat() != null) { if (information.getFormat() != null) {
appendLog("Format: " + information.getFormat() + "\n"); appendLog("Format: " + information.getFormat() + "\n");
@@ -129,10 +130,14 @@ public class HttpsTabFragment extends Fragment {
if (information.getStartTime() != null) { if (information.getStartTime() != null) {
appendLog("Start time: " + information.getStartTime() + "\n"); appendLog("Start time: " + information.getStartTime() + "\n");
} }
if (information.getMetadataEntries() != null) { if (information.getTags() != null) {
Set<Map.Entry<String, String>> entries = information.getMetadataEntries(); JSONObject tags = information.getTags();
for (Map.Entry<String, String> entry : entries) { if (tags != null) {
appendLog("Metadata: " + entry.getKey() + ":" + entry.getValue() + "\n"); Iterator<String> keys = tags.keys();
while (keys.hasNext()) {
String next = keys.next();
appendLog("Tag: " + next + ":" + tags.optString(next) + "\n");
}
} }
} }
if (information.getStreams() != null) { if (information.getStreams() != null) {
@@ -152,9 +157,6 @@ public class HttpsTabFragment extends Fragment {
if (stream.getFormat() != null) { if (stream.getFormat() != null) {
appendLog("Stream format: " + stream.getFormat() + "\n"); appendLog("Stream format: " + stream.getFormat() + "\n");
} }
if (stream.getFullFormat() != null) {
appendLog("Stream full format: " + stream.getFullFormat() + "\n");
}
if (stream.getWidth() != null) { if (stream.getWidth() != null) {
appendLog("Stream width: " + stream.getWidth() + "\n"); appendLog("Stream width: " + stream.getWidth() + "\n");
@@ -196,10 +198,14 @@ public class HttpsTabFragment extends Fragment {
appendLog("Stream codec time base: " + stream.getCodecTimeBase() + "\n"); appendLog("Stream codec time base: " + stream.getCodecTimeBase() + "\n");
} }
if (stream.getMetadataEntries() != null) { if (stream.getTags() != null) {
Set<Map.Entry<String, String>> entries = stream.getMetadataEntries(); JSONObject tags = stream.getTags();
for (Map.Entry<String, String> entry : entries) { if (tags != null) {
appendLog("Stream metadata: " + entry.getKey() + ":" + entry.getValue() + "\n"); Iterator<String> keys = tags.keys();
while (keys.hasNext()) {
String next = keys.next();
appendLog(String.format("Stream tag: %s:%s\n", next, tags.optString(next)));
}
} }
} }
} }
@@ -210,7 +216,7 @@ public class HttpsTabFragment extends Fragment {
public void setActive() { public void setActive() {
Log.i(MainActivity.TAG, "Https Tab Activated"); Log.i(MainActivity.TAG, "Https Tab Activated");
enableLogCallback(); enableLogCallback();
Popup.show(requireContext(), Tooltip.HTTPS_TEST_TOOLTIP_TEXT); Popup.show(requireContext(), getString(R.string.https_test_tooltip_text));
} }
public void appendLog(final String logMessage) { public void appendLog(final String logMessage) {
@@ -32,10 +32,12 @@ import androidx.core.app.ActivityCompat;
import androidx.viewpager.widget.PagerTabStrip; import androidx.viewpager.widget.PagerTabStrip;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.arthenica.mobileffmpeg.AsyncFFmpegExecuteTask;
import com.arthenica.mobileffmpeg.Config; 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.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions; import com.arthenica.smartexception.java.Exceptions;
import java.io.File; import java.io.File;
@@ -61,7 +63,7 @@ public class MainActivity extends AppCompatActivity {
Exceptions.registerRootPackage("com.arthenica"); Exceptions.registerRootPackage("com.arthenica");
} }
protected static final Queue<Callable> actionQueue = new ConcurrentLinkedQueue<>(); protected static final Queue<Callable<Object>> actionQueue = new ConcurrentLinkedQueue<>();
protected static final Handler handler = new Handler(); protected static final Handler handler = new Handler();
@@ -69,7 +71,7 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
public void run() { public void run() {
Callable callable; Callable<Object> callable;
do { do {
callable = actionQueue.poll(); callable = actionQueue.poll();
@@ -77,7 +79,7 @@ public class MainActivity extends AppCompatActivity {
try { try {
callable.call(); callable.call();
} catch (final Exception e) { } catch (final Exception e) {
android.util.Log.e(TAG, String.format("Running UI action received error.%s", Exceptions.getStackTraceString(e))); android.util.Log.e(TAG, String.format("Running UI action received error.%s.", Exceptions.getStackTraceString(e)));
} }
} }
} while (callable != null); } while (callable != null);
@@ -128,11 +130,13 @@ public class MainActivity extends AppCompatActivity {
registerAppFont(); registerAppFont();
Log.d(TAG, "Application fonts registered."); Log.d(TAG, "Application fonts registered.");
} catch (final IOException e) { } catch (final IOException e) {
Log.e(TAG, String.format("Font registration failed.%s", Exceptions.getStackTraceString(e))); Log.e(TAG, String.format("Font registration failed.%s.", Exceptions.getStackTraceString(e)));
} }
Log.d(TAG, "Listing supported camera ids."); Log.d(TAG, "Listing supported camera ids.");
listSupportedCameraIds(); listSupportedCameraIds();
Config.ignoreSignal(Signal.SIGXCPU);
Config.setLogLevel(Level.AV_LOG_DEBUG);
} }
@Override @Override
@@ -145,11 +149,11 @@ public class MainActivity extends AppCompatActivity {
/** /**
* <p>Starts a new asynchronous FFmpeg operation with command provided. * <p>Starts a new asynchronous FFmpeg operation with command provided.
* *
* @param singleExecuteCallback callback function to receive result of this execution * @param ExecuteCallback callback function to receive result of this execution
* @param command FFmpeg command * @param command FFmpeg command
*/ */
public static void executeAsync(final SingleExecuteCallback singleExecuteCallback, final String command) { public static void executeAsync(final ExecuteCallback ExecuteCallback, final String command) {
final AsyncSingleFFmpegExecuteTask asyncCommandTask = new AsyncSingleFFmpegExecuteTask(command, singleExecuteCallback); final AsyncFFmpegExecuteTask asyncCommandTask = new AsyncFFmpegExecuteTask(command, ExecuteCallback);
asyncCommandTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); asyncCommandTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@@ -157,7 +161,7 @@ public class MainActivity extends AppCompatActivity {
handler.postDelayed(runnable, 250); handler.postDelayed(runnable, 250);
} }
public static void addUIAction(final Callable callable) { public static void addUIAction(final Callable<Object> callable) {
actionQueue.add(callable); actionQueue.add(callable);
} }
@@ -171,7 +175,7 @@ public class MainActivity extends AppCompatActivity {
final HashMap<String, String> fontNameMapping = new HashMap<>(); final HashMap<String, String> fontNameMapping = new HashMap<>();
fontNameMapping.put("MyFontName", "Doppio One"); fontNameMapping.put("MyFontName", "Doppio One");
Config.setFontDirectory(this, cacheDirectory.getAbsolutePath(), fontNameMapping); 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() { protected void listSupportedCameraIds() {
@@ -26,7 +26,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
public class PagerAdapter extends FragmentPagerAdapter { public class PagerAdapter extends FragmentPagerAdapter {
private static final int NUMBER_OF_TABS = 7; private static final int NUMBER_OF_TABS = 9;
private final Context context; private final Context context;
@@ -59,6 +59,12 @@ public class PagerAdapter extends FragmentPagerAdapter {
case 6: { case 6: {
return PipeTabFragment.newInstance(); return PipeTabFragment.newInstance();
} }
case 7: {
return ConcurrentExecutionTabFragment.newInstance();
}
case 8: {
return SafTabFragment.newInstance();
}
default: { default: {
return null; return null;
} }
@@ -94,6 +100,12 @@ public class PagerAdapter extends FragmentPagerAdapter {
case 6: { case 6: {
return context.getString(R.string.pipe_tab); return context.getString(R.string.pipe_tab);
} }
case 7: {
return context.getString(R.string.concurrent_tab);
}
case 8: {
return context.getString(R.string.saf_tab);
}
default: { default: {
return null; return null;
} }
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config; import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.LogCallback; import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage; import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.Statistics; import com.arthenica.mobileffmpeg.Statistics;
@@ -42,7 +43,6 @@ import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.AsyncCatImageTask; import com.arthenica.mobileffmpeg.util.AsyncCatImageTask;
import com.arthenica.mobileffmpeg.util.DialogUtil; import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil; import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions; import com.arthenica.smartexception.java.Exceptions;
import java.io.File; import java.io.File;
@@ -108,7 +108,7 @@ public class PipeTabFragment extends Fragment {
@Override @Override
public void apply(final Statistics newStatistics) { public void apply(final Statistics newStatistics) {
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -155,17 +155,17 @@ public class PipeTabFragment extends Fragment {
final String ffmpegCommand = Video.generateCreateVideoWithPipesScript(pipe1, pipe2, pipe3, videoFile.getAbsolutePath()); 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 @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideProgressDialog(); hideProgressDialog();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -174,7 +174,7 @@ public class PipeTabFragment extends Fragment {
playVideo(); playVideo();
} else { } else {
Popup.show(requireContext(), "Create failed. Please check log for the details."); 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; return null;
@@ -189,7 +189,7 @@ public class PipeTabFragment extends Fragment {
startAsyncCatImageProcess(image3File.getAbsolutePath(), pipe3); startAsyncCatImageProcess(image3File.getAbsolutePath(), pipe3);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, String.format("Create video failed %s", Exceptions.getStackTraceString(e))); Log.e(TAG, String.format("Create video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Create video failed"); Popup.show(requireContext(), "Create video failed");
} }
} }
@@ -226,7 +226,7 @@ public class PipeTabFragment extends Fragment {
Log.i(MainActivity.TAG, "Pipe Tab Activated"); Log.i(MainActivity.TAG, "Pipe Tab Activated");
enableLogCallback(); enableLogCallback();
enableStatisticsCallback(); enableStatisticsCallback();
Popup.show(requireContext(), Tooltip.PIPE_TEST_TOOLTIP_TEXT); Popup.show(requireContext(), getString(R.string.pipe_test_tooltip_text));
} }
protected void showProgressDialog() { protected void showProgressDialog() {
@@ -251,7 +251,7 @@ public class PipeTabFragment extends Fragment {
TextView textView = progressDialog.findViewById(R.id.progressDialogText); TextView textView = progressDialog.findViewById(R.id.progressDialogText);
if (textView != null) { if (textView != null) {
textView.setText(String.format("Creating video: %% %s", completePercentage)); textView.setText(String.format("Creating video: %% %s.", completePercentage));
} }
} }
} }
@@ -259,7 +259,7 @@ public class PipeTabFragment extends Fragment {
protected void hideProgressDialog() { protected void hideProgressDialog() {
progressDialog.dismiss(); progressDialog.dismiss();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -0,0 +1,420 @@
/*
* 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.test;
import android.app.AlertDialog;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.TextView;
import android.widget.VideoView;
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.FFprobe;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
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.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.concurrent.Callable;
import static android.app.Activity.RESULT_OK;
import static com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS;
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
public class SafTabFragment extends Fragment {
private TextView outputText;
private Uri inUri;
private Uri outUri;
private static final int REQUEST_SAF_FFPROBE = 11;
private static final int REQUEST_SAF_FFMPEG = 12;
private boolean backFromIntent = false;
private VideoView videoView;
private AlertDialog progressDialog;
private Statistics statistics;
private Button runFFmpegButton;
private Button runFFprobeButton;
public SafTabFragment() {
super(R.layout.fragment_saf_tab);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
runFFmpegButton = view.findViewById(R.id.runFFmpegButton);
runFFmpegButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
.setType("video/*")
.putExtra(Intent.EXTRA_TITLE, "video.mp4")
.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, REQUEST_SAF_FFMPEG);
}
});
runFFprobeButton = view.findViewById(R.id.runFFprobeButton);
runFFprobeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
.setType("*/*")
.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*", "audio/*"})
.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, REQUEST_SAF_FFPROBE);
}
});
outputText = view.findViewById(R.id.outputText);
outputText.setMovementMethod(new ScrollingMovementMethod());
videoView = view.findViewById(R.id.videoPlayerFrame);
progressDialog = DialogUtil.createProgressDialog(requireContext(), "Encoding video");
}
@Override
public void onResume() {
super.onResume();
setActive();
}
static SafTabFragment newInstance() {
return new SafTabFragment();
}
private void enableLogCallback() {
Config.enableLogCallback(new LogCallback() {
@Override
public void apply(final LogMessage message) {
MainActivity.addUIAction(new Callable() {
@Override
public Object call() {
appendLog(message.getText());
return null;
}
});
}
});
}
private void runFFprobe() {
videoView.setVisibility(View.GONE);
outputText.setVisibility(View.VISIBLE);
clearLog();
final String ffprobeCommand = "-hide_banner -print_format json -show_format -show_streams " + Config.getSafParameterForRead(getContext(), inUri);
Log.d(TAG, "Testing FFprobe COMMAND synchronously.");
Log.d(TAG, String.format("FFprobe process started with arguments\n\'%s\'", ffprobeCommand));
int result = FFprobe.execute(ffprobeCommand);
Log.d(TAG, String.format("FFprobe process exited with rc %d", result));
if (result != 0) {
Popup.show(requireContext(), "Command failed. Please check output for the details.");
}
inUri = null;
}
private void setActive() {
if (backFromIntent) {
backFromIntent = false;
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
Popup.show(requireContext(), "SAF is only available for Android 4.4 and above.");
runFFprobeButton.setEnabled(false);
runFFmpegButton.setEnabled(false);
outputText.setEnabled(false);
Log.i(TAG, "SAF Tab Dectivated");
return;
}
Log.i(TAG, "SAF Tab Activated");
enableLogCallback();
enableStatisticsCallback();
Popup.show(requireContext(), getString(R.string.saf_test_tooltip_text));
}
private void appendLog(final String logMessage) {
outputText.append(logMessage);
}
private void clearLog() {
outputText.setText("");
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
backFromIntent = true;
if (requestCode == REQUEST_SAF_FFPROBE && resultCode == RESULT_OK && data != null) {
inUri = data.getData();
MainActivity.handler.post(new Runnable() {
@Override
public void run() {
runFFprobe();
}
});
} else if (requestCode == REQUEST_SAF_FFMPEG && resultCode == MainActivity.RESULT_OK && data != null) {
outUri = data.getData();
MainActivity.handler.post(new Runnable() {
@Override
public void run() {
encodeVideo();
}
});
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private String getCodec(String videoPath) {
String extension = "mp4";
int pos = videoPath.lastIndexOf('.');
if (pos >= 0)
extension = videoPath.substring(pos+1);
switch (extension) {
case "webm":
return "vp8";
case "mkv":
return "aom";
case "ogv":
return "theora";
case "mov":
return "hap";
case "mp4":
default:
return "mpeg4";
}
}
private String getCustomOptions(String videoCodec) {
switch (videoCodec) {
case "x265":
return "-crf 28 -preset fast ";
case "vp8":
return "-b:v 1M -crf 10 ";
case "vp9":
return "-b:v 2M ";
case "aom":
return "-crf 30 -strict experimental ";
case "theora":
return "-qscale:v 7 ";
case "hap":
return "-format hap_q ";
default:
// kvazaar, mpeg4, x264, xvid
return "";
}
}
private void enableStatisticsCallback() {
Config.enableStatisticsCallback(new StatisticsCallback() {
@Override
public void apply(final Statistics newStatistics) {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
statistics = newStatistics;
updateProgressDialog();
return null;
}
});
throw new AndroidRuntimeException("I am test exception thrown by test application");
}
});
}
private void encodeVideo() {
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 String videoPath = Config.getSafParameterForWrite(requireContext(), outUri);
try {
// IF VIDEO IS PLAYING STOP PLAYBACK
videoView.stopPlayback();
videoView.setVisibility(View.GONE);
outputText.setVisibility(View.VISIBLE);
String selectedCodec = getCodec(videoPath);
Log.d(TAG, String.format("Testing VIDEO encoding with '%s' codec", selectedCodec));
showProgressDialog();
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(), videoPath, selectedCodec, getCustomOptions(selectedCodec));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
long executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
@Override
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:");
Config.printLastCommandOutput(Log.INFO);
hideProgressDialog();
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) {
Log.d(TAG, "Encode completed successfully; playing video.");
playVideo(outUri, new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
videoView.setVisibility(View.GONE);
outputText.setVisibility(View.VISIBLE);
}
});
} else {
Popup.show(requireContext(), "Encode failed. Please check log for the details.");
Log.d(TAG, String.format("Encode failed with rc=%d.", returnCode));
}
return null;
}
});
}
});
Log.d(TAG, String.format("Async FFmpeg process started with executionId %d.", executionId));
} catch (IOException e) {
Log.e(TAG, String.format("Encode video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode video failed");
}
}
private void playVideo(Uri videoUri, MediaPlayer.OnCompletionListener onCompletionListener) {
videoView.setVisibility(View.VISIBLE);
outputText.setVisibility(View.GONE);
MediaController mediaController = new MediaController(requireContext());
mediaController.setAnchorView(videoView);
videoView.setVideoURI(videoUri);
videoView.setMediaController(mediaController);
videoView.requestFocus();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
videoView.setBackgroundColor(0x00000000);
}
});
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
videoView.stopPlayback();
return false;
}
});
videoView.setOnCompletionListener(onCompletionListener);
videoView.start();
}
private void showProgressDialog() {
// CLEAN STATISTICS
statistics = null;
Config.resetStatistics();
progressDialog.show();
}
private void updateProgressDialog() {
if (statistics == null) {
return;
}
int timeInMilliseconds = this.statistics.getTime();
if (timeInMilliseconds > 0) {
int totalVideoDuration = 9000;
String completePercentage = new BigDecimal(timeInMilliseconds).multiply(new BigDecimal(100)).divide(new BigDecimal(totalVideoDuration), 0, BigDecimal.ROUND_HALF_UP).toString();
TextView textView = progressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Encoding video: %% %s.", completePercentage));
}
}
}
private void hideProgressDialog() {
progressDialog.dismiss();
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
progressDialog = DialogUtil.createProgressDialog(requireContext(), "Encoding video");
return null;
}
});
}
}
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config; import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.FFmpeg; import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.LogCallback; import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage; import com.arthenica.mobileffmpeg.LogMessage;
@@ -41,7 +42,6 @@ import com.arthenica.mobileffmpeg.Statistics;
import com.arthenica.mobileffmpeg.StatisticsCallback; import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.DialogUtil; import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil; import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions; import com.arthenica.smartexception.java.Exceptions;
import java.io.File; import java.io.File;
@@ -66,6 +66,7 @@ public class SubtitleTabFragment extends Fragment {
private AlertDialog burnProgressDialog; private AlertDialog burnProgressDialog;
private Statistics statistics; private Statistics statistics;
private State state; private State state;
private Long executionId;
public SubtitleTabFragment() { public SubtitleTabFragment() {
super(R.layout.fragment_subtitle_tab); super(R.layout.fragment_subtitle_tab);
@@ -158,23 +159,24 @@ public class SubtitleTabFragment extends Fragment {
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath(), "mpeg4", ""); 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; state = State.CREATING;
MainActivity.executeAsync(new SingleExecuteCallback() { executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
@Override @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideCreateProgressDialog(); hideCreateProgressDialog();
MainActivity.addUIAction(new Callable() { if (returnCode == RETURN_CODE_SUCCESS) {
@Override MainActivity.addUIAction(new Callable<Object>() {
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) { @Override
public Object call() {
Log.d(TAG, "Create completed successfully; burning subtitles."); Log.d(TAG, "Create completed successfully; burning subtitles.");
@@ -182,19 +184,19 @@ public class SubtitleTabFragment extends Fragment {
showBurnProgressDialog(); 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; state = State.BURNING;
MainActivity.executeAsync(new SingleExecuteCallback() { FFmpeg.executeAsync(burnSubtitlesCommand, new ExecuteCallback() {
@Override @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideBurnProgressDialog(); hideBurnProgressDialog();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -206,31 +208,26 @@ public class SubtitleTabFragment extends Fragment {
Log.e(TAG, "Burn subtitles operation cancelled"); Log.e(TAG, "Burn subtitles operation cancelled");
} else { } else {
Popup.show(requireContext(), "Burn subtitles failed. Please check log for the details."); 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; return null;
} }
}); });
} }
}, burnSubtitlesCommand); });
} else if (returnCode == RETURN_CODE_CANCEL) { return null;
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; }
}
});
} }
}, ffmpegCommand); });
Log.d(TAG, String.format("Async FFmpeg process started with executionId %d.", executionId));
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, String.format("Burn subtitles failed %s", Exceptions.getStackTraceString(e))); Log.e(TAG, String.format("Burn subtitles failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Burn subtitles failed"); Popup.show(requireContext(), "Burn subtitles failed");
} }
} }
@@ -275,7 +272,7 @@ public class SubtitleTabFragment extends Fragment {
Log.i(MainActivity.TAG, "Subtitle Tab Activated"); Log.i(MainActivity.TAG, "Subtitle Tab Activated");
enableLogCallback(); enableLogCallback();
enableStatisticsCallback(); enableStatisticsCallback();
Popup.show(requireContext(), Tooltip.SUBTITLE_TEST_TOOLTIP_TEXT); Popup.show(requireContext(), getString(R.string.subtitle_test_tooltip_text));
} }
protected void showCreateProgressDialog() { protected void showCreateProgressDialog() {
@@ -288,7 +285,10 @@ public class SubtitleTabFragment extends Fragment {
@Override @Override
public void onClick(View v) { 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(); createProgressDialog.show();
@@ -307,12 +307,12 @@ public class SubtitleTabFragment extends Fragment {
if (state == State.CREATING) { if (state == State.CREATING) {
TextView textView = createProgressDialog.findViewById(R.id.progressDialogText); TextView textView = createProgressDialog.findViewById(R.id.progressDialogText);
if (textView != null) { if (textView != null) {
textView.setText(String.format("Creating video: %% %s", completePercentage)); textView.setText(String.format("Creating video: %% %s.", completePercentage));
} }
} else if (state == State.BURNING) { } else if (state == State.BURNING) {
TextView textView = burnProgressDialog.findViewById(R.id.progressDialogText); TextView textView = burnProgressDialog.findViewById(R.id.progressDialogText);
if (textView != null) { if (textView != null) {
textView.setText(String.format("Burning subtitles: %% %s", completePercentage)); textView.setText(String.format("Burning subtitles: %% %s.", completePercentage));
} }
} }
@@ -1,45 +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.test;
public interface Tooltip {
// COMMAND TEST
String COMMAND_TEST_TOOLTIP_TEXT = "Enter an FFmpeg command without 'ffmpeg' at the beginning and click one of the RUN buttons";
// VIDEO TEST
String VIDEO_TEST_TOOLTIP_TEXT = "Select a video codec and press ENCODE button";
// HTTPS TEST
String HTTPS_TEST_TOOLTIP_TEXT = "Enter the https url of a media file and click the button";
// AUDIO TEST
String AUDIO_TEST_TOOLTIP_TEXT = "Select an audio codec and press ENCODE button";
// SUBTITLE TEST
String SUBTITLE_TEST_TOOLTIP_TEXT = "Click the button to burn subtitles. Created video will play inside the frame below";
// VID.STAB TEST
String VIDSTAB_TEST_TOOLTIP_TEXT = "Click the button to stabilize video. Original video will play above and stabilized video will play below";
// 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";
}
@@ -37,7 +37,7 @@ import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage; import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.util.DialogUtil; import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil; import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback; import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.smartexception.java.Exceptions; import com.arthenica.smartexception.java.Exceptions;
import java.io.File; import java.io.File;
@@ -132,17 +132,17 @@ public class VidStabTabFragment extends Fragment {
final String ffmpegCommand = Video.generateShakingVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath()); final String ffmpegCommand = Video.generateShakingVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), 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 @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideCreateProgressDialog(); hideCreateProgressDialog();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -154,28 +154,28 @@ public class VidStabTabFragment extends Fragment {
showStabilizeProgressDialog(); showStabilizeProgressDialog();
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 @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
if (returnCode == RETURN_CODE_SUCCESS) { 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()); final String stabilizeVideoCommand = String.format("-y -i %s -vf vidstabtransform=smoothing=30:input=%s -c:v mpeg4 %s", videoFile.getAbsolutePath(), shakeResultsFile.getAbsolutePath(), stabilizedVideoFile.getAbsolutePath());
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 @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideStabilizeProgressDialog(); hideStabilizeProgressDialog();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -185,7 +185,7 @@ public class VidStabTabFragment extends Fragment {
playStabilizedVideo(); playStabilizedVideo();
} else { } else {
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details."); Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
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; return null;
@@ -197,14 +197,14 @@ public class VidStabTabFragment extends Fragment {
} else { } else {
hideStabilizeProgressDialog(); hideStabilizeProgressDialog();
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details."); Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
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); }, analyzeVideoCommand);
} else { } else {
Popup.show(requireContext(), "Create video failed. Please check log for the details."); Popup.show(requireContext(), "Create video 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; return null;
@@ -214,7 +214,7 @@ public class VidStabTabFragment extends Fragment {
}, ffmpegCommand); }, ffmpegCommand);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, String.format("Stabilize video failed %s", Exceptions.getStackTraceString(e))); Log.e(TAG, String.format("Stabilize video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Stabilize video failed"); Popup.show(requireContext(), "Stabilize video failed");
} }
} }
@@ -282,7 +282,7 @@ public class VidStabTabFragment extends Fragment {
public void setActive() { public void setActive() {
Log.i(MainActivity.TAG, "VidStab Tab Activated"); Log.i(MainActivity.TAG, "VidStab Tab Activated");
enableLogCallback(); enableLogCallback();
Popup.show(requireContext(), Tooltip.VIDSTAB_TEST_TOOLTIP_TEXT); Popup.show(requireContext(), getString(R.string.vidstab_test_tooltip_text));
} }
protected void showCreateProgressDialog() { protected void showCreateProgressDialog() {
@@ -38,6 +38,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config; import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.FFmpeg; import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.LogCallback; import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage; import com.arthenica.mobileffmpeg.LogMessage;
@@ -45,7 +46,6 @@ import com.arthenica.mobileffmpeg.Statistics;
import com.arthenica.mobileffmpeg.StatisticsCallback; import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.DialogUtil; import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil; import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions; import com.arthenica.smartexception.java.Exceptions;
import java.io.File; import java.io.File;
@@ -122,7 +122,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
@Override @Override
public void apply(final Statistics newStatistics) { public void apply(final Statistics newStatistics) {
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -174,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()); 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 @Override
public void apply(final int returnCode, final String commandOutput) { public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode)); Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
Log.d(TAG, "FFmpeg process output:"); Log.d(TAG, "FFmpeg process output:");
@@ -188,7 +188,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
hideProgressDialog(); hideProgressDialog();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -197,17 +197,19 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
playVideo(); playVideo();
} else { } else {
Popup.show(requireContext(), "Encode failed. Please check log for the details."); 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; return null;
} }
}); });
} }
}, ffmpegCommand); });
Log.d(TAG, String.format("Async FFmpeg process started with executionId %d.", executionId));
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, String.format("Encode video failed %s", Exceptions.getStackTraceString(e))); Log.e(TAG, String.format("Encode video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode video failed"); Popup.show(requireContext(), "Encode video failed");
} }
} }
@@ -223,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()); 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); 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) { } catch (IOException e) {
Log.e(TAG, String.format("Encode webp failed %s", Exceptions.getStackTraceString(e))); Log.e(TAG, String.format("Encode webp failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode webp failed"); Popup.show(requireContext(), "Encode webp failed");
} }
} }
@@ -259,8 +261,6 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
} }
public String getSelectedVideoCodec() { public String getSelectedVideoCodec() {
// NOTE THAT MPEG4 CODEC IS ASSIGNED HERE
String videoCodec = selectedCodec; String videoCodec = selectedCodec;
// VIDEO CODEC SPINNER HAS BASIC NAMES, FFMPEG NEEDS LONGER AND EXACT CODEC NAMES. // VIDEO CODEC SPINNER HAS BASIC NAMES, FFMPEG NEEDS LONGER AND EXACT CODEC NAMES.
@@ -269,6 +269,9 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
case "x264": case "x264":
videoCodec = "libx264"; videoCodec = "libx264";
break; break;
case "openh264":
videoCodec = "libopenh264";
break;
case "x265": case "x265":
videoCodec = "libx265"; videoCodec = "libx265";
break; break;
@@ -351,7 +354,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
Log.i(MainActivity.TAG, "Video Tab Activated"); Log.i(MainActivity.TAG, "Video Tab Activated");
enableLogCallback(); enableLogCallback();
enableStatisticsCallback(); enableStatisticsCallback();
Popup.show(requireContext(), Tooltip.VIDEO_TEST_TOOLTIP_TEXT); Popup.show(requireContext(), getString(R.string.video_test_tooltip_text));
} }
protected void showProgressDialog() { protected void showProgressDialog() {
@@ -376,7 +379,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
TextView textView = progressDialog.findViewById(R.id.progressDialogText); TextView textView = progressDialog.findViewById(R.id.progressDialogText);
if (textView != null) { if (textView != null) {
textView.setText(String.format("Encoding video: %% %s", completePercentage)); textView.setText(String.format("Encoding video: %% %s.", completePercentage));
} }
} }
} }
@@ -384,7 +387,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
protected void hideProgressDialog() { protected void hideProgressDialog() {
progressDialog.dismiss(); progressDialog.dismiss();
MainActivity.addUIAction(new Callable() { MainActivity.addUIAction(new Callable<Object>() {
@Override @Override
public Object call() { public Object call() {
@@ -42,7 +42,7 @@
android:background="@drawable/dialog_button" android:background="@drawable/dialog_button"
android:fontFamily="sans-serif" android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/dialog_cancel_button_text" android:text="@string/cancel_button_text"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:textSize="16sp" android:textSize="16sp"
@@ -45,7 +45,7 @@
android:background="@drawable/rounded_button" android:background="@drawable/rounded_button"
android:fontFamily="sans-serif" android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/audio_encode_button_text" android:text="@string/encode_button_text"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="16sp" 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>
@@ -0,0 +1,71 @@
<?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:orientation="vertical"
android:gravity="center"
tools:context=".SafTabFragment">
<Button
android:id="@+id/runFFmpegButton"
android:layout_width="140dp"
android:layout_height="36dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/command_run_ffmpeg_button_text"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
tools:targetApi="jelly_bean" />
<Button
android:id="@+id/runFFprobeButton"
android:layout_width="140dp"
android:layout_height="36dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/command_run_ffprobe_button_text"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
tools:targetApi="jelly_bean" />
<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" />
<VideoView
android:visibility="gone"
android:id="@+id/videoPlayerFrame"
android:layout_width="wrap_content"
android:background="@drawable/rounded_video_frame"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:layout_margin="20dp"
android:layout_weight="1" />
</LinearLayout>
@@ -45,7 +45,7 @@
android:background="@drawable/rounded_button" android:background="@drawable/rounded_button"
android:fontFamily="sans-serif" android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/video_encode_button_text" android:text="@string/encode_button_text"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="16sp" android:textSize="16sp"
@@ -6,7 +6,8 @@
<item>mp3 (libshine)</item> <item>mp3 (libshine)</item>
<item>vorbis</item> <item>vorbis</item>
<item>opus</item> <item>opus</item>
<item>amr</item> <item>amr-nb</item>
<item>amr-wb</item>
<item>ilbc</item> <item>ilbc</item>
<item>soxr</item> <item>soxr</item>
<item>speex</item> <item>speex</item>
@@ -7,15 +7,23 @@
<string name="subtitle_tab">SUBTITLE</string> <string name="subtitle_tab">SUBTITLE</string>
<string name="vidstab_tab">VID.STAB</string> <string name="vidstab_tab">VID.STAB</string>
<string name="pipe_tab">PIPE</string> <string name="pipe_tab">PIPE</string>
<string name="concurrent_tab">CONCURRENT</string>
<string name="saf_tab">SAF</string>
<string name="command_text_input_placeholder">Enter command</string> <string name="command_text_input_placeholder">Enter command</string>
<string name="command_run_ffmpeg_button_text">RUN FFMPEG</string> <string name="command_run_ffmpeg_button_text">RUN FFMPEG</string>
<string name="command_run_ffprobe_button_text">RUN FFPROBE</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="video_create_button_text">CREATE</string>
<string name="https_get_info_button_text">GET INFO</string> <string name="https_get_info_button_text">GET INFO</string>
<string name="https_text_input_placeholder">Enter https url</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="subtitle_burn_subtitles_button_text">BURN SUBTITLES</string>
<string name="vidstab_stabilize_video_button_text">STABILIZE VIDEO</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> </resources>
@@ -0,0 +1,11 @@
<resources>
<string name="command_test_tooltip_text">Enter an FFmpeg command without \'ffmpeg\' at the beginning and click one of the RUN buttons</string>
<string name="video_test_tooltip_text">Select a video codec and press ENCODE button</string>
<string name="https_test_tooltip_text">Enter the https url of a media file and click the button</string>
<string name="audio_test_tooltip_text">Select an audio codec and press ENCODE button</string>
<string name="subtitle_test_tooltip_text">Click the button to burn subtitles. Created video will play inside the frame below</string>
<string name="vidstab_test_tooltip_text">Click the button to stabilize video. Original video will play above and stabilized video will play below</string>
<string name="pipe_test_tooltip_text">Click the button to create a video using pipe redirection. Created video will play inside the frame below</string>
<string name="concurrent_execution_test_tooltip_text">Use ENCODE and CANCEL buttons to start/stop multiple execution</string>
<string name="saf_test_tooltip_text">Use system file picker to test scoped storage extension</string>
</resources>
@@ -3,6 +3,7 @@
<string-array name="video_codec"> <string-array name="video_codec">
<item>mpeg4</item> <item>mpeg4</item>
<item>x264</item> <item>x264</item>
<item>openh264</item>
<item>x265</item> <item>x265</item>
<item>xvid</item> <item>xvid</item>
<item>vp8</item> <item>vp8</item>
+1 -1
View File
@@ -64,6 +64,6 @@ cmake -Wno-dev \
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY # CREATE PACKAGE CONFIG MANUALLY
create_chromaprint_package_config "1.4.3" create_chromaprint_package_config "1.5.0"
make install || exit 1 make install || exit 1
+53 -298
View File
@@ -1,20 +1,6 @@
#!/bin/bash #!/bin/bash
get_cpu_count() { source "${BASEDIR}/build/arch-common.sh"
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
}
get_library_name() { get_library_name() {
case $1 in case $1 in
@@ -51,19 +37,21 @@ get_library_name() {
30) echo "sdl" ;; 30) echo "sdl" ;;
31) echo "tesseract" ;; 31) echo "tesseract" ;;
32) echo "openh264" ;; 32) echo "openh264" ;;
33) echo "giflib" ;; 33) echo "vo-amrwbenc" ;;
34) echo "jpeg" ;; 34) echo "giflib" ;;
35) echo "libogg" ;; 35) echo "jpeg" ;;
36) echo "libpng" ;; 36) echo "libogg" ;;
37) echo "libuuid" ;; 37) echo "libpng" ;;
38) echo "nettle" ;; 38) echo "libuuid" ;;
39) echo "tiff" ;; 39) echo "nettle" ;;
40) echo "expat" ;; 40) echo "tiff" ;;
41) echo "libsndfile" ;; 41) echo "expat" ;;
42) echo "leptonica" ;; 42) echo "libsndfile" ;;
43) echo "libsamplerate" ;; 43) echo "leptonica" ;;
44) echo "android-zlib" ;; 44) echo "libsamplerate" ;;
45) echo "android-media-codec" ;; 45) echo "android-zlib" ;;
46) echo "android-media-codec" ;;
47) echo "cpu-features" ;;
esac esac
} }
@@ -153,11 +141,7 @@ get_target_build() {
echo "arm" echo "arm"
;; ;;
arm-v7a-neon) arm-v7a-neon)
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]]; then echo "arm/neon"
echo "arm/neon"
else
echo "arm"
fi
;; ;;
arm64-v8a) arm64-v8a)
echo "arm64" echo "arm64"
@@ -839,159 +823,60 @@ EOF
} }
create_cpufeatures_package_config() { create_cpufeatures_package_config() {
cat > "${INSTALL_PKG_CONFIG_DIR}/cpufeatures.pc" << EOF cat > "${INSTALL_PKG_CONFIG_DIR}/cpu-features.pc" << EOF
prefix=${ANDROID_NDK_ROOT}/sources/android/cpufeatures prefix=${BASEDIR}/prebuilt/android-$(get_target_build)/cpu-features
exec_prefix=\${prefix} exec_prefix=\${prefix}/bin
libdir=\${exec_prefix} libdir=\${prefix}/lib
includedir=\${prefix} includedir=\${prefix}/include/ndk_compat
Name: cpufeatures Name: cpufeatures
Description: cpu features Android utility URL: https://github.com/google/cpu_features
Description: cpu_features Android compatibility library
Version: 1.${API} Version: 1.${API}
Requires: Requires:
Libs: -L\${libdir} -lcpufeatures Libs: -L\${libdir} -lndk_compat
Cflags: -I\${includedir} Cflags: -I\${includedir}
EOF EOF
} }
# android_ndk_abi() { # to be used with CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake
# download <url> <local file name> <on error action> case ${ARCH} in
# arm-v7a | arm-v7a-neon)
download() { echo "armeabi-v7a"
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"
;; ;;
x264) arm64-v8a)
GPL_LIB_URL="https://code.videolan.org/videolan/x264/-/archive/296494a4011f58f32adc54304a2654627558c59a/x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2" echo "arm64-v8a"
GPL_LIB_FILE="x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2"
GPL_LIB_ORIG_DIR="x264-296494a4011f58f32adc54304a2654627558c59a"
GPL_LIB_DEST_DIR="x264"
;; ;;
x265) x86)
GPL_LIB_URL="https://bitbucket.org/multicoreware/x265/downloads/x265_3.3.tar.gz" echo "x86"
GPL_LIB_FILE="x265_3.3.tar.gz"
GPL_LIB_ORIG_DIR="x265_3.3"
GPL_LIB_DEST_DIR="x265"
;; ;;
xvidcore) x86-64)
GPL_LIB_URL="https://downloads.xvid.com/downloads/xvidcore-1.3.7.tar.gz" echo "x86_64"
GPL_LIB_FILE="xvidcore-1.3.7.tar.gz"
GPL_LIB_ORIG_DIR="xvidcore"
GPL_LIB_DEST_DIR="xvidcore"
;;
rubberband)
GPL_LIB_URL="https://breakfastquay.com/files/releases/rubberband-1.8.2.tar.bz2"
GPL_LIB_FILE="rubberband-1.8.2.tar.bz2"
GPL_LIB_ORIG_DIR="rubberband-1.8.2"
GPL_LIB_DEST_DIR="rubberband"
;; ;;
esac 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 android_ndk_cmake() {
echo -e "INFO: $1 already downloaded. Source folder found at ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1 local cmake=$(find ${ANDROID_HOME}/cmake -path \*/bin/cmake -type f -print -quit)
echo 0 if [[ -z ${cmake} ]]; then
return cmake=$(which cmake)
fi
if [[ -z ${cmake} ]]; then
cmake="missing_cmake"
fi fi
local GPL_LIB_PACKAGE_PATH="${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_FILE}" echo ${cmake} \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake \
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 -H${BASEDIR}/src/${LIB_NAME} \
-B$(android_build_dir) \
if [ ! -f "${GPL_LIB_PACKAGE_PATH}" ]; then -DANDROID_ABI=$(android_ndk_abi) \
echo -e "DEBUG: $1 library package not found. Downloading from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1 -DANDROID_PLATFORM=android-${API} \
-DCMAKE_INSTALL_PREFIX=${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME}
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
} }
set_toolchain_clang_paths() { set_toolchain_clang_paths() {
@@ -1033,30 +918,6 @@ set_toolchain_clang_paths() {
prepare_inline_sed 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"
BUILD_HOST=$(get_build_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
${BUILD_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() { build_android_lts_support() {
# CLEAN OLD BUILD # CLEAN OLD BUILD
@@ -1078,109 +939,3 @@ build_android_lts_support() {
$(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 $(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
${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 ${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
} }
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
}
+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
+33 -17
View File
@@ -42,41 +42,41 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
TARGET_CPU="" TARGET_CPU=""
TARGET_ARCH="" TARGET_ARCH=""
ASM_FLAGS="" ARCH_OPTIONS=""
case ${ARCH} in case ${ARCH} in
arm-v7a) arm-v7a)
TARGET_CPU="armv7-a" TARGET_CPU="armv7-a"
TARGET_ARCH="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) arm-v7a-neon)
TARGET_CPU="armv7-a" TARGET_CPU="armv7-a"
TARGET_ARCH="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) arm64-v8a)
TARGET_CPU="armv8-a" TARGET_CPU="armv8-a"
TARGET_ARCH="aarch64" TARGET_ARCH="aarch64"
ASM_FLAGS=" --enable-neon --enable-asm --enable-inline-asm" ARCH_OPTIONS=" --enable-neon --enable-asm --enable-inline-asm"
;; ;;
x86) x86)
TARGET_CPU="i686" TARGET_CPU="i686"
TARGET_ARCH="i686" TARGET_ARCH="i686"
# asm disabled due to this ticker https://trac.ffmpeg.org/ticket/4928 # 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) x86-64)
TARGET_CPU="x86_64" TARGET_CPU="x86_64"
TARGET_ARCH="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 esac
CONFIGURE_POSTFIX="" CONFIGURE_POSTFIX=""
HIGH_PRIORITY_INCLUDES="" HIGH_PRIORITY_INCLUDES=""
for library in {1..46} for library in {1..49}
do do
if [[ ${!library} -eq 1 ]]; then if [[ ${!library} -eq 1 ]]; then
ENABLED_LIBRARY=$(get_library_name $((library - 1))) ENABLED_LIBRARY=$(get_library_name $((library - 1)))
@@ -163,7 +163,7 @@ do
libvpx) libvpx)
CFLAGS+=" $(pkg-config --cflags vpx)" CFLAGS+=" $(pkg-config --cflags vpx)"
LDFLAGS+=" $(pkg-config --libs vpx)" LDFLAGS+=" $(pkg-config --libs vpx)"
LDFLAGS+=" $(pkg-config --libs --static cpufeatures)" LDFLAGS+=" $(pkg-config --libs cpu-features)"
CONFIGURE_POSTFIX+=" --enable-libvpx" CONFIGURE_POSTFIX+=" --enable-libvpx"
;; ;;
libwebp) libwebp)
@@ -178,11 +178,8 @@ do
;; ;;
opencore-amr) opencore-amr)
CFLAGS+=" $(pkg-config --cflags opencore-amrnb)" 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-amrnb)"
LDFLAGS+=" $(pkg-config --libs --static opencore-amrwb)"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrnb" CONFIGURE_POSTFIX+=" --enable-libopencore-amrnb"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrwb"
;; ;;
openh264) openh264)
FFMPEG_CFLAGS+=" $(pkg-config --cflags openh264)" FFMPEG_CFLAGS+=" $(pkg-config --cflags openh264)"
@@ -236,6 +233,11 @@ do
LDFLAGS+=" $(pkg-config --libs --static twolame)" LDFLAGS+=" $(pkg-config --libs --static twolame)"
CONFIGURE_POSTFIX+=" --enable-libtwolame" 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) wavpack)
CFLAGS+=" $(pkg-config --cflags wavpack)" CFLAGS+=" $(pkg-config --cflags wavpack)"
LDFLAGS+=" $(pkg-config --libs --static wavpack)" LDFLAGS+=" $(pkg-config --libs --static wavpack)"
@@ -285,14 +287,14 @@ do
;; ;;
android-media-codec) android-media-codec)
CONFIGURE_POSTFIX+=" --enable-mediacodec" CONFIGURE_POSTFIX+=" --enable-mediacodec"
;;
esac esac
else else
# THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT # THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT
# NOTE THAT IDS MUST BE +1 OF THE INDEX VALUE
if [[ ${library} -eq 31 ]]; then if [[ ${library} -eq 31 ]]; then
CONFIGURE_POSTFIX+=" --disable-sdl2" CONFIGURE_POSTFIX+=" --disable-sdl2"
elif [[ ${library} -eq 45 ]]; then elif [[ ${library} -eq 46 ]]; then
CONFIGURE_POSTFIX+=" --disable-zlib" CONFIGURE_POSTFIX+=" --disable-zlib"
fi fi
fi fi
@@ -322,13 +324,19 @@ if [[ -z ${MOBILE_FFMPEG_DEBUG} ]]; then
DEBUG_OPTIONS="--disable-debug --disable-lto"; DEBUG_OPTIONS="--disable-debug --disable-lto";
fi fi
else 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 fi
cd ${BASEDIR}/src/${LIB_NAME} || exit 1 cd ${BASEDIR}/src/${LIB_NAME} || exit 1
echo -n -e "\n${LIB_NAME}: "
if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then
echo -e "INFO: Cleaning workspace for ${LIB_NAME}" 1>>${BASEDIR}/build.log 2>&1 echo -e "INFO: Cleaning workspace for ${LIB_NAME}" 1>>${BASEDIR}/build.log 2>&1
make distclean 2>/dev/null 1>/dev/null make distclean 2>/dev/null 1>/dev/null
@@ -341,6 +349,13 @@ export LDFLAGS="${LDFLAGS}"
# USE HIGHER LIMITS FOR FFMPEG LINKING # USE HIGHER LIMITS FOR FFMPEG LINKING
ulimit -n 2048 1>>${BASEDIR}/build.log 2>&1 ulimit -n 2048 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 \ ./configure \
--cross-prefix="${BUILD_HOST}-" \ --cross-prefix="${BUILD_HOST}-" \
--sysroot="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot" \ --sysroot="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot" \
@@ -351,8 +366,9 @@ ulimit -n 2048 1>>${BASEDIR}/build.log 2>&1
--cpu="${TARGET_CPU}" \ --cpu="${TARGET_CPU}" \
--cc="${CC}" \ --cc="${CC}" \
--cxx="${CXX}" \ --cxx="${CXX}" \
--extra-libs="$(pkg-config --libs --static cpu-features)" \
--target-os=android \ --target-os=android \
${ASM_FLAGS} \ ${ARCH_OPTIONS} \
--enable-cross-compile \ --enable-cross-compile \
--enable-pic \ --enable-pic \
--enable-jni \ --enable-jni \
+1 -1
View File
@@ -63,6 +63,6 @@ export LIBPNG_LIBS="-L${BASEDIR}/prebuilt/android-$(get_target_build)/libpng/lib
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY # CREATE PACKAGE CONFIG MANUALLY
create_freetype_package_config "23.1.17" create_freetype_package_config "23.2.17"
make install || exit 1 make install || exit 1
+2 -6
View File
@@ -36,14 +36,10 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
cd ${BASEDIR}/src/${LIB_NAME} || exit 1 cd ${BASEDIR}/src/${LIB_NAME} || exit 1
./autogen.sh || exit 1
make distclean 2>/dev/null 1>/dev/null make distclean 2>/dev/null 1>/dev/null
# RECONFIGURE IF REQUESTED # ALWAYS RECONFIGURE
if [[ ${RECONF_kvazaar} -eq 1 ]]; then autoreconf_library ${LIB_NAME}
autoreconf_library ${LIB_NAME}
fi
# LINKING WITH ANDROID LTS SUPPORT LIBRARY IS NECESSARY FOR API < 18 # LINKING WITH ANDROID LTS SUPPORT LIBRARY IS NECESSARY FOR API < 18
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${API} < 18 ]]; then if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${API} < 18 ]]; then
+3 -3
View File
@@ -38,6 +38,9 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null 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 # RECONFIGURE IF REQUESTED
if [[ ${RECONF_libsamplerate} -eq 1 ]]; then if [[ ${RECONF_libsamplerate} -eq 1 ]]; then
autoreconf_library ${LIB_NAME} autoreconf_library ${LIB_NAME}
@@ -53,9 +56,6 @@ fi
--disable-fast-install \ --disable-fast-install \
--host=${BUILD_HOST} || exit 1 --host=${BUILD_HOST} || exit 1
# DISABLE building of examples manually
${SED_INLINE} 's/examples tests//g' ${BASEDIR}/src/${LIB_NAME}/Makefile
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# MANUALLY COPY PKG-CONFIG FILES # MANUALLY COPY PKG-CONFIG FILES
+1 -1
View File
@@ -60,6 +60,6 @@ PKG_CONFIG= ./configure \
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY # CREATE PACKAGE CONFIG MANUALLY
create_libvorbis_package_config "1.3.6" create_libvorbis_package_config "1.3.7"
make install || exit 1 make install || exit 1
+1 -1
View File
@@ -30,7 +30,7 @@ set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS # PREPARING FLAGS
export CFLAGS="$(get_cflags ${LIB_NAME}) -I${ANDROID_NDK_ROOT}/sources/android/cpufeatures" export CFLAGS="$(get_cflags ${LIB_NAME}) -I${ANDROID_NDK_ROOT}/sources/android/cpufeatures"
export CXXFLAGS=$(get_cxxflags ${LIB_NAME}) export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS="$(get_ldflags ${LIB_NAME}) -L${ANDROID_NDK_ROOT}/sources/android/cpufeatures -lcpufeatures" export LDFLAGS="$(get_ldflags ${LIB_NAME})"
# RECOVER configure.sh # RECOVER configure.sh
rm -f ${BASEDIR}/src/${LIB_NAME}/build/make/configure.sh rm -f ${BASEDIR}/src/${LIB_NAME}/build/make/configure.sh
-1
View File
@@ -55,7 +55,6 @@ fi
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# MANUALLY COPY PKG-CONFIG FILES # MANUALLY COPY PKG-CONFIG FILES
cp amrwb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
cp amrnb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1 cp amrnb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
make install || exit 1 make install || exit 1
+8 -5
View File
@@ -35,17 +35,16 @@ LDFLAGS=$(get_ldflags ${LIB_NAME})
case ${ARCH} in case ${ARCH} in
arm-v7a-neon) arm-v7a-neon)
ASM_ARCH=arm ASM_ARCH=arm
# Enabling NEON causes undefined symbol error for WelsCopy8x8_neon CFLAGS+=" -DHAVE_NEON -DANDROID_NDK"
# CFLAGS+=" -DHAVE_NEON"
;; ;;
arm64-v8a) arm64-v8a)
ASM_ARCH=arm64 ASM_ARCH=arm64
CFLAGS+=" -DHAVE_NEON_AARCH64" CFLAGS+=" -DHAVE_NEON_AARCH64 -DANDROID_NDK"
;; ;;
x86*) x86*)
ASM_ARCH=x86 ASM_ARCH=x86
CFLAGS+=" -DHAVE_AVX2" CFLAGS+=" -DHAVE_AVX2 -DANDROID_NDK"
;; ;;
esac esac
@@ -57,6 +56,10 @@ make clean 2>/dev/null 1>/dev/null
git checkout ${BASEDIR}/src/${LIB_NAME}/build 1>>${BASEDIR}/build.log 2>&1 git checkout ${BASEDIR}/src/${LIB_NAME}/build 1>>${BASEDIR}/build.log 2>&1
git checkout ${BASEDIR}/src/${LIB_NAME}/codec 1>>${BASEDIR}/build.log 2>&1 git checkout ${BASEDIR}/src/${LIB_NAME}/codec 1>>${BASEDIR}/build.log 2>&1
# comment out the piece that compiles cpu-features into libopenh264.a
${SED_INLINE} 's/^COMMON_OBJS +=/# COMMON_OBJS +=/' ${BASEDIR}/src/${LIB_NAME}/build/platform-android.mk
${SED_INLINE} 's/^COMMON_CFLAGS +=/# COMMON_CFLAGS +=/' ${BASEDIR}/src/${LIB_NAME}/build/platform-android.mk
make -j$(get_cpu_count) \ make -j$(get_cpu_count) \
ARCH="$(get_toolchain_arch)" \ ARCH="$(get_toolchain_arch)" \
CC="$CC" \ CC="$CC" \
+68
View File
@@ -0,0 +1,68 @@
#!/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="vo-amrwbenc"
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})
ARCH_OPTIONS=""
case ${ARCH} in
arm-v7a-neon)
ARCH_OPTIONS="--enable-armv7neon"
;;
esac
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_vo_amrwbenc} -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 \
${ARCH_OPTIONS} \
--enable-static \
--disable-shared \
--disable-fast-install \
--disable-maintainer-mode \
--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
+1 -1
View File
@@ -94,6 +94,6 @@ cmake -Wno-dev \
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY # CREATE PACKAGE CONFIG MANUALLY
create_x265_package_config "3.3" create_x265_package_config "3.4"
make install || exit 1 make install || exit 1
+452
View File
@@ -0,0 +1,452 @@
#!/bin/bash
export MOBILE_FFMPEG_TMPDIR="${BASEDIR}/.tmp"
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
}
#
# 1. <repo url>
# 2. <local folder path>
# 3. <commit id>
#
clone_git_repository_with_commit_id() {
local RC
(mkdir -p $2 1>>${BASEDIR}/build.log 2>&1)
RC=$?
if [ ${RC} -ne 0 ]; then
echo -e "\nDEBUG: Failed to create local directory $2\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
echo ${RC}
return
fi
(git clone $1 $2 --depth 1 1>>${BASEDIR}/build.log 2>&1)
RC=$?
if [ ${RC} -ne 0 ]; then
echo -e "\nDEBUG: Failed to clone $1\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
echo ${RC}
return
fi
cd $2 1>>${BASEDIR}/build.log 2>&1
RC=$?
if [ ${RC} -ne 0 ]; then
echo -e "\nDEBUG: Failed to cd into $2\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
echo ${RC}
return
fi
(git fetch --depth 1 origin $3 1>>${BASEDIR}/build.log 2>&1)
RC=$?
if [ ${RC} -ne 0 ]; then
echo -e "\nDEBUG: Failed to fetch commit id $3 from $1\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
echo ${RC}
return
fi
(git checkout $3 1>>${BASEDIR}/build.log 2>&1)
RC=$?
if [ ${RC} -ne 0 ]; then
echo -e "\nDEBUG: Failed to checkout commit id $3 from $1\n" 1>>${BASEDIR}/build.log 2>&1
echo ${RC}
return
fi
echo ${RC}
}
#
# 1. <repo url>
# 2. <tag name>
# 3. <local folder path>
#
clone_git_repository_with_tag() {
local RC
(mkdir -p $3 1>>${BASEDIR}/build.log 2>&1)
RC=$?
if [ ${RC} -ne 0 ]; then
echo -e "\nDEBUG: Failed to create local directory $3\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf $3 1>>${BASEDIR}/build.log 2>&1
echo ${RC}
return
fi
(git clone --depth 1 --branch $2 $1 $3 1>>${BASEDIR}/build.log 2>&1)
RC=$?
if [ ${RC} -ne 0 ]; then
echo -e "\nDEBUG: Failed to clone $1 -> $2\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf $3 1>>${BASEDIR}/build.log 2>&1
echo ${RC}
return
fi
echo ${RC}
}
#
# 1. <url>
# 2. <local file name>
# 3. <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_library_source() {
local LIB_REPO_URL=""
local LIB_NAME="$1"
local LIB_LOCAL_PATH=${BASEDIR}/src/${LIB_NAME}
local SOURCE_ID=""
local LIBRARY_RC=""
local DOWNLOAD_RC=""
local SOURCE_TYPE=""
echo -e "\nDEBUG: Downloading library source: $1\n" 1>>${BASEDIR}/build.log 2>&1
case $1 in
cpu-features)
LIB_REPO_URL="https://github.com/tanersener/cpu_features"
SOURCE_ID="v0.4.1.1" # TAG
SOURCE_TYPE="TAG"
;;
ffmpeg)
LIB_REPO_URL="https://github.com/tanersener/FFmpeg"
SOURCE_ID="d222da435e63a2665b85c0305ad2cf8a07b1af6d" # COMMIT -> v4.4-dev-416
SOURCE_TYPE="COMMIT"
;;
esac
LIBRARY_RC=$(library_is_downloaded "${LIB_NAME}")
if [ ${LIBRARY_RC} -eq 0 ]; then
echo -e "INFO: $1 already downloaded. Source folder found at ${LIB_LOCAL_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
echo 0
return
fi
if [ ${SOURCE_TYPE} == "TAG" ]; then
DOWNLOAD_RC=$(clone_git_repository_with_tag "${LIB_REPO_URL}" "${SOURCE_ID}" "${LIB_LOCAL_PATH}")
else
DOWNLOAD_RC=$(clone_git_repository_with_commit_id "${LIB_REPO_URL}" "${LIB_LOCAL_PATH}" "${SOURCE_ID}")
fi
if [ ${DOWNLOAD_RC} -ne 0 ]; then
echo -e "INFO: Downloading library $1 failed. Can not get library from ${LIB_REPO_URL}\n" 1>>${BASEDIR}/build.log 2>&1
echo ${DOWNLOAD_RC}
else
echo -e "DEBUG: $1 library downloaded\n" 1>>${BASEDIR}/build.log 2>&1
fi
}
download_gpl_library_source() {
local GPL_LIB_URL=""
local GPL_LIB_FILE=""
local GPL_LIB_ORIG_DIR=""
local GPL_LIB_DEST_DIR="$1"
local GPL_LIB_SOURCE_PATH="${BASEDIR}/src/${GPL_LIB_DEST_DIR}"
local LIBRARY_RC=""
local DOWNLOAD_RC=""
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"
;;
x264)
GPL_LIB_URL="https://code.videolan.org/videolan/x264/-/archive/cde9a93319bea766a92e306d69059c76de970190/x264-cde9a93319bea766a92e306d69059c76de970190.tar.bz2"
GPL_LIB_FILE="x264-cde9a93319bea766a92e306d69059c76de970190.tar.bz2"
GPL_LIB_ORIG_DIR="x264-cde9a93319bea766a92e306d69059c76de970190"
;;
x265)
GPL_LIB_URL="https://github.com/videolan/x265/archive/3.4.tar.gz"
GPL_LIB_FILE="x265_3.4.tar.gz"
GPL_LIB_ORIG_DIR="x265-3.4"
;;
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"
;;
rubberband)
GPL_LIB_URL="https://breakfastquay.com/files/releases/rubberband-1.8.2.tar.bz2"
GPL_LIB_FILE="rubberband-1.8.2.tar.bz2"
GPL_LIB_ORIG_DIR="rubberband-1.8.2"
;;
esac
LIBRARY_RC=$(library_is_downloaded "${GPL_LIB_DEST_DIR}")
if [ ${LIBRARY_RC} -eq 0 ]; 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
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
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
}
get_cpu_count() {
if [ "$(uname)" == "Darwin" ]; then
echo $(sysctl -n hw.physicalcpu)
else
echo $(nproc)
fi
}
#
# 1. <lib name>
#
library_is_downloaded() {
local LOCAL_PATH
local LIB_NAME=$1
local FILE_COUNT
local REDOWNLOAD_VARIABLE
REDOWNLOAD_VARIABLE=$(echo "REDOWNLOAD_$1" | sed "s/\-/\_/g")
LOCAL_PATH=${BASEDIR}/src/${LIB_NAME}
echo -e "DEBUG: Checking if ${LIB_NAME} is already downloaded at ${LOCAL_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
if [ ! -d ${LOCAL_PATH} ]; then
echo -e "DEBUG: ${LOCAL_PATH} directory not found\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
FILE_COUNT=$(ls -l ${LOCAL_PATH} | wc -l)
if [[ ${FILE_COUNT} -eq 0 ]]; then
echo -e "DEBUG: No files found under ${LOCAL_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
if [[ ${REDOWNLOAD_VARIABLE} -eq 1 ]]; then
echo -e "INFO: ${LIB_NAME} library already downloaded but re-download requested\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf ${LOCAL_PATH} 1>>${BASEDIR}/build.log 2>&1
echo 1
else
echo -e "INFO: ${LIB_NAME} library already downloaded\n" 1>>${BASEDIR}/build.log 2>&1
echo 0
fi
}
library_is_installed() {
local INSTALL_PATH=$1
local LIB_NAME=$2
local HEADER_COUNT
local LIB_COUNT
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
HEADER_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/include | wc -l)
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
}
prepare_inline_sed() {
if [ "$(uname)" == "Darwin" ]; then
export SED_INLINE="sed -i .tmp"
else
export SED_INLINE="sed -i"
fi
}
to_capital_case() {
echo "$(echo ${1:0:1} | tr '[a-z]' '[A-Z]')${1:1}"
}
+27 -290
View File
@@ -1,20 +1,6 @@
#!/bin/bash #!/bin/bash
get_cpu_count() { source "${BASEDIR}/build/arch-common.sh"
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
}
get_library_name() { get_library_name() {
case $1 in case $1 in
@@ -50,19 +36,19 @@ get_library_name() {
29) echo "sdl" ;; 29) echo "sdl" ;;
30) echo "tesseract" ;; 30) echo "tesseract" ;;
31) echo "openh264" ;; 31) echo "openh264" ;;
32) echo "giflib" ;; 32) echo "vo-amrwbenc" ;;
33) echo "jpeg" ;; 33) echo "giflib" ;;
34) echo "libogg" ;; 34) echo "jpeg" ;;
35) echo "libpng" ;; 35) echo "libogg" ;;
36) echo "nettle" ;; 36) echo "libpng" ;;
37) echo "tiff" ;; 37) echo "nettle" ;;
38) echo "expat" ;; 38) echo "tiff" ;;
39) echo "libsndfile" ;; 39) echo "expat" ;;
40) echo "leptonica" ;; 40) echo "libsndfile" ;;
41) echo "libsamplerate" ;; 41) echo "leptonica" ;;
42) echo "ios-zlib" ;; 42) echo "libsamplerate" ;;
43) echo "ios-audiotoolbox" ;; 43) echo "ios-zlib" ;;
44) echo "ios-coreimage" ;; 44) echo "ios-audiotoolbox" ;;
45) echo "ios-bzip2" ;; 45) echo "ios-bzip2" ;;
46) echo "ios-videotoolbox" ;; 46) echo "ios-videotoolbox" ;;
47) echo "ios-avfoundation" ;; 47) echo "ios-avfoundation" ;;
@@ -84,21 +70,17 @@ get_package_config_file_name() {
26) echo "aom" ;; 26) echo "aom" ;;
27) echo "libchromaprint" ;; 27) echo "libchromaprint" ;;
29) echo "sdl2" ;; 29) echo "sdl2" ;;
33) echo "libjpeg" ;; 34) echo "libjpeg" ;;
34) echo "ogg" ;; 35) echo "ogg" ;;
37) echo "libtiff-4" ;; 38) echo "libtiff-4" ;;
39) echo "sndfile" ;; 40) echo "sndfile" ;;
40) echo "lept" ;; 41) echo "lept" ;;
41) echo "samplerate" ;; 42) echo "samplerate" ;;
49) echo "uuid" ;; 50) echo "uuid" ;;
*) echo $(get_library_name $1) *) echo $(get_library_name $1)
esac esac
} }
to_capital_case() {
echo "$(echo ${1:0:1} | tr '[a-z]' '[A-Z]')${1:1}"
}
get_static_archive_name() { get_static_archive_name() {
case $1 in case $1 in
5) echo "libmp3lame.a" ;; 5) echo "libmp3lame.a" ;;
@@ -111,12 +93,12 @@ get_static_archive_name() {
28) echo "libtwolame.a" ;; 28) echo "libtwolame.a" ;;
29) echo "libSDL2.a" ;; 29) echo "libSDL2.a" ;;
30) echo "libtesseract.a" ;; 30) echo "libtesseract.a" ;;
32) echo "libgif.a" ;; 33) echo "libgif.a" ;;
34) echo "libogg.a" ;; 35) echo "libogg.a" ;;
35) echo "libpng.a" ;; 36) echo "libpng.a" ;;
39) echo "libsndfile.a" ;; 40) echo "libsndfile.a" ;;
40) echo "liblept.a" ;; 41) echo "liblept.a" ;;
41) echo "libsamplerate.a" ;; 42) echo "libsamplerate.a" ;;
*) echo lib$(get_library_name $1).a *) echo lib$(get_library_name $1).a
esac esac
} }
@@ -874,145 +856,6 @@ Cflags: -I\${includedir}
EOF 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"
;;
x264)
GPL_LIB_URL="https://code.videolan.org/videolan/x264/-/archive/296494a4011f58f32adc54304a2654627558c59a/x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2"
GPL_LIB_FILE="x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2"
GPL_LIB_ORIG_DIR="x264-296494a4011f58f32adc54304a2654627558c59a"
GPL_LIB_DEST_DIR="x264"
;;
x265)
GPL_LIB_URL="https://bitbucket.org/multicoreware/x265/downloads/x265_3.3.tar.gz"
GPL_LIB_FILE="x265_3.3.tar.gz"
GPL_LIB_ORIG_DIR="x265_3.3"
GPL_LIB_DEST_DIR="x265"
;;
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"
;;
rubberband)
GPL_LIB_URL="https://breakfastquay.com/files/releases/rubberband-1.8.2.tar.bz2"
GPL_LIB_FILE="rubberband-1.8.2.tar.bz2"
GPL_LIB_ORIG_DIR="rubberband-1.8.2"
GPL_LIB_DEST_DIR="rubberband"
;;
esac
local GPL_LIB_SOURCE_PATH="${BASEDIR}/src/${GPL_LIB_DEST_DIR}"
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
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
}
set_toolchain_clang_paths() { set_toolchain_clang_paths() {
if [ ! -f "${MOBILE_FFMPEG_TMPDIR}/gas-preprocessor.pl" ]; then if [ ! -f "${MOBILE_FFMPEG_TMPDIR}/gas-preprocessor.pl" ]; then
DOWNLOAD_RESULT=$(download "https://github.com/libav/gas-preprocessor/raw/master/gas-preprocessor.pl" "gas-preprocessor.pl" "exit") DOWNLOAD_RESULT=$(download "https://github.com/libav/gas-preprocessor/raw/master/gas-preprocessor.pl" "gas-preprocessor.pl" "exit")
@@ -1093,109 +936,3 @@ set_toolchain_clang_paths() {
prepare_inline_sed prepare_inline_sed
} }
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
}
+27 -32
View File
@@ -94,16 +94,8 @@ case ${ARCH} in
;; ;;
esac esac
if [[ ${APPLE_TVOS_BUILD} -eq 1 ]]; then
CONFIGURE_POSTFIX="--disable-avfoundation"
LIBRARY_COUNT=49
else
CONFIGURE_POSTFIX=""
LIBRARY_COUNT=50
fi
library=1 library=1
while [[ ${library} -le ${LIBRARY_COUNT} ]] while [[ ${library} -le 49 ]]
do do
if [[ ${!library} -eq 1 ]]; then if [[ ${!library} -eq 1 ]]; then
ENABLED_LIBRARY=$(get_library_name $((library - 1))) ENABLED_LIBRARY=$(get_library_name $((library - 1)))
@@ -198,11 +190,8 @@ do
;; ;;
opencore-amr) opencore-amr)
FFMPEG_CFLAGS+=" $(pkg-config --cflags opencore-amrnb)" FFMPEG_CFLAGS+=" $(pkg-config --cflags opencore-amrnb)"
FFMPEG_CFLAGS+=" $(pkg-config --cflags opencore-amrwb)"
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static opencore-amrnb)" FFMPEG_LDFLAGS+=" $(pkg-config --libs --static opencore-amrnb)"
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static opencore-amrwb)"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrnb" CONFIGURE_POSTFIX+=" --enable-libopencore-amrnb"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrwb"
;; ;;
openh264) openh264)
FFMPEG_CFLAGS+=" $(pkg-config --cflags openh264)" FFMPEG_CFLAGS+=" $(pkg-config --cflags openh264)"
@@ -257,6 +246,11 @@ do
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static twolame)" FFMPEG_LDFLAGS+=" $(pkg-config --libs --static twolame)"
CONFIGURE_POSTFIX+=" --enable-libtwolame" CONFIGURE_POSTFIX+=" --enable-libtwolame"
;; ;;
vo-amrwbenc)
FFMPEG_CFLAGS+=" $(pkg-config --cflags vo-amrwbenc)"
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static vo-amrwbenc)"
CONFIGURE_POSTFIX+=" --enable-libvo-amrwbenc"
;;
wavpack) wavpack)
FFMPEG_CFLAGS+=" $(pkg-config --cflags wavpack)" FFMPEG_CFLAGS+=" $(pkg-config --cflags wavpack)"
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static wavpack)" FFMPEG_LDFLAGS+=" $(pkg-config --libs --static wavpack)"
@@ -312,9 +306,6 @@ do
*-bzip2) *-bzip2)
CONFIGURE_POSTFIX+=" --enable-bzlib" CONFIGURE_POSTFIX+=" --enable-bzlib"
;; ;;
*-coreimage)
CONFIGURE_POSTFIX+=" --enable-coreimage"
;;
*-videotoolbox) *-videotoolbox)
CONFIGURE_POSTFIX+=" --enable-videotoolbox" CONFIGURE_POSTFIX+=" --enable-videotoolbox"
;; ;;
@@ -330,14 +321,13 @@ do
else else
# THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT # THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT
# NOTE THAT IDS MUST BE +1 OF THE INDEX VALUE
if [[ ${library} -eq 30 ]]; then if [[ ${library} -eq 30 ]]; then
CONFIGURE_POSTFIX+=" --disable-sdl2" CONFIGURE_POSTFIX+=" --disable-sdl2"
elif [[ ${library} -eq 43 ]]; then
CONFIGURE_POSTFIX+=" --disable-zlib"
elif [[ ${library} -eq 44 ]]; then elif [[ ${library} -eq 44 ]]; then
CONFIGURE_POSTFIX+=" --disable-audiotoolbox" CONFIGURE_POSTFIX+=" --disable-zlib"
elif [[ ${library} -eq 45 ]]; then elif [[ ${library} -eq 45 ]]; then
CONFIGURE_POSTFIX+=" --disable-coreimage" CONFIGURE_POSTFIX+=" --disable-audiotoolbox"
elif [[ ${library} -eq 46 ]]; then elif [[ ${library} -eq 46 ]]; then
CONFIGURE_POSTFIX+=" --disable-bzlib" CONFIGURE_POSTFIX+=" --disable-bzlib"
elif [[ ${library} -eq 47 ]]; then elif [[ ${library} -eq 47 ]]; then
@@ -366,7 +356,7 @@ fi
if [[ -z ${MOBILE_FFMPEG_DEBUG} ]]; then if [[ -z ${MOBILE_FFMPEG_DEBUG} ]]; then
DEBUG_OPTIONS="--disable-debug"; DEBUG_OPTIONS="--disable-debug";
else else
DEBUG_OPTIONS="--enable-debug"; DEBUG_OPTIONS="--enable-debug --disable-stripping";
fi fi
# CFLAGS PARTS # CFLAGS PARTS
@@ -396,27 +386,37 @@ export CFLAGS="${ARCH_CFLAGS} ${APP_CFLAGS} ${COMMON_CFLAGS} ${OPTIMIZATION_CFLA
export CXXFLAGS=$(get_cxxflags ${LIB_NAME}) export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS="${ARCH_LDFLAGS}${FFMPEG_LDFLAGS} ${LINKED_LIBRARIES} ${COMMON_LDFLAGS} ${BITCODE_FLAGS} ${OPTIMIZATION_FLAGS}" export LDFLAGS="${ARCH_LDFLAGS}${FFMPEG_LDFLAGS} ${LINKED_LIBRARIES} ${COMMON_LDFLAGS} ${BITCODE_FLAGS} ${OPTIMIZATION_FLAGS}"
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
echo -n -e "\n${LIB_NAME}: " 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
if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then
echo -e "INFO: Cleaning workspace for ${LIB_NAME}" 1>>${BASEDIR}/build.log 2>&1 echo -e "INFO: Cleaning workspace for ${LIB_NAME}" 1>>${BASEDIR}/build.log 2>&1
make distclean 2>/dev/null 1>/dev/null make distclean 2>/dev/null 1>/dev/null
fi fi
# Workaround to prevent adding of -mdynamic-no-pic flag ########################### CUSTOMIZATIONS #######################
# 1. Workaround to prevent adding of -mdynamic-no-pic flag
${SED_INLINE} 's/check_cflags -mdynamic-no-pic && add_asflags -mdynamic-no-pic;/check_cflags -mdynamic-no-pic;/g' ./configure 1>>${BASEDIR}/build.log 2>&1 ${SED_INLINE} 's/check_cflags -mdynamic-no-pic && add_asflags -mdynamic-no-pic;/check_cflags -mdynamic-no-pic;/g' ./configure 1>>${BASEDIR}/build.log 2>&1
# Workaround for videotoolbox on mac catalyst # 2. Workaround for videotoolbox on mac catalyst
if [ ${ARCH} == "x86-64-mac-catalyst" ]; then if [ ${ARCH} == "x86-64-mac-catalyst" ]; then
${SED_INLINE} 's/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/ \/\/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/g' ${BASEDIR}/src/${LIB_NAME}/libavcodec/videotoolbox.c ${SED_INLINE} 's/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/ \/\/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/g' ${BASEDIR}/src/${LIB_NAME}/libavcodec/videotoolbox.c
else else
${SED_INLINE} 's/ \/\/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/g' ${BASEDIR}/src/${LIB_NAME}/libavcodec/videotoolbox.c ${SED_INLINE} 's/ \/\/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/g' ${BASEDIR}/src/${LIB_NAME}/libavcodec/videotoolbox.c
fi fi
# Workaround for not supported iOS/tvOS features # 3. Use thread local log level
git checkout 2e595085ef653f365af49e6a32f012cc6d9ee03c -- ${BASEDIR}/src/${LIB_NAME}/libavdevice/avfoundation.m 1>>${BASEDIR}/build.log 2>&1 ${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 \ ./configure \
--sysroot=${SDK_PATH} \ --sysroot=${SDK_PATH} \
@@ -443,6 +443,7 @@ git checkout 2e595085ef653f365af49e6a32f012cc6d9ee03c -- ${BASEDIR}/src/${LIB_NA
--disable-v4l2-m2m \ --disable-v4l2-m2m \
--disable-outdev=v4l2 \ --disable-outdev=v4l2 \
--disable-outdev=fbdev \ --disable-outdev=fbdev \
--disable-outdev=audiotoolbox \
--disable-indev=v4l2 \ --disable-indev=v4l2 \
--disable-indev=fbdev \ --disable-indev=fbdev \
--disable-openssl \ --disable-openssl \
@@ -474,12 +475,6 @@ git checkout 2e595085ef653f365af49e6a32f012cc6d9ee03c -- ${BASEDIR}/src/${LIB_NA
--disable-vdpau \ --disable-vdpau \
${CONFIGURE_POSTFIX} 1>>${BASEDIR}/build.log 2>&1 ${CONFIGURE_POSTFIX} 1>>${BASEDIR}/build.log 2>&1
# Workaround for issue #328
echo "" >>ffbuild/config.mak
echo "all:" >>ffbuild/config.mak
echo "libswscale/aarch64/hscale.o: ${BASEDIR}/tools/make/ffmpeg/libswscale/aarch64/hscale.S" >>ffbuild/config.mak
echo ' $(COMPILE_S)' >>ffbuild/config.mak
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "failed" echo "failed"
exit 1 exit 1
+1 -1
View File
@@ -67,6 +67,6 @@ export LIBPNG_LIBS="-L${BASEDIR}/prebuilt/$(get_target_build_directory)/libpng/l
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY # CREATE PACKAGE CONFIG MANUALLY
create_freetype_package_config "23.1.17" create_freetype_package_config "23.2.17"
make install || exit 1 make install || exit 1
+2 -6
View File
@@ -52,14 +52,10 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
cd ${BASEDIR}/src/${LIB_NAME} || exit 1 cd ${BASEDIR}/src/${LIB_NAME} || exit 1
./autogen.sh || exit 1
make distclean 2>/dev/null 1>/dev/null make distclean 2>/dev/null 1>/dev/null
# RECONFIGURE IF REQUESTED # ALWAYS RECONFIGURE
if [[ ${RECONF_kvazaar} -eq 1 ]]; then autoreconf_library ${LIB_NAME}
autoreconf_library ${LIB_NAME}
fi
./configure \ ./configure \
--prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \ --prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \
+3 -3
View File
@@ -42,6 +42,9 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null 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 # RECONFIGURE IF REQUESTED
if [[ ${RECONF_libsamplerate} -eq 1 ]]; then if [[ ${RECONF_libsamplerate} -eq 1 ]]; then
autoreconf_library ${LIB_NAME} autoreconf_library ${LIB_NAME}
@@ -57,9 +60,6 @@ fi
--disable-fast-install \ --disable-fast-install \
--host=${BUILD_HOST} || exit 1 --host=${BUILD_HOST} || exit 1
# DISABLE building of examples manually
${SED_INLINE} 's/examples tests//g' ${BASEDIR}/src/${LIB_NAME}/Makefile
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# MANUALLY COPY PKG-CONFIG FILES # MANUALLY COPY PKG-CONFIG FILES
+1 -1
View File
@@ -68,6 +68,6 @@ PKG_CONFIG= ./configure \
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY # CREATE PACKAGE CONFIG MANUALLY
create_libvorbis_package_config "1.3.6" create_libvorbis_package_config "1.3.7"
make install || exit 1 make install || exit 1
+1 -1
View File
@@ -61,7 +61,7 @@ if [[ ${RECONF_mobile_ffmpeg} -eq 1 ]]; then
fi fi
VIDEOTOOLBOX_SUPPORT_FLAG="" VIDEOTOOLBOX_SUPPORT_FLAG=""
if [[ ${46} -eq 1 ]]; then if [[ ${47} -eq 1 ]]; then
VIDEOTOOLBOX_SUPPORT_FLAG="--enable-videotoolbox" VIDEOTOOLBOX_SUPPORT_FLAG="--enable-videotoolbox"
fi fi
+3
View File
@@ -56,6 +56,9 @@ if [[ ${RECONF_nettle} -eq 1 ]]; then
autoreconf_library ${LIB_NAME} autoreconf_library ${LIB_NAME}
fi fi
${SED_INLINE} 's/exit(0)/return 0/g' ${BASEDIR}/src/${LIB_NAME}/configure
${SED_INLINE} 's/exit (0)/return 0/g' ${BASEDIR}/src/${LIB_NAME}/configure
./configure \ ./configure \
--prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \ --prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \
--enable-pic \ --enable-pic \
-1
View File
@@ -69,7 +69,6 @@ fi
make -j$(get_cpu_count) || exit 1 make -j$(get_cpu_count) || exit 1
# MANUALLY COPY PKG-CONFIG FILES # MANUALLY COPY PKG-CONFIG FILES
cp amrwb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
cp amrnb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1 cp amrnb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
make install || exit 1 make install || exit 1
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
if [[ -z ${ARCH} ]]; then
echo -e "(*) ARCH not defined\n"
exit 1
fi
if [[ -z ${TARGET_SDK} ]]; then
echo -e "(*) TARGET_SDK not defined\n"
exit 1
fi
if [[ -z ${SDK_PATH} ]]; then
echo -e "(*) SDK_PATH not defined\n"
exit 1
fi
if [[ -z ${BASEDIR} ]]; then
echo -e "(*) BASEDIR not defined\n"
exit 1
fi
# ENABLE COMMON FUNCTIONS
if [[ ${APPLE_TVOS_BUILD} -eq 1 ]]; then
. ${BASEDIR}/build/tvos-common.sh
else
. ${BASEDIR}/build/ios-common.sh
fi
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="vo-amrwbenc"
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})
# OVERRIDE CXX
case ${ARCH} in
x86-64 | x86-64-mac-catalyst)
export CXX="xcrun --sdk $(get_sdk_name) clang++ -arch x86_64"
;;
*)
export CXX="xcrun --sdk $(get_sdk_name) clang++ -arch ${ARCH}"
;;
esac
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_vo_amrwbenc} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
./configure \
--prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \
--with-pic \
--with-sysroot=${SDK_PATH} \
--enable-static \
--disable-shared \
--disable-fast-install \
--disable-maintainer-mode \
--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
-12
View File
@@ -73,18 +73,6 @@ cd cmake-build || exit 1
${SED_INLINE} 's/win64/macho64 -DPREFIX/g' ${BASEDIR}/src/x265/source/cmake/CMakeASM_NASMInformation.cmake ${SED_INLINE} 's/win64/macho64 -DPREFIX/g' ${BASEDIR}/src/x265/source/cmake/CMakeASM_NASMInformation.cmake
${SED_INLINE} 's/win/macho/g' ${BASEDIR}/src/x265/source/cmake/CMakeASM_NASMInformation.cmake ${SED_INLINE} 's/win/macho/g' ${BASEDIR}/src/x265/source/cmake/CMakeASM_NASMInformation.cmake
# fix pointer array assignments
${SED_INLINE} '/addAvg/s/ p.pu/ *p.pu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/convert_p2s/s/ p.pu/ *p.pu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/pixelavg_pp/s/ p.pu/ *p.pu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/addAvg/s/ p.chroma/ *p.chroma/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/add_ps/s/ p.chroma/ *p.chroma/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/add_ps/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/blockfill_s/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/ssd_s/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/calcresidual/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
${SED_INLINE} '/scale1D_128to64_neon/s/ p.scale/ *p.scale/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
# fixing constant shift # fixing constant shift
${SED_INLINE} 's/lsr 16/lsr #16/g' ${BASEDIR}/src/x265/source/common/arm/blockcopy8.S ${SED_INLINE} 's/lsr 16/lsr #16/g' ${BASEDIR}/src/x265/source/common/arm/blockcopy8.S
+2 -5
View File
@@ -122,19 +122,16 @@ fi
# FILTERING WHICH EXTERNAL LIBRARIES WILL BE BUILT # FILTERING WHICH EXTERNAL LIBRARIES WILL BE BUILT
# NOTE THAT BUILT-IN LIBRARIES ARE FORWARDED TO FFMPEG SCRIPT WITHOUT ANY PROCESSING # NOTE THAT BUILT-IN LIBRARIES ARE FORWARDED TO FFMPEG SCRIPT WITHOUT ANY PROCESSING
enabled_library_list=() enabled_library_list=()
for library in {1..44} for library in {1..45} 48
do do
if [[ ${!library} -eq 1 ]]; then if [[ ${!library} -eq 1 ]]; then
ENABLED_LIBRARY=$(get_library_name $((library - 1))) ENABLED_LIBRARY=$(get_library_name $((library - 1)))
enabled_library_list+=(${ENABLED_LIBRARY}) enabled_library_list+=(${ENABLED_LIBRARY})
echo -e "INFO: Enabled library ${ENABLED_LIBRARY}" 1>>${BASEDIR}/build.log 2>&1 echo -e "INFO: Enabled library ${ENABLED_LIBRARY}\n" 1>>${BASEDIR}/build.log 2>&1
fi fi
done done
# BUILD CPU-FEATURES FIRST
build_cpufeatures
# BUILD LTS SUPPORT LIBRARY FOR API < 18 # BUILD LTS SUPPORT LIBRARY FOR API < 18
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${API} < 18 ]]; then if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${API} < 18 ]]; then
build_android_lts_support build_android_lts_support

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