Compare commits

..

182 Commits

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

* update wiki images

* cleanup Android project

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

* Update README.md

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

* update project page

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

* update project page
2019-10-27 15:48:12 +00:00
Taner Sener 92eb33e0b5 Merge pull request #261 from tanersener/development
merge v4.3
2019-10-27 15:14:40 +00:00
14742 changed files with 733158 additions and 1877807 deletions
+19
View File
@@ -0,0 +1,19 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'
+73 -35
View File
@@ -2,36 +2,32 @@ branches:
only:
- master
- development
sudo: false
git:
quiet: true
addons:
apt:
packages:
- autoconf
- automake
- libtool
- pkg-config
- curl
- git
- cmake
- gcc
- gperf
- texinfo
- yasm
- nasm
- bison
- autogen
- patch
homebrew:
packages:
- nasm
update: true
matrix:
depth: false
jobs:
include:
- name: "Android Main Build"
language: android
os: linux
dist : trusty
addons:
apt:
packages:
- autoconf
- automake
- libtool
- pkg-config
- curl
- git
- cmake
- gcc
- gperf
- texinfo
- yasm
- bison
- autogen
- patch
android:
components:
- tools
@@ -42,24 +38,42 @@ matrix:
- extra-google-m2repository
- extra-android-m2repository
install:
- echo y | sdkmanager "ndk-bundle"
- echo y | sdkmanager "ndk;21.3.6528147"
- echo y | sdkmanager "cmake;3.10.2.4988404"
- echo y | sdkmanager "lldb;3.1"
before_install:
- touch $HOME/.android/repositories.cfg
before_script:
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk-bundle
- export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
- rm -f ./build.log
- wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.gz;tar zxvf nasm-2.14.02.tar.gz;cd nasm-2.14.02;./configure;make;sudo make install;cd ..
after_success:
- grep -e INFO ./build.log | grep build
after_failure:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./android.sh --no-output-redirection
- bash ./android.sh --no-output-redirection -d
- name: "Android LTS Build"
language: android
os: linux
dist : trusty
addons:
apt:
packages:
- autoconf
- automake
- libtool
- pkg-config
- curl
- git
- cmake
- gcc
- gperf
- texinfo
- yasm
- bison
- autogen
- patch
android:
components:
- tools
@@ -71,24 +85,30 @@ matrix:
- extra-google-m2repository
- extra-android-m2repository
install:
- echo y | sdkmanager "ndk-bundle"
- echo y | sdkmanager "ndk;21.3.6528147"
- echo y | sdkmanager "cmake;3.10.2.4988404"
- echo y | sdkmanager "lldb;3.1"
before_install:
- touch $HOME/.android/repositories.cfg
before_script:
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk-bundle
- export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
- rm -f ./build.log
- wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.gz;tar zxvf nasm-2.14.02.tar.gz;cd nasm-2.14.02;./configure;make;sudo make install;cd ..
after_success:
- grep -e INFO ./build.log | grep build
after_failure:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./android.sh --lts --no-output-redirection
- bash ./android.sh --lts --no-output-redirection -d
- name: "iOS Main Build"
language: objective-c
os: osx
osx_image: xcode10.2
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -97,10 +117,16 @@ matrix:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./ios.sh --no-output-redirection
- bash ./ios.sh --no-output-redirection --disable-arm64e
- name: "iOS LTS Build"
language: objective-c
osx_image: xcode7.3
os: osx
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -109,10 +135,16 @@ matrix:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./ios.sh --lts --no-output-redirection
- bash ./ios.sh --lts --no-output-redirection --disable-armv7 --disable-armv7s --disable-i386
- name: "tvOS Main Build"
language: objective-c
os: osx
osx_image: xcode10.2
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -124,7 +156,13 @@ matrix:
- bash ./tvos.sh --no-output-redirection
- name: "tvOS LTS Build"
language: objective-c
os: osx
osx_image: xcode7.3
addons:
homebrew:
packages:
- nasm
update: true
before_script:
- rm -f ./build.log
after_success:
@@ -133,4 +171,4 @@ matrix:
- tail -30 ./build.log
- tail -30 ./src/ffmpeg/ffbuild/config.log
script:
- bash ./tvos.sh --lts --no-output-redirection
- bash ./tvos.sh --lts --no-output-redirection
+189 -91
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.1-blue.svg) ![Bintray](https://img.shields.io/badge/bintray-v4.3.1-blue.svg) ![CocoaPods](https://img.shields.io/badge/pod-v4.3.1-blue.svg) [![Build Status](https://travis-ci.org/tanersener/mobile-ffmpeg.svg?branch=master)](https://travis-ci.org/tanersener/mobile-ffmpeg)
# MobileFFmpeg [![Financial Contributors on Open Collective](https://opencollective.com/mobile-ffmpeg/all/badge.svg?label=financial+contributors)](https://opencollective.com/mobile-ffmpeg) ![GitHub release](https://img.shields.io/badge/release-v4.4-blue.svg) ![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
@@ -9,21 +9,21 @@ FFmpeg for Android, iOS and tvOS
- Use binaries available at `Github`/`JCenter`/`CocoaPods` or build your own version with external libraries you need
- Supports
- Android, iOS and tvOS
- FFmpeg `v3.4.x`, `v4.0.x`, `v4.1`, `v4.2` and `v4.3-dev` releases
- 28 external libraries
- FFmpeg `v3.4.x`, `v4.0.x`, `v4.1`, `v4.2` , `v4.3` and `v4.4-dev` releases
- 29 external libraries
`chromaprint`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `openh264`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `twolame`, `wavpack`
`chromaprint`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `openh264`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `twolame`, `vo-amrwbenc`, `wavpack`
- 4 external libraries with GPL license
- 5 external libraries with GPL license
`vid.stab`, `x264`, `x265`, `xvidcore`
`rubberband`, `vid.stab`, `x264`, `x265`, `xvidcore`
- Concurrent execution
- Exposes both FFmpeg library and MobileFFmpeg wrapper library capabilities
- Includes cross-compile instructions for 44 open-source libraries
- Includes cross-compile instructions for 47 open-source libraries
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `leptonica`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `openh264`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `tiff`, `twolame`, `vid.stab`, `wavpack`, `x264`, `x265`, `xvidcore`
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `leptonica`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libsamplerate`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `openh264`, `opus`, `rubberband`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `tiff`, `twolame`, `vid.stab`, `vo-amrwbenc`, `wavpack`, `x264`, `x265`, `xvidcore`
- Licensed under LGPL 3.0, can be customized to support GPL v3.0
@@ -36,26 +36,29 @@ FFmpeg for Android, iOS and tvOS
- Supports `API Level 16+`
#### 1.2 iOS
- Builds `armv7`, `armv7s`, `arm64`, `arm64e`, `i386` and `x86_64` architectures
- Supports `bzip2`, `zlib`, `iconv` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` system frameworks
- Builds `armv7`, `armv7s`, `arm64`, `arm64e`, `i386`, `x86_64` and `x86_64` (Mac Catalyst) architectures
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox`, `AVFoundation` system frameworks
- Objective-C API
- Camera access
- `ARC` enabled library
- Built with `-fembed-bitcode` flag
- Creates static framework and static universal (fat) library (.a)
- Creates static frameworks, static xcframeworks and static universal (fat) libraries (.a)
- Supports `iOS SDK 9.3` or later
#### 1.3 tvOS
- Builds `arm64` and `x86_64` architectures
- Supports `bzip2`, `zlib`, `iconv` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox` system frameworks
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox` system frameworks
- Objective-C API
- `ARC` enabled library
- Built with `-fembed-bitcode` flag
- Creates static framework and static universal (fat) library (.a)
- Creates static frameworks and static universal (fat) libraries (.a)
- Supports `tvOS SDK 9.2` or later
### 2. Using
Published binaries are available at [Github](https://github.com/tanersener/mobile-ffmpeg/releases), [JCenter](https://bintray.com/bintray/jcenter) and [CocoaPods](https://cocoapods.org).
Prebuilt binaries are available at [Github](https://github.com/tanersener/mobile-ffmpeg/releases), [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.
@@ -65,14 +68,14 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
<thead>
<tr>
<th align="center"></th>
<th align="center">min</th>
<th align="center">min-gpl</th>
<th align="center">https</th>
<th align="center">https-gpl</th>
<th align="center">audio</th>
<th align="center">video</th>
<th align="center">full</th>
<th align="center">full-gpl</th>
<th align="center"><sup>min</sup></th>
<th align="center"><sup>min-gpl</sup></th>
<th align="center"><sup>https</sup></th>
<th align="center"><sup>https-gpl</sup></th>
<th align="center"><sup>audio</sup></th>
<th align="center"><sup>video</sup></th>
<th align="center"><sup>full</sup></th>
<th align="center"><sup>full-gpl</sup></th>
</tr>
</thead>
<tbody>
@@ -82,10 +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>gmp</sup><br><sup>gnutls</sup></td>
<td align="center"><sup>gmp</sup><br><sup>gnutls</sup><br><sup>vid.stab</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
<td align="center"><sup>lame</sup><br><sup>libilbc</sup><br><sup>libvorbis</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>lame</sup><br><sup>libilbc</sup><br><sup>libvorbis</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vo-amrwbenc</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>kvazaar</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libtheora</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>snappy</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vid.stab</sup><br><sup>wavpack</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vo-amrwbenc</sup><br><sup>wavpack</sup></td>
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vid.stab</sup><br><sup>vo-amrwbenc</sup><br><sup>wavpack</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
</tr>
<tr>
<td align="center"><sup>android system libraries</sup></td>
@@ -93,11 +96,11 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
</tr>
<tr>
<td align="center"><sup>ios system libraries</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>AVFoundation</sup><br><sup>CoreImage</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>AVFoundation</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
</tr>
<tr>
<td align="center"><sup>tvos system libraries</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>CoreImage</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
</tr>
</tbody>
</table>
@@ -108,7 +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`
- `sdl`, `tesseract`, `twolame` external libraries; `zlib`, `MediaCodec` Android system libraries; `bzip2`, `zlib` iOS system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` iOS system frameworks are supported since `v3.0`
- `sdl`, `tesseract`, `twolame` external libraries; `zlib`, `MediaCodec` Android system libraries; `bzip2`, `zlib` iOS system libraries and `AudioToolbox`, `VideoToolbox`, `AVFoundation` iOS system frameworks are supported since `v3.0`
- Since `v4.2`, `chromaprint`, `sdl` and `tesseract` libraries are not included in binary releases. You can still build them and include in your releases
@@ -116,15 +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
#### 2.1 Android
1. Add MobileFFmpeg dependency to your `build.gradle` in `mobile-ffmpeg-<package name>` format
- `vo-amrwbenc` is supported since `v4.4`
#### 2.2 Android
1. Add MobileFFmpeg dependency to your `build.gradle` in `mobile-ffmpeg-<package name>` pattern.
```
dependencies {
implementation 'com.arthenica:mobile-ffmpeg-full:4.3.1'
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.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 (rc == RETURN_CODE_SUCCESS) {
Log.i(Config.TAG, "Async command execution completed successfully.");
} else if (rc == 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 rc=%d.", rc));
}
}
});
```
4. Execute FFprobe commands.
```
import com.arthenica.mobileffmpeg.Config;
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();
@@ -170,26 +195,26 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
}
```
5. Stop an ongoing FFmpeg operation.
```
FFmpeg.cancel();
```
6. Stop ongoing FFmpeg operations.
- Stop all executions
```
FFmpeg.cancel();
```
- Stop a specific execution
```
FFmpeg.cancel(executionId);
```
6. Get media information for a file.
7. Get media information for a file.
```
MediaInformation info = FFprobe.getMediaInformation("<file path or uri>");
```
7. Record video using Android camera.
8. Record video using Android camera.
```
FFmpeg.execute("-f android_camera -i 0:0 -r 30 -pixel_format bgr0 -t 00:00:05 <record file path>");
```
8. List enabled external libraries.
```
List<String> externalLibraries = Config.getExternalLibraries();
```
9. Enable log callback.
```
Config.enableLogCallback(new LogCallback() {
@@ -207,31 +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);
```
12. Register custom fonts directory.
14. Register custom fonts directory.
```
Config.setFontDirectory(this, "<folder with fonts>", Collections.EMPTY_MAP);
```
#### 2.2 iOS / tvOS
1. Add MobileFFmpeg dependency to your `Podfile` in `mobile-ffmpeg-<package name>` format
#### 2.3 iOS / tvOS
1. Add MobileFFmpeg dependency to your `Podfile` in `mobile-ffmpeg-<package name>` pattern.
- iOS
```
pod 'mobile-ffmpeg-full', '~> 4.3.1'
pod 'mobile-ffmpeg-full', '~> 4.4'
```
- tvOS
```
pod 'mobile-ffmpeg-tvos-full', '~> 4.3.1'
pod 'mobile-ffmpeg-tvos-full', '~> 4.4'
```
2. Execute FFmpeg commands.
2. Execute synchronous FFmpeg commands.
```
#import <mobileffmpeg/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFmpeg.h>
@@ -246,8 +284,26 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
NSLog(@"Command execution failed with rc=%d and output=%@.\n", rc, [MobileFFmpegConfig getLastCommandOutput]);
}
```
3. Execute FFprobe commands.
3. Execute asynchronous FFmpeg commands.
```
#import <mobileffmpeg/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFmpeg.h>
long executionId = [MobileFFmpeg executeAsync:@"-i file1.mp4 -c:v mpeg4 file2.mp4" withCallback:self];
- (void)executeCallback:(long)executionId :(int)returnCode {
if (rc == RETURN_CODE_SUCCESS) {
NSLog(@"Async command execution completed successfully.\n");
} else if (rc == RETURN_CODE_CANCEL) {
NSLog(@"Async command execution cancelled by user.\n");
} else {
NSLog(@"Async command execution failed with rc=%d.\n", rc);
}
}
```
4. Execute FFprobe commands.
```
#import <mobileffmpeg/MobileFFmpegConfig.h>
#import <mobileffmpeg/MobileFFprobe.h>
@@ -263,7 +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];
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.
```
[MobileFFmpeg cancel];
```
6. Stop ongoing FFmpeg operations.
- Stop all executions
```
[MobileFFmpeg cancel];
6. Get media information for a file.
```
- Stop a specific execution
```
[MobileFFmpeg cancel:executionId];
```
7. Get media information for a file.
```
MediaInformation *mediaInformation = [MobileFFprobe getMediaInformation:@"<file path or uri>"];
```
7. Record video and audio using iOS camera. This operation is not supported on `tvOS` since `AVFoundation` is not available on `tvOS`.
8. Record video and audio using iOS camera. This operation is not supported on `tvOS` since `AVFoundation` is not available on `tvOS`.
```
[MobileFFmpeg execute: @"-f avfoundation -r 30 -video_size 1280x720 -pixel_format bgr0 -i 0:0 -vcodec h264_videotoolbox -vsync 2 -f h264 -t 00:00:05 %@", recordFilePath];
```
8. List enabled external libraries.
```
NSArray *externalLibraries = [MobileFFmpegConfig getExternalLibraries];
```
9. Enable log callback.
```
[MobileFFmpegConfig setLogDelegate:self];
- (void)logCallback: (int)level :(NSString*)message {
- (void)logCallback:(long)executionId :(int)level :(NSString*)message {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", message);
});
@@ -320,33 +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];
```
12. Register custom fonts directory.
14. Register custom fonts directory.
```
[MobileFFmpegConfig setFontDirectory:@"<folder with fonts>" with:nil];
```
#### 2.3 Manual Installation
##### 2.3.1 Android
#### 2.4 Manual Installation
##### 2.4.1 Android
You can import `MobileFFmpeg` aar packages in `Android Studio` using the `File` -> `New` -> `New Module` -> `Import .JAR/.AAR Package` menu.
##### 2.3.2 iOS / tvOS
##### 2.4.2 iOS / tvOS
iOS and tvOS frameworks can be installed manually using the [Importing Frameworks](https://github.com/tanersener/mobile-ffmpeg/wiki/Importing-Frameworks) guide.
If you want to use universal binaries please refer to [Using Universal Binaries](https://github.com/tanersener/mobile-ffmpeg/wiki/Using-Universal-Binaries) guide.
#### 2.4 Test Application
#### 2.5 Test Application
You can see how MobileFFmpeg is used inside an application by running test applications provided.
There is an `Android` test application under the `android/test-app` folder, an `iOS` test application under the
`ios/test-app` folder and a `tvOS` test application under the `tvos/test-app` folder.
All applications are identical and supports command execution, video encoding, accessing https, encoding audio,
burning subtitles, video stabilization and pipe operations.
burning subtitles, video stabilisation, pipe operations and concurrent command execution.
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/master/docs/assets/android_test_app.gif" width="240">
@@ -362,6 +433,9 @@ Exact version number is obtained using `git describe --tags`.
| MobileFFmpeg Version | FFmpeg Version | Release Date |
| :----: | :----: |:----: |
| [4.4](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.4) | 4.4-dev-416 | Jul 25, 2020 |
| [4.4.LTS](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.4.LTS) | 4.4-dev-416 | Jul 24, 2020 |
| [4.3.2](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3.2) | 4.3-dev-2955 | Apr 15, 2020 |
| [4.3.1](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3.1) | 4.3-dev-1944 | Jan 25, 2020 |
| [4.3.1.LTS](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3.1.LTS) | 4.3-dev-1944 | Jan 25, 2020 |
| [4.3](https://github.com/tanersener/mobile-ffmpeg/releases/tag/v4.3) | 4.3-dev-1181 | Oct 27, 2019 |
@@ -397,10 +471,15 @@ This table shows the differences between two variants.
| Android Architectures | arm-v7a-neon<br/>arm64-v8a<br/>x86<br/>x86-64 | arm-v7a<br/>arm-v7a-neon<br/>arm64-v8a<br/>x86<br/>x86-64 |
| Xcode Support | 10.1 | 7.3.1 |
| iOS SDK | 12.1 | 9.3 |
| iOS Architectures | arm64<br/>arm64e<br/>x86-64 | armv7<br/>arm64<br/>i386<br/>x86-64 |
| iOS AVFoundation | Yes | - |
| iOS Architectures | arm64<br/>arm64e<sup>1</sup><br/>x86-64<br/>x86-64-mac-catalyst<sup>2</sup> | armv7<br/>arm64<br/>i386<br/>x86-64 |
| tvOS SDK | 10.2 | 9.2 |
| tvOS Architectures | arm64<br/>x86-64 | arm64<br/>x86-64 |
<sup>1</sup> - Included until `v4.3.2`
<sup>2</sup> - Included since `v4.3.2`
### 5. Building
Build scripts from `master` and `development` branches are tested periodically. See the latest status from the table below.
@@ -424,7 +503,7 @@ Please visit [Android Prerequisites](https://github.com/tanersener/mobile-ffmpeg
2. Android builds require these additional packages.
- **Android SDK 4.1 Jelly Bean (API Level 16)** or later
- **Android NDK r20** or later with LLDB and CMake
- **Android NDK r21** or later with LLDB and CMake
3. iOS builds need these extra packages and tools.
- **Xcode 7.3.1** or later
@@ -478,14 +557,16 @@ All libraries created by the top level build scripts (`android.sh`, `ios.sh` and
the `prebuilt` directory.
- `Android` archive (.aar file) is located under the `android-aar` folder
- `iOS` frameworks are located under the `ios-framework`folder
- `iOS` universal binaries are located under the `ios-universal`folder
- `tvOS` frameworks are located under the `tvos-framework`folder
- `tvOS` universal binaries are located under the `tvos-universal`folder
- `iOS` frameworks are located under the `ios-framework` folder
- `iOS` xcframeworks are located under the `ios-xcframework` folder
- `iOS` universal binaries are located under the `ios-universal` folder
- `tvOS` frameworks are located under the `tvos-framework` folder
- `tvOS` universal binaries are located under the `tvos-universal` folder
#### 5.4 GPL Support
It is possible to enable GPL licensed libraries `x264`, `xvidcore` since `v1.1` and `vid.stab`, `x265` since `v2.1`
from the top level build scripts. Their source code is not included in the repository and downloaded when enabled.
It is possible to enable GPL licensed libraries `x264`, `xvidcore` since `v1.1`; `vid.stab`, `x265` since `v2.1` and
`rubberband` since `v4.3.2` from the top level build scripts. Their source code is not included in the repository and
downloaded when enabled.
#### 5.5 External Libraries
`build` directory includes build scripts of all external libraries. Two scripts exist for each external library,
@@ -531,30 +612,47 @@ Support this project with your organization. Your logo will show up here with a
### 8. License
This project is licensed under the LGPL v3.0. However, if source code is built using optional `--enable-gpl` flag or
prebuilt binaries with `-gpl` postfix are used then MobileFFmpeg is subject to the GPL v3.0 license.
`MobileFFmpeg` is licensed under the LGPL v3.0. However, if source code is built using the optional `--enable-gpl` flag
or prebuilt binaries with `-gpl` postfix are used, then MobileFFmpeg is subject to the GPL v3.0 license.
Source code of FFmpeg and external libraries is included in compliance with their individual licenses.
The source code of all external libraries included is in compliance with their individual licenses.
`openh264` source code included in this repository is licensed under the 2-clause BSD License but this license does
not cover the `MPEG LA` licensing fees. If you build `mobile-ffmpeg` with `openh264` and distribute that library, then
you are subject to pay `MPEG LA` licensing fees. Refer to [OpenH264 FAQ](https://www.openh264.org/faq.html) page for
the details. Please note that `mobile-ffmpeg` does not publish a binary with `openh264` inside.
`strip-frameworks.sh` script included and distributed (until v4.x) is published under the [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
`strip-frameworks.sh` script included and distributed (until v4.x) is published under the
[Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
In test applications; embedded fonts are licensed under the [SIL Open Font License](https://opensource.org/licenses/OFL-1.1), other digital assets are published in the public domain.
In test applications; embedded fonts are licensed under the
[SIL Open Font License](https://opensource.org/licenses/OFL-1.1), other digital assets are published in the public
domain.
Please visit [License](https://github.com/tanersener/mobile-ffmpeg/wiki/License) page for the details.
### 9. Contributing
### 9. Patents
If you have any recommendations or ideas to improve it, please feel free to submit issues or pull requests. Any help is appreciated.
It is not clearly explained in their documentation but it is believed that `FFmpeg`, `kvazaar`, `x264` and `x265`
include algorithms which are subject to software patents. If you live in a country where software algorithms are
patentable then you'll probably need to pay royalty fees to patent holders. We are not lawyers though, so we recommend
that you seek legal advice first. See [FFmpeg Patent Mini-FAQ](https://ffmpeg.org/legal.html).
### 10. See Also
`openh264` clearly states that it uses patented algorithms. Therefore, if you build mobile-ffmpeg with openh264 and
distribute that library, then you are subject to pay MPEG LA licensing fees. Refer to
[OpenH264 FAQ](https://www.openh264.org/faq.html) page for the details.
### 10. Contributing
Feel free to submit issues or pull requests.
Please note that `master` branch includes only the latest released source code. Changes planned for the next release
are implemented under the `development` branch. Therefore, if you want to create a pull request, please open it against
the `development`.
### 11. See Also
- [libav gas-preprocessor](https://github.com/libav/gas-preprocessor/raw/master/gas-preprocessor.pl)
- [FFmpeg API Documentation](https://ffmpeg.org/doxygen/4.0/index.html)
- [FFmpeg Wiki](https://trac.ffmpeg.org/wiki/WikiStart)
- [FFmpeg License and Legal Considerations](https://ffmpeg.org/legal.html)
- [FFmpeg External Library Licenses](https://www.ffmpeg.org/doxygen/4.0/md_LICENSE.html)
+625 -590
View File
File diff suppressed because it is too large Load Diff
+9 -6
View File
@@ -1,10 +1,13 @@
*.iml
.gradle
.gradle/
local.properties
.DS_Store
build
/build/
/app/build/
/test-app/build/
/app/.cxx/
captures
.externalNativeBuild
.idea
obj
libs
.externalNativeBuild/
.idea/
/obj/
/libs/
+137 -69
View File
@@ -1,4 +1,4 @@
# Doxyfile 1.8.14
# Doxyfile 1.8.18
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -17,10 +17,10 @@
# Project related configuration options
#---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the config file
# that follow. The default is UTF-8 which is also the encoding used for all text
# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
# built into libc) for the transcoding. See
# This tag specifies the encoding used for all characters in the configuration
# file that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8.
@@ -38,7 +38,7 @@ PROJECT_NAME = "MobileFFmpeg Android API"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 4.3.1
PROJECT_NUMBER = 4.4
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all generated output in the proper direction.
# Possible values are: None, LTR, RTL and Context.
# The default value is: None.
OUTPUT_TEXT_DIRECTION = None
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
@@ -189,6 +197,16 @@ SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
# such as
# /***************
# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
# Javadoc-style will behave just like regular comments and it will not be
# interpreted by doxygen.
# The default value is: NO.
JAVADOC_BANNER = NO
# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
# line (until the first dot) of a Qt-style comment as the brief description. If
# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
@@ -238,15 +256,13 @@ TAB_SIZE = 4
# "Side Effects:". You can put \n's in the value part of an alias to insert
# newlines (in the resulting output). You can put ^^ in the value part of an
# alias to insert a newline as if a physical newline was in the original file.
# When you need a literal { or } or , in the value part of an alias you have to
# escape them by means of a backslash (\), this can lead to conflicts with the
# commands \{ and \} for these it is advised to use the version @{ and @} or use
# a double escape (\\{ and \\})
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@@ -275,17 +291,26 @@ OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
# sources only. Doxygen will then generate output that is more tailored for that
# language. For instance, namespaces will be presented as modules, types will be
# separated into more groups, etc.
# The default value is: NO.
OPTIMIZE_OUTPUT_SLICE = NO
# Doxygen selects the parser to use depending on the extension of the files it
# parses. With this tag you can assign which parser to use for a given
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
# Fortran. In the later case the parser tries to guess whether the code is fixed
# or free formatted code, this is the default for Fortran type files), VHDL. For
# instance to make doxygen treat .inc files as Fortran files (default is PHP),
# and .f files as C (default is Fortran), use: inc=Fortran f=C.
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
@@ -296,7 +321,7 @@ EXTENSION_MAPPING =
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
# documentation. See http://daringfireball.net/projects/markdown/ for details.
# documentation. See https://daringfireball.net/projects/markdown/ for details.
# The output of markdown processing is further processed by doxygen, so you can
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues.
@@ -308,7 +333,7 @@ MARKDOWN_SUPPORT = YES
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings.
# Minimum value: 0, maximum value: 99, default value: 0.
# Minimum value: 0, maximum value: 99, default value: 5.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
TOC_INCLUDE_HEADINGS = 0
@@ -444,6 +469,12 @@ EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
# methods of a class will be included in the documentation.
# The default value is: NO.
EXTRACT_PRIV_VIRTUAL = NO
# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation.
# The default value is: NO.
@@ -498,8 +529,8 @@ HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# (class|struct|union) declarations. If set to NO, these declarations will be
# included in the documentation.
# declarations. If set to NO, these declarations will be included in the
# documentation.
# The default value is: NO.
HIDE_FRIEND_COMPOUNDS = NO
@@ -522,7 +553,7 @@ INTERNAL_DOCS = NO
# names in lower-case letters. If set to YES, upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
# (including Cygwin) ands Mac users are advised to set this option to NO.
# The default value is: system dependent.
CASE_SENSE_NAMES = NO
@@ -754,7 +785,8 @@ WARN_IF_DOC_ERROR = YES
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong or incomplete
# parameter documentation, but not about the absence of documentation.
# parameter documentation, but not about the absence of documentation. If
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.
WARN_NO_PARAMDOC = NO
@@ -813,8 +845,10 @@ INPUT_ENCODING = UTF-8
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
*.cc \
@@ -889,7 +923,7 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = cmdutils.* ffmpeg.* ffmpeg_*
EXCLUDE_PATTERNS =
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
@@ -1011,7 +1045,7 @@ INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
# function all documented functions referencing it will be listed.
# entity all documented functions referencing it will be listed.
# The default value is: NO.
REFERENCED_BY_RELATION = NO
@@ -1048,7 +1082,7 @@ SOURCE_TOOLTIPS = YES
#
# To use it do the following:
# - Install the latest version of global
# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
# - Make sure the INPUT points to the root of the source tree
# - Run doxygen as normal
#
@@ -1226,9 +1260,9 @@ HTML_TIMESTAMP = YES
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via Javascript. If disabled, the navigation index will
# are dynamically created via JavaScript. If disabled, the navigation index will
# consists of multiple levels of tabs that are statically embedded in every HTML
# page. Disable this option to support browsers that do not have Javascript,
# page. Disable this option to support browsers that do not have JavaScript,
# like the Qt help browser.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1258,13 +1292,13 @@ HTML_INDEX_NUM_ENTRIES = 100
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
# generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see: https://developer.apple.com/tools/xcode/), introduced with
# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
# Makefile in the HTML output directory. Running make will produce the docset in
# that directory and running make install will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html
# for more information.
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# genXcode/_index.html for more information.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1303,7 +1337,7 @@ DOCSET_PUBLISHER_NAME = Publisher
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
# Windows.
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
@@ -1379,7 +1413,7 @@ QCH_FILE =
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace
# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES.
@@ -1387,7 +1421,8 @@ QHP_NAMESPACE = org.doxygen.Project
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
# Help Project output. For more information please see Qt Help Project / Virtual
# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
# folders).
# The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES.
@@ -1395,21 +1430,23 @@ QHP_VIRTUAL_FOLDER = doc
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
# filter to add. For more information please see Qt Help Project / Custom
# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
# custom filter to add. For more information please see Qt Help Project / Custom
# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
# project's filter section matches. Qt Help Project / Filter Attributes (see:
# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_SECT_FILTER_ATTRS =
@@ -1493,6 +1530,17 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png The default and svg Looks nicer but requires the
# pdf2svg tool.
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FORMULA_FORMAT = png
# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
@@ -1513,8 +1561,14 @@ FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
FORMULA_MACROFILE =
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# https://www.mathjax.org) which uses client side Javascript for the rendering
# https://www.mathjax.org) which uses client side JavaScript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
# installed or if you want to formulas look prettier in the HTML output. When
# enabled you may also need to install MathJax separately and configure the path
@@ -1542,7 +1596,7 @@ MATHJAX_FORMAT = HTML-CSS
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/
@@ -1584,7 +1638,7 @@ MATHJAX_CODEFILE =
SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
# implemented using a web server instead of a web client using Javascript. There
# implemented using a web server instead of a web client using JavaScript. There
# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
# setting. When disabled, doxygen will generate a PHP script for searching and
# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
@@ -1668,21 +1722,35 @@ LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked.
#
# Note that when enabling USE_PDFLATEX this option is only used for generating
# bitmaps for formulas in the HTML output, but not in the Makefile that is
# written to the output directory.
# The default file is: latex.
# Note that when not enabling USE_PDFLATEX the default is latex when enabling
# USE_PDFLATEX the default is pdflatex and when in the later case latex is
# chosen this is overwritten by pdflatex. For specific output languages the
# default can have been set differently, this depends on the implementation of
# the output language.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX.
# Note: This tag is used in the Makefile / make.bat.
# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
# (.tex).
# The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex
# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
# generate index for LaTeX. In case there is no backslash (\) as first character
# it will be automatically added in the LaTeX code.
# Note: This tag is used in the generated output file (.tex).
# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
# The default value is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_MAKEINDEX_CMD = makeindex
# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
# documents. This may be useful for small projects and may help to save some
# trees in general.
@@ -1817,6 +1885,14 @@ LATEX_BIB_STYLE = plain
LATEX_TIMESTAMP = NO
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
# LATEX_OUTPUT directory will be used.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EMOJI_DIRECTORY =
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
@@ -1856,9 +1932,9 @@ COMPACT_RTF = NO
RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's config
# file, i.e. a series of assignments. You only have to provide replacements,
# missing definitions are set to their default value.
# Load stylesheet definitions from file. Syntax is similar to doxygen's
# configuration file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
#
# See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses.
@@ -1867,8 +1943,8 @@ RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an RTF document. Syntax is
# similar to doxygen's config file. A template extensions file can be generated
# using doxygen -e rtf extensionFile.
# similar to doxygen's configuration file. A template extensions file can be
# generated using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_EXTENSIONS_FILE =
@@ -1954,6 +2030,13 @@ XML_OUTPUT = xml
XML_PROGRAMLISTING = YES
# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
# namespace members in file scope as well, matching the HTML output.
# The default value is: NO.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_NS_MEMB_FILE_SCOPE = NO
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
@@ -2155,12 +2238,6 @@ EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
# The PERL_PATH should be the absolute path and name of the perl script
# interpreter (i.e. the result of 'which perl').
# The default file (with absolute path) is: /usr/bin/perl.
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
@@ -2174,15 +2251,6 @@ PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = YES
# You can define message sequence charts within doxygen comments using the \msc
# command. Doxygen will then run the mscgen tool (see:
# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
# documentation. The MSCGEN_PATH tag allows you to specify the directory where
# the mscgen tool resides. If left empty the tool is assumed to be found in the
# default search path.
MSCGEN_PATH =
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+6 -4
View File
@@ -1,13 +1,14 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
compileSdkVersion 30
ndkVersion "21.3.6528147"
defaultConfig {
minSdkVersion 24
targetSdkVersion 29
versionCode 240431
versionName "4.3.1"
targetSdkVersion 30
versionCode 240440
versionName "4.4"
project.archivesBaseName = "mobile-ffmpeg"
consumerProguardFiles 'proguard-rules.pro'
}
@@ -39,4 +40,5 @@ task javadoc(type: Javadoc) {
dependencies {
testImplementation "androidx.test.ext:junit:1.1.1"
testImplementation "org.json:json:20190722"
}
+2 -2
View File
@@ -7,8 +7,8 @@
-keep class com.arthenica.mobileffmpeg.Config {
native <methods>;
void log(int, byte[]);
void statistics(int, float, float, long , int, double, double);
void log(long, int, byte[]);
void statistics(long, int, float, float, long , int, double, double);
}
-keep class com.arthenica.mobileffmpeg.AbiDetect {
+1 -1
View File
@@ -105,7 +105,7 @@ __thread AVDictionary *format_opts, *codec_opts, *resample_opts;
FILE *report_file;
int report_file_level = AV_LOG_DEBUG;
__thread int hide_banner = 0;
__thread int longjmp_value = 0;
__thread volatile int longjmp_value = 0;
enum show_muxdemuxers {
SHOW_DEFAULT,
+1 -1
View File
@@ -77,7 +77,6 @@ extern __thread AVDictionary *swr_opts;
extern __thread AVDictionary *format_opts, *codec_opts, *resample_opts;
extern __thread int hide_banner;
extern __thread int find_stream_info;
extern __thread int filter_nbthreads;
/**
* Register a program-specific cleanup routine.
@@ -335,6 +334,7 @@ typedef struct OptionParseContext {
* Parse an options group and write results into optctx.
*
* @param optctx an app-specific options context. NULL for global options group
* @param g option group
*/
int parse_optgroup(void *optctx, OptionGroup *g);
+47 -16
View File
@@ -24,11 +24,16 @@
*/
/*
* CHANGES 06.2020
* - ignoring signals implemented
* - cancel_operation() method signature updated with id
* - cancel by execution id implemented
*
* CHANGES 01.2020
* - ffprobe support changes
*
* CHANGES 12.2019
* - Concurrent execution support
* - concurrent execution support
*
* CHANGES 08.2018
* --------------------------------------------------------
@@ -239,6 +244,16 @@ __thread int restore_tty;
static void free_input_threads(void);
#endif
extern volatile int handleSIGQUIT;
extern volatile int handleSIGINT;
extern volatile int handleSIGTERM;
extern volatile int handleSIGXCPU;
extern volatile int handleSIGPIPE;
extern __thread volatile long executionId;
extern void removeExecution(long id);
extern int cancelRequested(long id);
/* sub2video hack:
Convert subtitles to video with alpha to insert them in filter graphs.
This is a temporary solution until libavfilter gets real subtitles support.
@@ -402,12 +417,12 @@ void term_exit(void)
term_exit_sigsafe();
}
volatile int received_sigterm = 0;
volatile int received_nb_signals = 0;
static volatile int received_sigterm = 0;
static volatile int received_nb_signals = 0;
__thread atomic_int transcode_init_done = ATOMIC_VAR_INIT(0);
__thread volatile int ffmpeg_exited = 0;
__thread int main_ffmpeg_return_code = 0;
extern __thread int longjmp_value;
__thread volatile int main_ffmpeg_return_code = 0;
extern __thread volatile int longjmp_value;
static void
sigterm_handler(int sig)
@@ -476,17 +491,27 @@ void term_init(void)
tcsetattr (0, TCSANOW, &tty);
}
signal(SIGQUIT, sigterm_handler); /* Quit (POSIX). */
if (handleSIGQUIT == 1) {
signal(SIGQUIT, sigterm_handler); /* Quit (POSIX). */
}
}
#endif
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
if (handleSIGINT == 1) {
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
}
if (handleSIGTERM == 1) {
signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
}
#ifdef SIGXCPU
signal(SIGXCPU, sigterm_handler);
if (handleSIGXCPU == 1) {
signal(SIGXCPU, sigterm_handler);
}
#endif
#ifdef SIGPIPE
signal(SIGPIPE, SIG_IGN); /* Broken pipe (POSIX). */
if (handleSIGPIPE == 1) {
signal(SIGPIPE, SIG_IGN); /* Broken pipe (POSIX). */
}
#endif
#if HAVE_SETCONSOLECTRLHANDLER
SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE);
@@ -701,6 +726,8 @@ static void ffmpeg_cleanup(int ret)
if (received_sigterm) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n",
(int) received_sigterm);
} else if (cancelRequested(executionId)) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received cancel signal.\n");
} else if (ret && atomic_load(&transcode_init_done)) {
av_log(NULL, AV_LOG_INFO, "Conversion failed!\n");
}
@@ -2362,7 +2389,7 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts)
if (ifilter->filter) {
/* THIS VALIDATION IS REQUIRED TO COMPLETE CANCELLATION */
if (!received_sigterm) {
if (!received_sigterm && !cancelRequested(executionId)) {
ret = av_buffersrc_close(ifilter->filter, pts, AV_BUFFERSRC_FLAG_PUSH);
}
if (ret < 0)
@@ -4829,7 +4856,7 @@ static int transcode(void)
goto fail;
#endif
while (!received_sigterm) {
while (!received_sigterm && !cancelRequested(executionId)) {
int64_t cur_time= av_gettime_relative();
/* if 'q' pressed, exits */
@@ -5029,9 +5056,13 @@ void set_report_callback(void (*callback)(int, float, float, int64_t, int, doubl
report_callback = callback;
}
void cancel_operation()
void cancel_operation(long id)
{
sigterm_handler(SIGINT);
if (id == 0) {
sigterm_handler(SIGINT);
} else {
removeExecution(id);
}
}
__thread OptionDef *ffmpeg_options = NULL;
@@ -5541,10 +5572,10 @@ int ffmpeg_execute(int argc, char **argv)
if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
exit_program(69);
exit_program(received_nb_signals ? 255 : main_ffmpeg_return_code);
exit_program((received_nb_signals || cancelRequested(executionId))? 255 : main_ffmpeg_return_code);
} else {
main_ffmpeg_return_code = longjmp_value;
main_ffmpeg_return_code = (received_nb_signals || cancelRequested(executionId)) ? 255 : longjmp_value;
}
return main_ffmpeg_return_code;
+5 -1
View File
@@ -17,6 +17,9 @@
*/
/*
* CHANGES 06.2020
* - cancel_operation() method signature updated with id
*
* CHANGES 01.2020
* - ffprobe support changes
*
@@ -627,6 +630,7 @@ extern __thread AVIOContext *progress_avio;
extern __thread float max_error_rate;
extern __thread char *videotoolbox_pixfmt;
extern __thread int filter_nbthreads;
extern __thread int filter_complex_nbthreads;
extern __thread int vstats_version;
@@ -684,7 +688,7 @@ int hwaccel_decode_init(AVCodecContext *avctx);
void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double));
void cancel_operation();
void cancel_operation(long id);
int opt_map(void *optctx, const char *opt, const char *arg);
int opt_map_channel(void *optctx, const char *opt, const char *arg);
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -261,8 +261,8 @@ __thread AVInputFormat *iformat = NULL;
__thread struct AVHashContext *hash;
__thread int main_ffprobe_return_code = 0;
extern __thread int longjmp_value;
__thread volatile int main_ffprobe_return_code = 0;
extern __thread volatile int longjmp_value;
static const struct {
double bin_val;
+185 -112
View File
@@ -17,34 +17,6 @@
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* CHANGES 08.2019
* --------------------------------------------------------
* - lastCommandOutput methods introduced
* - AV_LOG_STDERR introduced
*
* CHANGES 04.2019
* --------------------------------------------------------
* - setNativeEnvironmentVariable method added
*
* CHANGES 02.2019
* --------------------------------------------------------
* - JavaVM registered via av_jni_set_java_vm()
* - registerNewNativeFFmpegPipe() method added
*
* CHANGES 10.2018
* --------------------------------------------------------
* - getBuildConf method added
*
* CHANGES 09.2018
* --------------------------------------------------------
* - Merged with mobileffmpeg_config
*
* CHANGES 08.2018
* --------------------------------------------------------
* - Copied methods with avutil_log_ prefix from libavutil/log.c
*/
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -59,9 +31,10 @@
/** Callback data structure */
struct CallbackData {
int type; // 1 (log callback) or 2 (statistics callback)
long executionId; // execution id
int logLevel; // log level
char *logData; // log data
AVBPrint logData; // log data
int statisticsFrameNumber; // statistics frame number
float statisticsFps; // statistics fps
@@ -74,14 +47,19 @@ struct CallbackData {
struct CallbackData *next;
};
/** Execution map variables */
const int EXECUTION_MAP_SIZE = 1000;
static volatile int executionMap[EXECUTION_MAP_SIZE];
static pthread_mutex_t executionMapMutex;
/** Redirection control variables */
pthread_mutex_t lockMutex;
pthread_mutex_t monitorMutex;
pthread_cond_t monitorCondition;
static pthread_mutex_t lockMutex;
static pthread_mutex_t monitorMutex;
static pthread_cond_t monitorCondition;
/** Last command output variables */
pthread_mutex_t logMutex;
static char *lastCommandOutput;
static pthread_mutex_t logMutex;
static AVBPrint lastCommandOutput;
pthread_t callbackThread;
int redirectionEnabled;
@@ -113,6 +91,19 @@ const char *configClassName = "com/arthenica/mobileffmpeg/Config";
/** Full name of String class */
const char *stringClassName = "java/lang/String";
/** Fields that control the handling of SIGNALs */
volatile int handleSIGQUIT = 1;
volatile int handleSIGINT = 1;
volatile int handleSIGTERM = 1;
volatile int handleSIGXCPU = 1;
volatile int handleSIGPIPE = 1;
/** Holds the id of the current execution */
__thread volatile long executionId = 0;
/** Holds the default log level */
int configuredLogLevel = AV_LOG_INFO;
/** Prototypes of native functions defined by Config class. */
JNINativeMethod configMethods[] = {
{"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection},
@@ -121,20 +112,19 @@ JNINativeMethod configMethods[] = {
{"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel},
{"getNativeFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion},
{"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeVersion},
{"nativeFFmpegExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute},
{"nativeFFmpegCancel", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel},
{"nativeFFmpegExecute", "(J[Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute},
{"nativeFFmpegCancel", "(J)V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel},
{"nativeFFprobeExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeFFprobeExecute},
{"registerNewNativeFFmpegPipe", "(Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_registerNewNativeFFmpegPipe},
{"getNativeBuildDate", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeBuildDate},
{"setNativeEnvironmentVariable", "(Ljava/lang/String;Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmentVariable}
{"setNativeEnvironmentVariable", "(Ljava/lang/String;Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmentVariable},
{"getNativeLastCommandOutput", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput},
{"ignoreNativeSignal", "(I)V", (void*) Java_com_arthenica_mobileffmpeg_Config_ignoreNativeSignal}
};
/** Forward declaration for function defined in fftools_ffmpeg.c */
int ffmpeg_execute(int argc, char **argv);
/** DEFINES LINE SIZE USED FOR LOGGING */
#define LOG_LINE_SIZE 1024
static const char *avutil_log_get_level_str(int level) {
switch (level) {
case AV_LOG_STDERR:
@@ -233,7 +223,16 @@ void logInit() {
pthread_mutex_init(&logMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
lastCommandOutput = NULL;
av_bprint_init(&lastCommandOutput, 0, AV_BPRINT_SIZE_UNLIMITED);
}
void executionMapLockInit() {
pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&executionMapMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
}
void mutexUnInit() {
@@ -249,6 +248,10 @@ void logUnInit() {
pthread_mutex_destroy(&logMutex);
}
void executionMapLockUnInit() {
pthread_mutex_destroy(&executionMapMutex);
}
void mutexLock() {
pthread_mutex_lock(&lockMutex);
}
@@ -257,6 +260,10 @@ void lastCommandOutputLock() {
pthread_mutex_lock(&logMutex);
}
void executionMapLock() {
pthread_mutex_lock(&executionMapMutex);
}
void mutexUnlock() {
pthread_mutex_unlock(&lockMutex);
}
@@ -265,50 +272,24 @@ void lastCommandOutputUnlock() {
pthread_mutex_unlock(&logMutex);
}
void executionMapUnlock() {
pthread_mutex_unlock(&executionMapMutex);
}
void clearLastCommandOutput() {
lastCommandOutputLock();
if (lastCommandOutput != NULL) {
av_free(lastCommandOutput);
lastCommandOutput = NULL;
}
av_bprint_clear(&lastCommandOutput);
lastCommandOutputUnlock();
}
void appendLastCommandOutput(const char *logMessage) {
size_t length = 0;
char *tempLastCommandOutput = NULL;
size_t logMessageLength = strlen(logMessage);
if (logMessageLength <= 0) {
void appendLastCommandOutput(AVBPrint *logMessage) {
if (logMessage->len <= 0) {
return;
}
lastCommandOutputLock();
if (lastCommandOutput == NULL) {
length = logMessageLength + 1;
lastCommandOutput = (char*)av_malloc(length);
memcpy(lastCommandOutput, logMessage, length);
} else {
size_t length1 = strlen(lastCommandOutput);
length = length1 + logMessageLength + 1;
char *newLastCommandOutput = (char*)av_malloc(length);
memcpy(newLastCommandOutput, lastCommandOutput, length1);
memcpy(newLastCommandOutput + length1, logMessage, logMessageLength + 1);
tempLastCommandOutput = lastCommandOutput;
lastCommandOutput = newLastCommandOutput;
}
av_bprintf(&lastCommandOutput, "%s", logMessage->str);
lastCommandOutputUnlock();
if (tempLastCommandOutput != NULL) {
av_free(tempLastCommandOutput);
}
}
void monitorWait(int milliSeconds) {
@@ -343,15 +324,15 @@ void monitorNotify() {
* @param level log level
* @param data log data
*/
void logCallbackDataAdd(int level, const char *data) {
void logCallbackDataAdd(int level, AVBPrint *data) {
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 1;
newData->executionId = executionId;
newData->logLevel = level;
size_t dataSize = strlen(data) + 1;
newData->logData = (char*)av_malloc(dataSize);
memcpy(newData->logData, data, dataSize);
av_bprint_init(&newData->logData, 0, AV_BPRINT_SIZE_UNLIMITED);
av_bprintf(&newData->logData, "%s", data->str);
newData->next = NULL;
mutexLock();
@@ -385,6 +366,7 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 2;
newData->executionId = executionId;
newData->statisticsFrameNumber = frameNumber;
newData->statisticsFps = fps;
newData->statisticsQuality = quality;
@@ -418,6 +400,20 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
monitorNotify();
}
/**
* Adds an execution id to the execution map.
*
* @param id execution id
*/
void addExecution(long id) {
executionMapLock();
int key = id % EXECUTION_MAP_SIZE;
executionMap[key] = 1;
executionMapUnlock();
}
/**
* Removes head of callback data list.
*/
@@ -450,6 +446,41 @@ struct CallbackData *callbackDataRemove() {
return currentData;
}
/**
* Removes an execution id from the execution map.
*
* @param id execution id
*/
void removeExecution(long id) {
executionMapLock();
int key = id % EXECUTION_MAP_SIZE;
executionMap[key] = 0;
executionMapUnlock();
}
/**
* Checks whether a cancel request for the given execution id exists in the execution map.
*
* @param id execution id
* @return 1 if exists, false otherwise
*/
int cancelRequested(long id) {
int found = 0;
executionMapLock();
int key = id % EXECUTION_MAP_SIZE;
if (executionMap[key] == 0) {
found = 1;
}
executionMapUnlock();
return found;
}
/**
* Callback function for FFmpeg logs.
*
@@ -459,7 +490,7 @@ struct CallbackData *callbackDataRemove() {
* @param vargs arguments
*/
void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs) {
char line[LOG_LINE_SIZE];
AVBPrint fullLine;
AVBPrint part[4];
int print_prefix = 1;
@@ -473,21 +504,27 @@ void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format
return;
}
av_bprint_init(&fullLine, 0, AV_BPRINT_SIZE_UNLIMITED);
avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix);
avutil_log_sanitize(part[0].str);
avutil_log_sanitize(part[1].str);
avutil_log_sanitize(part[2].str);
avutil_log_sanitize(part[3].str);
snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
// COMBINE ALL 4 LOG PARTS
av_bprintf(&fullLine, "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
logCallbackDataAdd(level, line);
appendLastCommandOutput(line);
if (fullLine.len > 0) {
logCallbackDataAdd(level, &fullLine);
appendLastCommandOutput(&fullLine);
}
av_bprint_finalize(part, NULL);
av_bprint_finalize(part+1, NULL);
av_bprint_finalize(part+2, NULL);
av_bprint_finalize(part+3, NULL);
av_bprint_finalize(&fullLine, NULL);
}
/**
@@ -533,25 +570,25 @@ void *callbackThreadFunction() {
// LOG CALLBACK
size_t size = strlen(callbackData->logData);
int size = callbackData->logData.len;
jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, (jbyte *)callbackData->logData);
(*env)->CallStaticVoidMethod(env, configClass, logMethod, callbackData->logLevel, byteArray);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, callbackData->logData.str);
(*env)->CallStaticVoidMethod(env, configClass, logMethod, (jlong) callbackData->executionId, callbackData->logLevel, byteArray);
(*env)->DeleteLocalRef(env, byteArray);
// CLEAN LOG DATA
av_free(callbackData->logData);
av_bprint_finalize(&callbackData->logData, NULL);
} else {
// STATISTICS CALLBACK
(*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
callbackData->statisticsFrameNumber, callbackData->statisticsFps,
callbackData->statisticsQuality, callbackData->statisticsSize,
callbackData->statisticsTime, callbackData->statisticsBitrate,
callbackData->statisticsSpeed);
(jlong) callbackData->executionId, callbackData->statisticsFrameNumber,
callbackData->statisticsFps, callbackData->statisticsQuality,
callbackData->statisticsSize, callbackData->statisticsTime,
callbackData->statisticsBitrate, callbackData->statisticsSpeed);
}
@@ -604,24 +641,21 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
(*env)->GetJavaVM(env, &globalVm);
logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(I[B)V");
logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(JI[B)V");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "log");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE;
}
statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(IFFJIDD)V");
statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(JIFFJIDD)V");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "statistics");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE;
}
stringConstructor = (*env)->GetMethodID(env, localStringClass, "<init>", "([BLjava/lang/String;)V");
if (stringConstructor == NULL) {
LOGE("OnLoad thread failed to GetMethodID for %s.\n", "<init>");
(*globalVm)->DetachCurrentThread(globalVm);
return JNI_FALSE;
}
@@ -634,10 +668,15 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
callbackDataHead = NULL;
callbackDataTail = NULL;
for(int i = 0; i<EXECUTION_MAP_SIZE; i++) {
executionMap[i] = 0;
}
mutexInit();
monitorInit();
logInit();
executionMapLockInit();
return JNI_VERSION_1_6;
}
@@ -650,7 +689,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
* @param level log level
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) {
av_log_set_level(level);
configuredLogLevel = level;
}
/**
@@ -660,7 +699,7 @@ JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(
* @param object reference to the class on which this method is invoked
*/
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object) {
return av_log_get_level();
return configuredLogLevel;
}
/**
@@ -741,14 +780,18 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersio
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param id execution id
* @param stringArray reference to the object holding FFmpeg command arguments
* @return zero on successful execution, non-zero on error
*/
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *env, jclass object, jobjectArray stringArray) {
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *env, jclass object, jlong id, jobjectArray stringArray) {
jstring *tempArray = NULL;
int argumentCount = 1;
char **argv = NULL;
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW EXECUTION
av_log_set_level(configuredLogLevel);
if (stringArray != NULL) {
int programArgumentCount = (*env)->GetArrayLength(env, stringArray);
argumentCount = programArgumentCount + 1;
@@ -777,9 +820,16 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecut
// LAST COMMAND OUTPUT SHOULD BE CLEARED BEFORE STARTING A NEW EXECUTION
clearLastCommandOutput();
// REGISTER THE ID BEFORE STARTING EXECUTION
executionId = (long) id;
addExecution((long) id);
// RUN
int retCode = ffmpeg_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP
removeExecution((long) id);
// CLEANUP
if (tempArray != NULL) {
for (int i = 0; i < (argumentCount - 1); i++) {
@@ -799,9 +849,10 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecut
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param id execution id
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *env, jclass object) {
cancel_operation();
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *env, jclass object, jlong id) {
cancel_operation(id);
}
/**
@@ -844,7 +895,11 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
const char *variableNameString = (*env)->GetStringUTFChars(env, variableName, 0);
const char *variableValueString = (*env)->GetStringUTFChars(env, variableValue, 0);
return setenv(variableNameString, variableValueString, 1);
int rc = setenv(variableNameString, variableValueString, 1);
(*env)->ReleaseStringUTFChars(env, variableName, variableNameString);
(*env)->ReleaseStringUTFChars(env, variableValue, variableValueString);
return rc;
}
/**
@@ -855,16 +910,34 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
* @return output of the last executed command
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput(JNIEnv *env, jclass object) {
if (lastCommandOutput != NULL) {
int size = strlen(lastCommandOutput);
if (size > 0) {
jbyteArray byteArray = (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, lastCommandOutput);
jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
return (jstring) (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName);
}
int size = lastCommandOutput.len;
if (size > 0) {
jbyteArray byteArray = (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, lastCommandOutput.str);
jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
return (jstring) (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName);
}
return (*env)->NewStringUTF(env, "");
}
/**
* Registers a new ignored signal. Ignored signals are not handled by the library.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param signum signal number
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum) {
if (signum == SIGQUIT) {
handleSIGQUIT = 0;
} else if (signum == SIGINT) {
handleSIGINT = 0;
} else if (signum == SIGTERM) {
handleSIGTERM = 0;
} else if (signum == SIGXCPU) {
handleSIGXCPU = 0;
} else if (signum == SIGPIPE) {
handleSIGPIPE = 0;
}
}
+12 -5
View File
@@ -27,7 +27,7 @@
#include "libavutil/ffversion.h"
/** Library version string */
#define MOBILE_FFMPEG_VERSION "4.3.1"
#define MOBILE_FFMPEG_VERSION "4.4"
/** Defines tag used for Android logging. */
#define LIB_NAME "mobile-ffmpeg"
@@ -92,16 +92,16 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersio
/*
* Class: com_arthenica_mobileffmpeg_Config
* Method: nativeFFmpegExecute
* Signature: ([Ljava/lang/String;)I
* Signature: (J[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *, jclass, jobjectArray);
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegExecute(JNIEnv *, jclass, jlong id, jobjectArray);
/*
* Class: com_arthenica_mobileffmpeg_Config
* Method: nativeFFmpegCancel
* Signature: ()V
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *, jclass);
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFmpegCancel(JNIEnv *, jclass, jlong);
/*
* Class: com_arthenica_mobileffmpeg_Config
@@ -131,4 +131,11 @@ JNIEXPORT int JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeEnvironmen
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLastCommandOutput(JNIEnv *env, jclass object);
/*
* Class: com_arthenica_mobileffmpeg_Config
* Method: ignoreNativeSignal
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum);
#endif /* MOBILE_FFMPEG_H */
@@ -122,7 +122,7 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_getNativeCpu
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @return YES or NO
* @return yes or no
*/
JNIEXPORT jboolean JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_isNativeLTSBuild(JNIEnv *env, jclass object) {
#if defined(MOBILE_FFMPEG_LTS)
-39
View File
@@ -1,39 +0,0 @@
/*
* 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 <jni.h>
#include "libavcodec/jni.h"
/** Forward declaration for function defined in fftools_ffplay.c */
int ffplay_execute(int argc, char **argv);
/** Forward declaration for functions defined in SDl_android.c */
void set_mobile_ffmpeg_ffplay_execute(int (*ffplay_execute_function)(int argc, char **argv));
jint SDL_Android_Initialize(JavaVM* vm, void* reserved);
/**
* Initializes SDL for FFplay. It must be called before other SDL functions.
*/
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_FFplay_nativeSDLInit(JNIEnv *env, jclass object) {
set_mobile_ffmpeg_ffplay_execute(ffplay_execute);
JavaVM *globalVm = av_jni_get_java_vm(NULL);
if (globalVm) {
SDL_Android_Initialize(globalVm, NULL);
}
}
-32
View File
@@ -1,32 +0,0 @@
/*
* 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_FFPLAY_H
#define MOBILE_FFPLAY_H
#include <jni.h>
/*
* Class: com_arthenica_mobileffmpeg_Config
* Method: nativeFFplayExecute
* Signature: ([Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFplayExecute(JNIEnv *, jclass, jobjectArray);
#endif /* MOBILE_FFPLAY_H */
+5
View File
@@ -32,6 +32,8 @@ int ffprobe_execute(int argc, char **argv);
/** Forward declaration for function defined in mobileffmpeg.c */
void clearLastCommandOutput();
extern int configuredLogLevel;
/**
* Synchronously executes FFprobe natively with arguments provided.
*
@@ -45,6 +47,9 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeFFprobeExecu
int argumentCount = 1;
char **argv = NULL;
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW EXECUTION
av_log_set_level(configuredLogLevel);
if (stringArray != NULL) {
int programArgumentCount = (*env)->GetArrayLength(env, stringArray);
argumentCount = programArgumentCount + 1;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,11 +19,8 @@
package com.arthenica.mobileffmpeg;
import android.os.Build;
/**
* <p>This class is used to detect running ABI name using Android's <code>cpufeatures</code>
* library.
* <p>This class is used to detect running ABI name using Google <code>cpu-features</code> library.
*
* @author Taner Sener
* @since v1.0
@@ -33,10 +30,6 @@ public class AbiDetect {
static {
armV7aNeonLoaded = false;
/* LOAD NOT-LOADED LIBRARIES ON API < 21 */
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
System.loadLibrary("cpufeatures");
}
System.loadLibrary("mobileffmpeg_abidetect");
/* ALL LIBRARIES LOADED AT STARTUP */
@@ -90,7 +83,7 @@ public class AbiDetect {
/**
* <p>Returns whether MobileFFmpeg release is a long term release or not.
*
* @return YES or NO
* @return yes or no
*/
native static boolean isNativeLTSBuild();
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
/**
* <p>Utility class to execute an FFmpeg command asynchronously.
*
* @author Taner Sener
*/
public class AsyncFFmpegExecuteTask extends AsyncTask<Void, Integer, Integer> {
private final String[] arguments;
private final ExecuteCallback executeCallback;
private final Long executionId;
public AsyncFFmpegExecuteTask(final String command, final ExecuteCallback executeCallback) {
this(FFmpeg.parseArguments(command), executeCallback);
}
public AsyncFFmpegExecuteTask(final String[] arguments, final ExecuteCallback executeCallback) {
this(FFmpeg.DEFAULT_EXECUTION_ID, arguments, executeCallback);
}
public AsyncFFmpegExecuteTask(final long executionId, final String command, final ExecuteCallback executeCallback) {
this(executionId, FFmpeg.parseArguments(command), executeCallback);
}
public AsyncFFmpegExecuteTask(final long executionId, final String[] arguments, final ExecuteCallback executeCallback) {
this.executionId = executionId;
this.arguments = arguments;
this.executeCallback = executeCallback;
}
@Override
protected Integer doInBackground(final Void... unused) {
return Config.ffmpegExecute(executionId, this.arguments);
}
@Override
protected void onPostExecute(final Integer rc) {
if (executeCallback != null) {
executeCallback.apply(executionId, rc);
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
/**
* <p>Utility class to execute an FFprobe command asynchronously.
*
* @author Taner Sener
*/
public class AsyncFFprobeExecuteTask extends AsyncTask<Void, Integer, Integer> {
private final String[] arguments;
private final ExecuteCallback ExecuteCallback;
public AsyncFFprobeExecuteTask(final String command, final ExecuteCallback executeCallback) {
this.arguments = FFmpeg.parseArguments(command);
this.ExecuteCallback = executeCallback;
}
public AsyncFFprobeExecuteTask(final String[] arguments, final ExecuteCallback executeCallback) {
this.arguments = arguments;
ExecuteCallback = executeCallback;
}
@Override
protected Integer doInBackground(final Void... unused) {
return FFprobe.execute(this.arguments);
}
@Override
protected void onPostExecute(final Integer rc) {
if (ExecuteCallback != null) {
ExecuteCallback.apply(FFmpeg.DEFAULT_EXECUTION_ID, rc);
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.FFprobe;
import com.arthenica.mobileffmpeg.GetMediaInformationCallback;
import com.arthenica.mobileffmpeg.MediaInformation;
/**
* <p>Utility class to get media information asynchronously.
*
* @author Taner Sener
*/
public class AsyncGetMediaInformationTask extends AsyncTask<String, MediaInformation, MediaInformation> {
private final String path;
private final GetMediaInformationCallback getMediaInformationCallback;
public AsyncGetMediaInformationTask(final String path, final GetMediaInformationCallback getMediaInformationCallback) {
this.path = path;
this.getMediaInformationCallback = getMediaInformationCallback;
}
@Override
protected MediaInformation doInBackground(final String... arguments) {
return FFprobe.getMediaInformation(path);
}
@Override
protected void onPostExecute(final MediaInformation mediaInformation) {
if (getMediaInformationCallback != null) {
getMediaInformationCallback.apply(mediaInformation);
}
}
}
@@ -33,6 +33,11 @@ import java.util.List;
import static android.content.Context.CAMERA_SERVICE;
import static com.arthenica.mobileffmpeg.Config.TAG;
/**
* Utility class for camera devices.
*
* @author Taner Sener
*/
class CameraSupport {
/**
@@ -27,6 +27,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@@ -78,57 +79,71 @@ public class Config {
private static int lastCreatedPipeIndex;
private static final List<FFmpegExecution> executions;
static {
Log.i(Config.TAG, "Loading mobile-ffmpeg.");
/* LOAD NOT-LOADED LIBRARIES ON API < 21 */
boolean nativeFFmpegLoaded = false;
boolean nativeFFmpegTriedAndFailed = false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
/* LOADING LIBRARIES MANUALLY ON API < 21 */
final List<String> externalLibrariesEnabled = getExternalLibraries();
if (externalLibrariesEnabled.contains("tesseract") || externalLibrariesEnabled.contains("x265") || externalLibrariesEnabled.contains("snappy") || externalLibrariesEnabled.contains("openh264")) {
// libc++_shared.so included only when tesseract or x265 is enabled
if (externalLibrariesEnabled.contains("tesseract") || externalLibrariesEnabled.contains("x265") || externalLibrariesEnabled.contains("snappy") || externalLibrariesEnabled.contains("openh264") || externalLibrariesEnabled.contains("rubberband")) {
System.loadLibrary("c++_shared");
}
System.loadLibrary("cpufeatures");
System.loadLibrary("avutil");
System.loadLibrary("swscale");
System.loadLibrary("swresample");
System.loadLibrary("avcodec");
System.loadLibrary("avformat");
System.loadLibrary("avfilter");
System.loadLibrary("avdevice");
if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
try {
System.loadLibrary("avutil_neon");
System.loadLibrary("swscale_neon");
System.loadLibrary("swresample_neon");
System.loadLibrary("avcodec_neon");
System.loadLibrary("avformat_neon");
System.loadLibrary("avfilter_neon");
System.loadLibrary("avdevice_neon");
nativeFFmpegLoaded = true;
} catch (final UnsatisfiedLinkError e) {
Log.i(Config.TAG, "NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.", e);
nativeFFmpegTriedAndFailed = true;
}
}
if (!nativeFFmpegLoaded) {
System.loadLibrary("avutil");
System.loadLibrary("swscale");
System.loadLibrary("swresample");
System.loadLibrary("avcodec");
System.loadLibrary("avformat");
System.loadLibrary("avfilter");
System.loadLibrary("avdevice");
}
}
/* ALL MOBILE-FFMPEG LIBRARIES LOADED AT STARTUP */
Abi.class.getName();
FFmpeg.class.getName();
FFprobe.class.getName();
FFplay.class.getName();
/*
* NEON supported arm-v7a library has a different name
*/
boolean nativeLibraryLoaded = false;
if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
if (AbiDetect.isNativeLTSBuild()) {
boolean nativeMobileFFmpegLoaded = false;
if (!nativeFFmpegTriedAndFailed && AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
try {
/*
* IF CPU SUPPORTS ARM-V7A-NEON THE TRY TO LOAD IT FIRST. IF NOT LOAD DEFAULT ARM-V7A
* THE TRY TO LOAD ARM-V7A-NEON FIRST. IF NOT LOAD DEFAULT ARM-V7A
*/
try {
System.loadLibrary("mobileffmpeg_armv7a_neon");
nativeLibraryLoaded = true;
AbiDetect.setArmV7aNeonLoaded(true);
} catch (final UnsatisfiedLinkError e) {
Log.i(Config.TAG, "NEON supported armeabi-v7a library not found. Loading default armeabi-v7a library.", e);
}
} else {
System.loadLibrary("mobileffmpeg_armv7a_neon");
nativeMobileFFmpegLoaded = true;
AbiDetect.setArmV7aNeonLoaded(true);
} catch (final UnsatisfiedLinkError e) {
Log.i(Config.TAG, "NEON supported armeabi-v7a mobileffmpeg library not found. Loading default armeabi-v7a library.", e);
}
}
if (!nativeLibraryLoaded) {
if (!nativeMobileFFmpegLoaded) {
System.loadLibrary("mobileffmpeg");
}
@@ -142,6 +157,8 @@ public class Config {
enableRedirection();
lastCreatedPipeIndex = 0;
executions = Collections.synchronizedList(new ArrayList<FFmpegExecution>());
}
/**
@@ -215,10 +232,11 @@ public class Config {
/**
* <p>Log redirection method called by JNI/native part.
*
* @param levelValue log level as defined in {@link Level}
* @param logMessage redirected log message
* @param executionId id of the execution that generated this log, 0 by default
* @param levelValue log level as defined in {@link Level}
* @param logMessage redirected log message
*/
private static void log(final int levelValue, final byte[] logMessage) {
private static void log(final long executionId, final int levelValue, final byte[] logMessage) {
final Level level = Level.from(levelValue);
final String text = new String(logMessage);
@@ -230,7 +248,7 @@ public class Config {
if (logCallbackFunction != null) {
try {
logCallbackFunction.apply(new LogMessage(level, text));
logCallbackFunction.apply(new LogMessage(executionId, level, text));
} catch (final Exception e) {
Log.e(Config.TAG, "Exception thrown inside LogCallback block", e);
}
@@ -275,6 +293,7 @@ public class Config {
/**
* <p>Statistics redirection method called by JNI/native part.
*
* @param executionId id of the execution that generated this statistics, 0 by default
* @param videoFrameNumber last processed frame number for videos
* @param videoFps frames processed per second for videos
* @param videoQuality quality of the video stream
@@ -283,10 +302,10 @@ public class Config {
* @param bitrate output bit rate in kbits/s
* @param speed processing speed = processed duration / operation duration
*/
private static void statistics(final int videoFrameNumber, final float videoFps,
final float videoQuality, final long size, final int time,
final double bitrate, final double speed) {
final Statistics newStatistics = new Statistics(videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
private static void statistics(final long executionId, final int videoFrameNumber,
final float videoFps, final float videoQuality, final long size,
final int time, final double bitrate, final double speed) {
final Statistics newStatistics = new Statistics(executionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
lastReceivedStatistics.update(newStatistics);
if (statisticsCallbackFunction != null) {
@@ -498,7 +517,7 @@ public class Config {
* @return MobileFFmpeg version
*/
public static String getVersion() {
if (AbiDetect.isNativeLTSBuild()) {
if (isLTSBuild()) {
return String.format("%s-lts", getNativeVersion());
} else {
return getNativeVersion();
@@ -587,6 +606,48 @@ public class Config {
} while (buffer.length() > 0);
}
/**
* <p>Sets an environment variable.
*
* @param variableName environment variable name
* @param variableValue environment variable value
* @return zero on success, non-zero on error
*/
public static int setEnvironmentVariable(final String variableName, final String variableValue) {
return setNativeEnvironmentVariable(variableName, variableValue);
}
/**
* <p>Registers a new ignored signal. Ignored signals are not handled by the library.
*
* @param signal signal number to ignore
*/
public static void ignoreSignal(final Signal signal) {
ignoreNativeSignal(signal.getValue());
}
/**
* <p>Synchronously executes FFmpeg with arguments provided.
*
* @param executionId id of the execution
* @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
static int ffmpegExecute(final long executionId, final String[] arguments) {
final FFmpegExecution currentFFmpegExecution = new FFmpegExecution(executionId, arguments);
executions.add(currentFFmpegExecution);
try {
final int lastReturnCode = nativeFFmpegExecute(executionId, arguments);
Config.setLastReturnCode(lastReturnCode);
return lastReturnCode;
} finally {
executions.remove(currentFFmpegExecution);
}
}
/**
* Updates return code value for the last executed command.
*
@@ -596,57 +657,69 @@ public class Config {
lastReturnCode = newLastReturnCode;
}
/**
* <p>Lists ongoing FFmpeg executions.
*
* @return list of ongoing FFmpeg executions
*/
static List<FFmpegExecution> listFFmpegExecutions() {
return new ArrayList<>(executions);
}
/**
* <p>Enables native redirection. Necessary for log and statistics callback functions.
*/
private native static void enableNativeRedirection();
private static native void enableNativeRedirection();
/**
* <p>Disables native redirection
*/
private native static void disableNativeRedirection();
private static native void disableNativeRedirection();
/**
* Sets native log level
*
* @param level log level
*/
private native static void setNativeLogLevel(int level);
private static native void setNativeLogLevel(int level);
/**
* Returns native log level.
*
* @return log level
*/
private native static int getNativeLogLevel();
private static native int getNativeLogLevel();
/**
* <p>Returns FFmpeg version bundled within the library natively.
*
* @return FFmpeg version
*/
native static String getNativeFFmpegVersion();
private native static String getNativeFFmpegVersion();
/**
* <p>Returns MobileFFmpeg library version natively.
*
* @return MobileFFmpeg version
*/
native static String getNativeVersion();
private native static String getNativeVersion();
/**
* <p>Synchronously executes FFmpeg natively with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @param executionId id of the execution
* @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
native static int nativeFFmpegExecute(final String[] arguments);
private native static int nativeFFmpegExecute(final long executionId, final String[] arguments);
/**
* <p>Cancels an ongoing FFmpeg operation natively. This function does not wait for termination
* to complete and returns immediately.
*
* @param executionId id of the execution
*/
native static void nativeFFmpegCancel();
native static void nativeFFmpegCancel(final long executionId);
/**
* <p>Synchronously executes FFprobe natively with arguments provided.
@@ -664,14 +737,14 @@ public class Config {
* @param ffmpegPipePath full path of ffmpeg pipe
* @return zero on successful creation, non-zero on error
*/
native static int registerNewNativeFFmpegPipe(final String ffmpegPipePath);
private native static int registerNewNativeFFmpegPipe(final String ffmpegPipePath);
/**
* <p>Returns MobileFFmpeg library build date natively.
*
* @return MobileFFmpeg library build date
*/
native static String getNativeBuildDate();
private native static String getNativeBuildDate();
/**
* <p>Sets an environment variable natively.
@@ -680,13 +753,20 @@ public class Config {
* @param variableValue environment variable value
* @return zero on success, non-zero on error
*/
public native static int setNativeEnvironmentVariable(final String variableName, final String variableValue);
private native static int setNativeEnvironmentVariable(final String variableName, final String variableValue);
/**
* <p>Returns log output of the last executed single command natively.
*
* @return output of the last executed single command
*/
native static String getNativeLastCommandOutput();
private native static String getNativeLastCommandOutput();
/**
* <p>Registers a new ignored signal natively. Ignored signals are not handled by the library.
*
* @param signum signal number
*/
private native static void ignoreNativeSignal(final int signum);
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to receive an asynchronous execution result.
*
* @author Taner Sener
* @since v2.1
*/
@FunctionalInterface
public interface ExecuteCallback {
/**
* <p>Called when an asynchronous FFmpeg execution is completed.
*
* @param executionId id of the execution that completed
* @param returnCode return code of the execution completed, 0 on successful completion, 255
* on user cancel, other non-zero codes on error
*/
void apply(long executionId, int returnCode);
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,22 +19,34 @@
package com.arthenica.mobileffmpeg;
import android.os.AsyncTask;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
/**
* <p>Main class for FFmpeg operations. Provides {@link #execute(String...)} method to execute
* FFmpeg commands.
* <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and
* asynchronous {@link #executeAsync(String, ExecuteCallback)} methods to execute FFmpeg commands.
* <pre>
* int rc = FFmpeg.execute("-i file1.mp4 -c:v libxvid file1.avi");
* Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
* </pre>
* <pre>
* long executionId = FFmpeg.executeAsync("-i file1.mp4 -c:v libxvid file1.avi", executeCallback);
* Log.i(Config.TAG, String.format("Asynchronous execution %d started.", executionId));
* </pre>
*
* @author Taner Sener
* @since v1.0
*/
public class FFmpeg {
static final long DEFAULT_EXECUTION_ID = 0;
private static final AtomicLong executionIdCounter = new AtomicLong(3000);
static {
AbiDetect.class.getName();
Config.class.getName();
@@ -50,14 +62,43 @@ public class FFmpeg {
* <p>Synchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
*/
public static int execute(final String[] arguments) {
final int lastReturnCode = Config.nativeFFmpegExecute(arguments);
return Config.ffmpegExecute(DEFAULT_EXECUTION_ID, arguments);
}
Config.setLastReturnCode(lastReturnCode);
/**
* <p>Asynchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback) {
final long newExecutionId = executionIdCounter.incrementAndGet();
return lastReturnCode;
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return newExecutionId;
}
/**
* <p>Asynchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback, final Executor executor) {
final long newExecutionId = executionIdCounter.incrementAndGet();
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(executor);
return newExecutionId;
}
/**
@@ -66,7 +107,7 @@ public class FFmpeg {
*
* @param command FFmpeg command
* @param delimiter delimiter used to split arguments
* @return zero on successful execution, 255 on user cancel and non-zero on error
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
* @since 3.0
* @deprecated argument splitting mechanism used in this method is pretty simple and prone to
* errors. Consider using a more advanced method like {@link #execute(String)} or
@@ -82,18 +123,76 @@ public class FFmpeg {
* your command.
*
* @param command FFmpeg command
* @return zero on successful execution, 255 on user cancel and non-zero on error
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
*/
public static int execute(final String command) {
return execute(parseArguments(command));
}
/**
* <p>Cancels an ongoing operation. This function does not wait for termination to complete and
* returns immediately.
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
*
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String command, final ExecuteCallback executeCallback) {
final long newExecutionId = executionIdCounter.incrementAndGet();
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return newExecutionId;
}
/**
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
*
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation
* @return returns a unique id that represents this execution
*/
public static long executeAsync(final String command, final ExecuteCallback executeCallback, final Executor executor) {
final long newExecutionId = executionIdCounter.incrementAndGet();
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(executor);
return newExecutionId;
}
/**
* <p>Cancels an ongoing operation.
*
* <p>This function does not wait for termination to complete and returns immediately.
*/
public static void cancel() {
Config.nativeFFmpegCancel();
Config.nativeFFmpegCancel(DEFAULT_EXECUTION_ID);
}
/**
* <p>Cancels an ongoing operation.
*
* <p>This function does not wait for termination to complete and returns immediately.
*
* @param executionId id of the execution
*/
public static void cancel(final long executionId) {
Config.nativeFFmpegCancel(executionId);
}
/**
* <p>Lists ongoing executions.
*
* @return list of ongoing executions
*/
public static List<FFmpegExecution> listExecutions() {
return Config.listFFmpegExecutions();
}
/**
@@ -153,4 +252,26 @@ public class FFmpeg {
return argumentList.toArray(new String[0]);
}
/**
* <p>Combines arguments into a string.
*
* @param arguments arguments
* @return string containing all arguments
*/
static String argumentsToString(final String[] arguments) {
if (arguments == null) {
return "null";
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < arguments.length; i++) {
if (i > 0) {
stringBuilder.append(" ");
}
stringBuilder.append(arguments[i]);
}
return stringBuilder.toString();
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import java.util.Date;
/**
* <p>Represents an ongoing FFmpeg execution.
*
* @author Taner Sener
* @since v4.4
*/
public class FFmpegExecution {
private final Date startTime;
private final long executionId;
private final String command;
public FFmpegExecution(final long executionId, final String[] arguments) {
this.startTime = new Date();
this.executionId = executionId;
this.command = FFmpeg.argumentsToString(arguments);
}
public Date getStartTime() {
return startTime;
}
public long getExecutionId() {
return executionId;
}
public String getCommand() {
return command;
}
}
@@ -1,526 +0,0 @@
/*
* 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 android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Surface;
import com.arthenica.mobileffmpeg.player.AudioHandler;
import com.arthenica.mobileffmpeg.player.ControllerHandler;
import com.arthenica.mobileffmpeg.player.PlayerManager;
import java.io.IOException;
import java.io.InputStream;
import static com.arthenica.mobileffmpeg.Config.TAG;
public class FFplay {
private static final boolean enabled;
private static boolean separateMouseAndTouch;
private static AudioHandler audioHandler;
private static ControllerHandler controllerHandler;
private static PlayerManager playerManager;
static {
/* MOUSE AND TOUCH IS THE SAME DEVICE BY DEFAULT */
separateMouseAndTouch = false;
/* FFPLAY DEPENDS ON SDL. CHECK WHETHER IT IS ENABLED OR NOT */
if (Config.getExternalLibraries().contains("sdl2")) {
init();
/* FFPLAY METHODS ARE ENABLED ONLY IF INITIALIZATION COMPLETES SUCCESSFULLY */
enabled = true;
} else {
enabled = false;
}
}
static void init() {
// ENABLE SDL FIRST
nativeSDLInit();
// COMPLETE OTHER COMPONENTS AFTER
nativePlayerInit();
nativeAudioInit();
nativeControllerInit();
}
/**
* <p>Synchronously executes FFplay with arguments provided.
*
* @param arguments FFplay command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
public static int execute(final String[] arguments) {
if (enabled) {
return nativePlayerRun(arguments);
} else {
throw new RuntimeException("sdl not found. FFplay requires sdl to run.");
}
}
public static boolean isSeparateMouseAndTouch() {
return separateMouseAndTouch;
}
public static void setSeparateMouseAndTouch(boolean separateMouseAndTouch) {
FFplay.separateMouseAndTouch = separateMouseAndTouch;
}
public static AudioHandler getAudioHandler() {
return audioHandler;
}
public static ControllerHandler getControllerHandler() {
return controllerHandler;
}
public static PlayerManager getPlayerManager() {
return playerManager;
}
public static void setAudioHandler(final AudioHandler newAudioHandler) {
audioHandler = newAudioHandler;
}
public static void setControllerManager(final ControllerHandler newControllerHandler) {
controllerHandler = newControllerHandler;
}
public static void setPlayerManager(PlayerManager newPlayerManager) {
playerManager = newPlayerManager;
}
public static String playerNativeGetHint(final String name) {
return nativePlayerNativeGetHint(name);
}
public static void inputSetComposingText(final String text, final int newCursorPosition) {
nativeInputSetComposingText(text, newCursorPosition);
}
public static void inputGenerateScancodeForUnichar(final char c) {
nativeInputGenerateScancodeForUnichar(c);
}
public static void inputCommitText(String text, int newCursorPosition) {
nativeInputCommitText(text, newCursorPosition);
}
public static void playerNativeSetenv(final String name, final String value) {
//@TODO We already have this method under Config
nativePlayerNativeSetenv(name, value);
}
public static void playerOnDropFile(final String filename) {
nativePlayerOnDropFile(filename);
}
public static void playerNativeLowMemory() {
nativePlayerNativeLowMemory();
}
public static void playerNativeQuit() {
nativePlayerNativeQuit();
}
public static void playerNativePause() {
nativePlayerNativePause();
}
public static void playerNativeResume() {
nativePlayerNativeResume();
}
public static void playerOnKeyDown(final int keyCode) {
nativePlayerOnKeyDown(keyCode);
}
public static void playerOnKeyUp(final int keyCode) {
nativePlayerOnKeyUp(keyCode);
}
public static void playerOnKeyboardFocusLost() {
nativePlayerOnKeyboardFocusLost();
}
public static void playerOnClipboardChanged() {
nativePlayerOnClipboardChanged();
}
public static void playerOnSurfaceChanged() {
nativePlayerOnSurfaceChanged();
}
public static void playerOnSurfaceDestroyed() {
nativePlayerOnSurfaceDestroyed();
}
public static void playerOnMouse(final int button, final int action, final float x, final float y) {
nativePlayerOnMouse(button, action, x, y);
}
public static int controllerAddHaptic(final int deviceId, final String name) {
return nativeControllerAddHaptic(deviceId, name);
}
public static int controllerRemoveHaptic(final int deviceId) {
return nativeControllerRemoveHaptic(deviceId);
}
public static int controllerAddJoystick(final int deviceId, final String name, final String desc, final int isAccelerometer, final int nButtons, final int nAxes, final int nHats, final int nBalls) {
return nativeControllerAddJoystick(deviceId, name, desc, isAccelerometer, nButtons, nAxes, nHats, nBalls);
}
public static int controllerRemoveJoystick(final int deviceId) {
return nativeControllerRemoveJoystick(deviceId);
}
public static void controllerOnJoy(final int deviceId, final int axis, final float value) {
nativeControllerOnJoy(deviceId, axis, value);
}
public static void controllerOnHat(final int deviceId, final int hatId, final int x, final int y) {
nativeControllerOnHat(deviceId, hatId, x, y);
}
public static void playerOnResize(final int x, final int y, final int format, final float rate) {
nativePlayerOnResize(x, y, format, rate);
}
public static int controllerOnPadDown(final int deviceId, final int keycode) {
return nativeControllerOnPadDown(deviceId, keycode);
}
public static int controllerOnPadUp(final int deviceId, final int keycode) {
return nativeControllerOnPadUp(deviceId, keycode);
}
public static void playerOnTouch(final int touchDevId, final int pointerFingerId, final int action, final float x, final float y, final float p) {
nativePlayerOnTouch(touchDevId, pointerFingerId, action, x, y, p);
}
public static void playerOnAccel(final float x, final float y, final float z) {
nativePlayerOnAccel(x, y, z);
}
/* AUDIO FUNCTIONS CALLED BY NATIVE THREADS */
static int audioOpen(final int sampleRate, final boolean is16Bit, final boolean isStereo, final int desiredFrames) {
if (audioHandler != null) {
return audioHandler.audioOpen(sampleRate, is16Bit, isStereo, desiredFrames);
} else {
return -1;
}
}
static void audioWriteShortBuffer(final short[] buffer) {
if (audioHandler != null) {
audioHandler.audioWriteShortBuffer(buffer);
}
}
static void audioWriteByteBuffer(final byte[] buffer) {
if (audioHandler != null) {
audioHandler.audioWriteByteBuffer(buffer);
}
}
static int captureOpen(final int sampleRate, final boolean is16Bit, final boolean isStereo, final int desiredFrames) {
if (audioHandler != null) {
return audioHandler.captureOpen(sampleRate, is16Bit, isStereo, desiredFrames);
} else {
return -1;
}
}
static int captureReadShortBuffer(final short[] buffer, final boolean blocking) {
if (audioHandler != null) {
return audioHandler.captureReadShortBuffer(buffer, blocking);
} else {
return -1;
}
}
static int captureReadByteBuffer(final byte[] buffer, final boolean blocking) {
if (audioHandler != null) {
return audioHandler.captureReadByteBuffer(buffer, blocking);
} else {
return -1;
}
}
static void audioClose() {
if (audioHandler != null) {
audioHandler.audioClose();
}
}
static void captureClose() {
if (audioHandler != null) {
audioHandler.captureClose();
}
}
/* CONTROLLER FUNCTIONS CALLED BY NATIVE THREADS */
static void pollInputDevices() {
if (controllerHandler != null) {
controllerHandler.pollInputDevices();
}
}
static void pollHapticDevices() {
if (controllerHandler != null) {
controllerHandler.pollHapticDevices();
}
}
static void hapticRun(final int deviceId, final int length) {
if (controllerHandler != null) {
controllerHandler.hapticRun(deviceId, length);
}
}
/* PLAYER FUNCTIONS CALLED BY NATIVE THREADS */
static boolean setActivityTitle(final String title) {
if (playerManager != null) {
return playerManager.setActivityTitle(title);
} else {
return true;
}
}
static void setWindowStyle(final boolean fullScreen) {
if (playerManager != null) {
playerManager.setWindowStyle(fullScreen);
}
}
static void setOrientation(final int w, final int h, final boolean resizable, final String hint) {
if (playerManager != null) {
playerManager.setOrientation(w, h, resizable, hint);
}
}
static boolean isScreenKeyboardShown() {
if (playerManager != null) {
return playerManager.isScreenKeyboardShown();
} else {
return false;
}
}
static boolean sendMessage(final int command, final int param) {
if (playerManager != null) {
return playerManager.sendMessage(command, param);
} else {
return false;
}
}
static Context getContext() {
if (playerManager != null) {
return playerManager.getContext();
} else {
return null;
}
}
static boolean isAndroidTV() {
if (playerManager != null) {
return playerManager.isAndroidTV();
} else {
return false;
}
}
static DisplayMetrics getDisplayDPI() {
if (playerManager != null) {
return playerManager.getDisplayDPI();
} else {
return null;
}
}
static boolean getManifestEnvironmentVariables() {
if (playerManager != null) {
return playerManager.getManifestEnvironmentVariables();
} else {
return false;
}
}
static boolean showTextInput(final int x, final int y, final int w, final int h) {
if (playerManager != null) {
return playerManager.showTextInput(x, y, w, h);
} else {
return false;
}
}
static Surface getNativeSurface() {
if (playerManager != null) {
return playerManager.getNativeSurface();
} else {
return null;
}
}
static int[] inputGetInputDeviceIds(final int sources) {
if (playerManager != null) {
return playerManager.inputGetInputDeviceIds(sources);
} else {
return new int[0];
}
}
static boolean clipboardHasText() {
if (playerManager != null) {
return playerManager.clipboardHasText();
} else {
return false;
}
}
static String clipboardGetText() {
if (playerManager != null) {
return playerManager.clipboardGetText();
} else {
return "";
}
}
static void clipboardSetText(final String string) {
if (playerManager != null) {
playerManager.clipboardSetText(string);
}
}
static InputStream openAPKExpansionInputStream(final String fileName) throws IOException {
Log.e(TAG, "Opening APK Expansion is not supported.");
return null;
}
static int messageBoxShowMessageBox(final int flags, final String title, final String message, final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors) {
if (playerManager != null) {
return playerManager.showMessageBox(flags, title, message, buttonFlags, buttonIds, buttonTexts, colors);
} else {
return -1;
}
}
/* NATIVE SDL FUNCTIONS */
/**
* <p>Initializes SDL for FFplay. It must be called before other SDL functions.
*/
native static void nativeSDLInit();
/* NATIVE PLAYER FUNCTIONS */
native static int nativePlayerInit();
/**
* <p>Synchronously executes FFplay natively with arguments provided.
*
* @param arguments FFplay command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
native static int nativePlayerRun(final String[] arguments);
native static void nativePlayerNativeLowMemory();
native static void nativePlayerNativeQuit();
native static void nativePlayerNativePause();
native static void nativePlayerNativeResume();
native static void nativePlayerOnDropFile(final String filename);
native static void nativePlayerOnResize(final int x, final int y, final int format, final float rate);
native static void nativePlayerOnKeyDown(final int keyCode);
native static void nativePlayerOnKeyUp(final int keyCode);
native static void nativePlayerOnKeyboardFocusLost();
native static void nativePlayerOnMouse(final int button, final int action, final float x, final float y);
native static void nativePlayerOnTouch(final int touchDevId, final int pointerFingerId, final int action, final float x, final float y, final float p);
native static void nativePlayerOnAccel(final float x, final float y, final float z);
native static void nativePlayerOnClipboardChanged();
native static void nativePlayerOnSurfaceChanged();
native static void nativePlayerOnSurfaceDestroyed();
native static String nativePlayerNativeGetHint(final String name);
native static void nativePlayerNativeSetenv(final String name, final String value);
/* NATIVE AUDIO FUNCTIONS */
native static int nativeAudioInit();
/* NATIVE CONTROLLER FUNCTIONS */
native static int nativeControllerInit();
native static int nativeControllerAddJoystick(final int deviceId, final String name, final String desc, final int isAccelerometer, final int nButtons, final int nAxes, final int nHats, final int nBalls);
native static int nativeControllerRemoveJoystick(final int deviceId);
native static int nativeControllerAddHaptic(final int deviceId, final String name);
native static int nativeControllerRemoveHaptic(final int deviceId);
native static int nativeControllerOnPadDown(final int deviceId, final int keycode);
native static int nativeControllerOnPadUp(final int deviceId, final int keycode);
native static void nativeControllerOnJoy(final int deviceId, final int axis, final float value);
native static void nativeControllerOnHat(final int deviceId, final int hatId, final int x, final int y);
/* NATIVE INPUT FUNCTIONS */
native static void nativeInputCommitText(final String text, final int newCursorPosition);
native static void nativeInputGenerateScancodeForUnichar(final char c);
native static void nativeInputSetComposingText(final String text, final int newCursorPosition);
}
@@ -72,7 +72,7 @@ public class FFprobe {
}
/**
* <p>Returns media information for given file.
* <p>Returns media information for the given file.
*
* <p>This method does not support executing multiple concurrent operations. If you execute
* multiple operations (execute or getMediaInformation) at the same time, the response of this
@@ -83,14 +83,22 @@ public class FFprobe {
* @since 3.0
*/
public static MediaInformation getMediaInformation(final String path) {
final int rc = execute(new String[]{"-v", "info", "-hide_banner", "-i", path});
return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path});
}
if (rc == 0) {
return MediaInformationParser.from(Config.getLastCommandOutput());
} else {
Log.i(Config.TAG, Config.getLastCommandOutput());
return null;
}
/**
* <p>Returns media information for the given command.
*
* <p>This method does not support executing multiple concurrent operations. If you execute
* multiple operations (execute or getMediaInformation) at the same time, the response of this
* method is not predictable.
*
* @param command command to execute
* @return media information
* @since 4.3.3
*/
public static MediaInformation getMediaInformationFromCommand(final String command) {
return getMediaInformationFromCommandArguments(FFmpeg.parseArguments(command));
}
/**
@@ -111,4 +119,15 @@ public class FFprobe {
return getMediaInformation(path);
}
private static MediaInformation getMediaInformationFromCommandArguments(final String[] arguments) {
final int rc = execute(arguments);
if (rc == 0) {
return MediaInformationParser.from(Config.getLastCommandOutput());
} else {
Log.w(Config.TAG, Config.getLastCommandOutput());
return null;
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018-2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to receive asynchronous getMediaInformation result.
*
* @author Taner Sener
*/
@FunctionalInterface
public interface GetMediaInformationCallback {
void apply(MediaInformation mediaInformation);
}
@@ -20,7 +20,7 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to redirect logs.
* <p>Represents a callback function to receive logs from running executions
*
* @author Taner Sener
* @since v2.1
@@ -20,21 +20,27 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a redirected log message.
* <p>Logs for running executions.
*
* @author Taner Sener
* @since v2.1
*/
public class LogMessage {
private final long executionId;
private final Level level;
private final String text;
public LogMessage(final Level level, final String text) {
public LogMessage(final long executionId, final Level level, final String text) {
this.executionId = executionId;
this.level = level;
this.text = text;
}
public long getExecutionId() {
return executionId;
}
public Level getLevel() {
return level;
}
@@ -43,4 +49,21 @@ public class LogMessage {
return text;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("LogMessage{");
stringBuilder.append("executionId=");
stringBuilder.append(executionId);
stringBuilder.append(", level=");
stringBuilder.append(level);
stringBuilder.append(", text=");
stringBuilder.append("\'");
stringBuilder.append(text);
stringBuilder.append('\'');
stringBuilder.append('}');
return stringBuilder.toString();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,11 +19,9 @@
package com.arthenica.mobileffmpeg;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONObject;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Media information class.
@@ -32,49 +30,38 @@ import java.util.Set;
*/
public class MediaInformation {
/**
* Format
*/
private String format;
private static final String KEY_MEDIA_PROPERTIES = "format";
private static final String KEY_FILENAME = "filename";
private static final String KEY_FORMAT = "format_name";
private static final String KEY_FORMAT_LONG = "format_long_name";
private static final String KEY_START_TIME = "start_time";
private static final String KEY_DURATION = "duration";
private static final String KEY_SIZE = "size";
private static final String KEY_BIT_RATE = "bit_rate";
private static final String KEY_TAGS = "tags";
/**
* Path
* Stores all properties.
*/
private String path;
private final JSONObject jsonObject;
/**
* Duration, in milliseconds
* Stores streams.
*/
private Long duration;
private final List<StreamInformation> streams;
public MediaInformation(final JSONObject jsonObject, final List<StreamInformation> streams) {
this.jsonObject = jsonObject;
this.streams = streams;
}
/**
* Start time, in milliseconds
* Returns file name.
*
* @return media file name
*/
private Long startTime;
/**
* Bitrate, kb/s
*/
private Long bitrate;
/**
* Metadata map
*/
private Map<String, String> metadata;
/**
* List of streams
*/
private List<StreamInformation> streams;
/**
* Raw unparsed media information
*/
private String rawInformation;
public MediaInformation() {
this.metadata = new HashMap<>();
this.streams = new ArrayList<>();
public String getFilename() {
return getStringProperty(KEY_FILENAME);
}
/**
@@ -83,34 +70,16 @@ public class MediaInformation {
* @return media format
*/
public String getFormat() {
return format;
return getStringProperty(KEY_FORMAT);
}
/**
* Sets media format.
* Returns long format.
*
* @param format media format
* @return media long format
*/
public void setFormat(String format) {
this.format = format;
}
/**
* Returns path.
*
* @return media path
*/
public String getPath() {
return path;
}
/**
* Sets path.
*
* @param path media path
*/
public void setPath(String path) {
this.path = path;
public String getLongFormat() {
return getStringProperty(KEY_FORMAT_LONG);
}
/**
@@ -118,17 +87,8 @@ public class MediaInformation {
*
* @return media duration in milliseconds
*/
public Long getDuration() {
return duration;
}
/**
* Sets duration.
*
* @param duration media duration in milliseconds
*/
public void setDuration(Long duration) {
this.duration = duration;
public String getDuration() {
return getStringProperty(KEY_DURATION);
}
/**
@@ -136,17 +96,17 @@ public class MediaInformation {
*
* @return media start time in milliseconds
*/
public Long getStartTime() {
return startTime;
public String getStartTime() {
return getStringProperty(KEY_START_TIME);
}
/**
* Sets start time.
* Returns size.
*
* @param startTime media start time in milliseconds
* @return media size in bytes
*/
public void setStartTime(Long startTime) {
this.startTime = startTime;
public String getSize() {
return getStringProperty(KEY_SIZE);
}
/**
@@ -154,63 +114,17 @@ public class MediaInformation {
*
* @return media bitrate in kb/s
*/
public Long getBitrate() {
return bitrate;
public String getBitrate() {
return getStringProperty(KEY_BIT_RATE);
}
/**
* Sets bitrate.
* Returns all tags.
*
* @param bitrate media bitrate in kb/s
* @return tags dictionary
*/
public void setBitrate(Long bitrate) {
this.bitrate = bitrate;
}
/**
* Returns unparsed media information.
*
* @return unparsed media information data
*/
public String getRawInformation() {
return rawInformation;
}
/**
* Sets unparsed media information.
*
* @param rawInformation unparsed media information data
*/
public void setRawInformation(String rawInformation) {
this.rawInformation = rawInformation;
}
/**
* Adds metadata.
*
* @param key metadata key
* @param value metadata value
*/
public void addMetadata(String key, String value) {
this.metadata.put(key, value);
}
/**
* Returns all metadata entries.
*
* @return set of metadata entries
*/
public Set<Map.Entry<String, String>> getMetadataEntries() {
return this.metadata.entrySet();
}
/**
* Adds new stream.
*
* @param stream new stream information
*/
public void addStream(StreamInformation stream) {
this.streams.add(stream);
public JSONObject getTags() {
return getProperties(KEY_TAGS);
}
/**
@@ -222,4 +136,75 @@ public class MediaInformation {
return streams;
}
/**
* Returns the media property associated with the key.
*
* @param key property key
* @return media property as string or null if the key is not found
*/
public String getStringProperty(final String key) {
JSONObject mediaProperties = getMediaProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optString(key);
} else {
return null;
}
}
/**
* Returns the media property associated with the key.
*
* @param key property key
* @return media property as Long or null if the key is not found
*/
public Long getNumberProperty(String key) {
JSONObject mediaProperties = getMediaProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optLong(key);
} else {
return null;
}
}
/**
* Returns the media properties associated with the key.
*
* @param key properties key
* @return media properties as a JSONObject or null if the key is not found
*/
public JSONObject getProperties(String key) {
JSONObject mediaProperties = getMediaProperties();
if (mediaProperties == null) {
return null;
}
return mediaProperties.optJSONObject(key);
}
/**
* Returns all media properties.
*
* @return all media properties as a JSONObject or null if no media properties are defined
*/
public JSONObject getMediaProperties() {
return jsonObject.optJSONObject(KEY_MEDIA_PROPERTIES);
}
/**
* Returns all properties defined.
*
* @return all properties as a JSONObject or null if no properties are defined
*/
public JSONObject getAllProperties() {
return jsonObject;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -21,505 +21,55 @@ package com.arthenica.mobileffmpeg;
import android.util.Log;
import com.arthenica.mobileffmpeg.util.Pair;
import com.arthenica.mobileffmpeg.util.Trio;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ArrayList;
/**
* Helper class for {@link MediaInformation}.
* Helper class for parsing {@link MediaInformation}.
*
* @since 3.0
*/
public class MediaInformationParser {
public static SimpleDateFormat DURATION_FORMAT;
public static Date REFERENCE_DURATION;
static {
/**
* Extracts MediaInformation from the given ffprobe json output.
*
* @param ffprobeJsonOutput ffprobe json output
* @return created {@link MediaInformation} instance of null if a parsing error occurs
*/
public static MediaInformation from(final String ffprobeJsonOutput) {
try {
DURATION_FORMAT = new SimpleDateFormat("kk:mm:ss", Locale.getDefault());
REFERENCE_DURATION = DURATION_FORMAT.parse("00:00:00");
} catch (final ParseException e) {
Log.i(Config.TAG, "Preparing duration reference failed.", e);
DURATION_FORMAT = null;
REFERENCE_DURATION = null;
return fromWithError(ffprobeJsonOutput);
} catch (JSONException e) {
Log.e(Config.TAG, "MediaInformation parsing failed.", e);
e.printStackTrace();
return null;
}
}
/**
* Parses media information command output and builds a {@link MediaInformation} instance.
* Extracts MediaInformation from the given ffprobe json output.
*
* @param rawCommandOutput media information command output
* @return parsed instance of null if a parsing error occurs
* @param ffprobeJsonOutput ffprobe json output
* @return created {@link MediaInformation} instance
* @throws JSONException if a parsing error occurs
*/
public static MediaInformation from(final String rawCommandOutput) {
final MediaInformation mediaInformation = new MediaInformation();
public static MediaInformation fromWithError(final String ffprobeJsonOutput) throws JSONException {
JSONObject jsonObject = new JSONObject(ffprobeJsonOutput);
JSONArray streamArray = jsonObject.optJSONArray("streams");
if (rawCommandOutput != null) {
final String[] split = rawCommandOutput.split("\n");
boolean metadata = false;
boolean sidedata = false;
StreamInformation lastCreatedStream = null;
final StringBuilder rawInformation = new StringBuilder();
for (final String outputLine : split) {
if (outputLine.startsWith("[")) {
metadata = false;
sidedata = false;
continue;
}
final String trimmedLine = outputLine.trim();
if (trimmedLine.startsWith("Input")) {
metadata = false;
sidedata = false;
lastCreatedStream = null;
Pair<String, String> pair = parseInputBlock(trimmedLine);
mediaInformation.setFormat(pair.getFirst());
mediaInformation.setPath(pair.getSecond());
} else if (trimmedLine.startsWith("Duration")) {
metadata = false;
sidedata = false;
lastCreatedStream = null;
Trio<Long, Long, Long> trio = parseDurationBlock(trimmedLine);
mediaInformation.setDuration(trio.getFirst());
mediaInformation.setStartTime(trio.getSecond());
mediaInformation.setBitrate(trio.getThird());
} else if (trimmedLine.toLowerCase(Locale.ENGLISH).startsWith("metadata")) {
sidedata = false;
metadata = true;
} else if (trimmedLine.toLowerCase(Locale.ENGLISH).startsWith("side data")) {
metadata = false;
sidedata = true;
} else if (trimmedLine.startsWith("Stream mapping") || trimmedLine.startsWith("Press [q] to stop") || trimmedLine.startsWith("Output")) {
break;
} else if (trimmedLine.startsWith("Stream")) {
metadata = false;
sidedata = false;
lastCreatedStream = MediaInformationParser.parseStreamBlock(trimmedLine);
mediaInformation.addStream(lastCreatedStream);
} else if (metadata) {
Pair<String, String> pair = parseMetadataBlock(trimmedLine);
if (pair.getFirst() != null && pair.getSecond() != null) {
if (lastCreatedStream != null) {
lastCreatedStream.addMetadata(pair.getFirst(), pair.getSecond());
} else {
mediaInformation.addMetadata(pair.getFirst(), pair.getSecond());
}
}
} else if (sidedata) {
Pair<String, String> pair = parseMetadataBlock(trimmedLine);
if (pair.getFirst() != null && pair.getSecond() != null) {
if (lastCreatedStream != null) {
lastCreatedStream.addSidedata(pair.getFirst(), pair.getSecond());
}
}
}
rawInformation.append(outputLine);
rawInformation.append("\n");
}
mediaInformation.setRawInformation(rawInformation.toString());
}
return mediaInformation;
}
static Pair<String, String> parseInputBlock(final String input) {
String format = substring(input, ",", ", from", Collections.<String>emptyList());
String path = substring(input, "\'", "\'", Collections.<String>emptyList());
return new Pair<>(format, path);
}
static Trio<Long, Long, Long> parseDurationBlock(final String line) {
Long duration = parseDuration(substring(line, "Duration:", ",", Collections.singletonList("uration:")));
Long start = parseStartTime(substring(line, "start:", ",", Collections.singletonList("tart:")));
Long bitrate = toLongObject(substring(line, "bitrate:", Arrays.asList("itrate:", "kb/s")));
return new Trio<>(duration, start, bitrate);
}
static Pair<String, String> parseMetadataBlock(final String metadata) {
String key = null;
String value = null;
if (metadata != null) {
int index = metadata.indexOf(':');
if (index > -1) {
key = metadata.substring(0, index).trim();
value = metadata.substring(index + 1).trim();
ArrayList<StreamInformation> arrayList = new ArrayList<>();
for (int i = 0; streamArray != null && i < streamArray.length(); i++) {
JSONObject streamObject = streamArray.optJSONObject(i);
if (streamObject != null) {
arrayList.add(new StreamInformation(streamObject));
}
}
return new Pair<>(key, value);
}
static StreamInformation parseStreamBlock(final String input) {
final StreamInformation streamInformation = new StreamInformation();
if (input != null) {
streamInformation.setIndex(parseStreamIndex(input));
int typeBlockStartIndex = index(input, ":", 0, 2);
if (typeBlockStartIndex > -1 && (typeBlockStartIndex < input.length())) {
String[] parts = input.substring(typeBlockStartIndex + 1).split(",");
String typePart = safeGet(parts, 0);
final String type = parseStreamType(typePart);
streamInformation.setType(type);
streamInformation.setCodec(parseStreamCodec(typePart));
streamInformation.setFullCodec(parseStreamFullCodec(typePart));
String part2 = safeGet(parts, 1);
String part3 = safeGet(parts, 2);
String part4 = safeGet(parts, 3);
String part5 = safeGet(parts, 4);
if ("video".equals(type)) {
int lastUsedPart = 1;
if (part2 != null) {
int pStart = count(part2, "(");
int pEnd = count(part2, ")");
while (pStart != pEnd) {
lastUsedPart++;
String newPart = safeGet(parts, lastUsedPart);
if (newPart == null) {
break;
}
part2 = String.format("%s,%s", part2, newPart);
pStart = count(part2, "(");
pEnd = count(part2, ")");
}
streamInformation.setFullFormat(part2.toLowerCase(Locale.getDefault()).trim());
streamInformation.setFormat(part2.replaceAll("\\(.*\\)", "").toLowerCase(Locale.getDefault()).trim());
}
lastUsedPart++;
String videoDimensionPart = safeGet(parts, lastUsedPart);
if (videoDimensionPart != null) {
String videoLayout = videoDimensionPart.toLowerCase(Locale.getDefault()).trim();
Pair<Long, Long> dimensions = parseVideoDimensions(videoLayout);
streamInformation.setWidth(dimensions.getFirst());
streamInformation.setHeight(dimensions.getSecond());
streamInformation.setSampleAspectRatio(parseVideoStreamSampleAspectRatio(videoLayout));
streamInformation.setDisplayAspectRatio(parseVideoStreamDisplayAspectRatio(videoLayout));
}
for (int i = lastUsedPart + 1; i < parts.length; i++) {
String part = parts[i].replaceAll("\\(.*\\)", "").toLowerCase(Locale.getDefault());
if (part.contains("kb/s")) {
streamInformation.setBitrate(toLongObject(part.replaceAll("kb/s", "").trim()));
} else if (part.contains("fps")) {
streamInformation.setAverageFrameRate(part.replaceAll("fps", "").trim());
} else if (part.contains("tbr")) {
streamInformation.setRealFrameRate(part.replaceAll("tbr", "").trim());
} else if (part.contains("tbn")) {
streamInformation.setTimeBase(part.replaceAll("tbn", "").trim());
} else if (part.contains("tbc")) {
streamInformation.setCodecTimeBase(part.replaceAll("tbc", "").trim());
}
}
} else if ("audio".equals(type)) {
if (part2 != null) {
streamInformation.setSampleRate(parseAudioStreamSampleRate(part2));
}
if (part3 != null) {
streamInformation.setChannelLayout(part3.toLowerCase(Locale.getDefault()).trim());
}
if (part4 != null) {
streamInformation.setSampleFormat(part4.toLowerCase(Locale.getDefault()).trim());
}
if (part5 != null) {
streamInformation.setBitrate(toLongObject(part5.toLowerCase(Locale.getDefault()).replaceAll("\\(.*\\)", "").replaceAll("kb/s", "").trim()));
}
} else if ("data".equals(type)) {
if (part2 != null) {
streamInformation.setBitrate(toLongObject(part2.toLowerCase(Locale.getDefault()).replaceAll("\\(.*\\)", "").replaceAll("kb/s", "").trim()));
}
}
}
}
return streamInformation;
}
static Pair<Long, Long> parseVideoDimensions(final String input) {
Long width = null;
Long height = null;
if (input != null) {
final String[] dimensions = input.toLowerCase(Locale.getDefault()).replaceAll("\\[.*\\]", "").trim().split("x");
width = toLongObject(safeGet(dimensions, 0));
height = toLongObject(safeGet(dimensions, 1));
}
return new Pair<>(width, height);
}
static String parseVideoStreamSampleAspectRatio(final String input) {
if (input != null) {
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
for (int i = 0; i < parts.length; i++) {
if (parts[i].toLowerCase(Locale.getDefault()).equals("sar")) {
return safeGet(parts, i + 1);
}
}
}
return null;
}
static String parseVideoStreamDisplayAspectRatio(final String input) {
if (input != null) {
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
for (int i = 0; i < parts.length; i++) {
if (parts[i].toLowerCase(Locale.getDefault()).equals("dar")) {
return safeGet(parts, i + 1);
}
}
}
return null;
}
static Long parseAudioStreamSampleRate(final String input) {
if (input != null) {
boolean khz = false;
boolean mhz = false;
String lowerCase = input.toLowerCase(Locale.getDefault());
if (lowerCase.contains("khz")) {
khz = true;
}
if (lowerCase.contains("mhz")) {
mhz = true;
}
String sampleRate = lowerCase
.replaceAll("khz", "")
.replaceAll("mhz", "")
.replaceAll("hz", "")
.trim();
if (khz) {
return 1000 * toLong(sampleRate);
} else if (mhz) {
return 1000000 * toLong(sampleRate);
} else {
return toLong(sampleRate);
}
}
return null;
}
static String parseStreamType(final String input) {
if (input != null) {
if (input.toLowerCase(Locale.getDefault()).contains("audio:")) {
return "audio";
} else if (input.toLowerCase(Locale.getDefault()).contains("video:")) {
return "video";
} else if (input.toLowerCase(Locale.getDefault()).contains("data:")) {
return "data";
}
}
return null;
}
static String parseStreamCodec(final String input) {
if (input != null) {
return input.toLowerCase(Locale.getDefault())
.replaceAll("\\(.*\\)", "")
.replaceAll("video:", "")
.replaceAll("audio:", "")
.replaceAll("data:", "")
.trim();
}
return null;
}
static String parseStreamFullCodec(final String input) {
if (input != null) {
return input.toLowerCase(Locale.getDefault())
.replaceAll("video:", "")
.replaceAll("audio:", "")
.replaceAll("data:", "")
.trim();
}
return null;
}
static Long parseStreamIndex(final String input) {
String substring = substring(input, "Stream #0:", ":", Collections.singletonList("tream #0"));
if (substring != null) {
// DISCARD PARANTHESIS
return toLongObject(substring
.replace(":", "")
.replaceAll("\\(.*\\)", ""));
}
return null;
}
static Long parseDuration(final String duration) {
if (duration == null || duration.equals("N/A")) {
return null;
}
try {
final Date calculated = DURATION_FORMAT.parse(duration);
long secondsPartInMilliseconds = calculated.getTime() - REFERENCE_DURATION.getTime();
int index = duration.indexOf('.');
if (index > -1) {
Long centiSeconds = toLong(duration.substring(index + 1));
secondsPartInMilliseconds += 10 * centiSeconds;
}
return secondsPartInMilliseconds;
} catch (final ParseException e) {
Log.d(Config.TAG, String.format("Parsing duration: %s failed.", duration), e);
return null;
}
}
static Long parseStartTime(final String startTime) {
if (startTime == null || startTime.equals("N/A")) {
return null;
}
try {
BigDecimal bigDecimal = new BigDecimal(startTime);
bigDecimal = bigDecimal.setScale(3, BigDecimal.ROUND_CEILING).multiply(new BigDecimal(1000));
return bigDecimal.longValue();
} catch (NumberFormatException e) {
Log.d(Config.TAG, String.format("Parsing startTime: %s failed.", startTime), e);
return null;
}
}
public static String substring(final String string, final String start, final String end, final List<String> ignoredTokens) {
String extractedSubstring = null;
if (string != null) {
int formatStart = string.indexOf(start);
if (formatStart > -1) {
int formatEnd = string.indexOf(end, formatStart + start.length());
if (formatEnd > -1) {
extractedSubstring = string.substring(formatStart + start.length(), formatEnd);
}
}
}
if ((ignoredTokens != null) && (extractedSubstring != null)) {
for (String token : ignoredTokens) {
extractedSubstring = extractedSubstring.replaceAll(token, "");
}
}
return (extractedSubstring == null) ? null : extractedSubstring.trim();
}
public static String substring(final String string, final String start, final List<String> ignoredTokens) {
String extractedSubstring = null;
if (string != null) {
int formatStart = string.indexOf(start);
if (formatStart > -1) {
extractedSubstring = string.substring(formatStart + 1);
}
}
if ((ignoredTokens != null) && (extractedSubstring != null)) {
for (String token : ignoredTokens) {
extractedSubstring = extractedSubstring.replaceAll(token, "");
}
}
return (extractedSubstring == null) ? null : extractedSubstring.trim();
}
public static int index(final String string, String substring, int startIndex, int n) {
int count = 1;
while (count <= n) {
startIndex = string.indexOf(substring, startIndex + substring.length());
count++;
}
return startIndex;
}
public static int count(final String string, String substring) {
int count = 0;
int index = 0;
do {
index = string.indexOf(substring, index);
if (index >= 0) {
count++;
index = index + substring.length();
}
} while (index >= 0);
return count;
}
private static <K> K safeGet(final K[] array, final int index) {
if (array == null) {
return null;
}
try {
final int size = array.length;
if (size > index) {
return array[index];
} else {
return null;
}
} catch (final ArrayIndexOutOfBoundsException e) {
return null;
}
}
private static long toLong(final String value) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return 0;
}
}
static Long toLongObject(final String value) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return null;
}
return new MediaInformation(jsonObject, arrayList);
}
}
@@ -54,6 +54,7 @@ class Packages {
supportedExternalLibraries.add("opencore-amr");
supportedExternalLibraries.add("openh264");
supportedExternalLibraries.add("opus");
supportedExternalLibraries.add("rubberband");
supportedExternalLibraries.add("sdl2");
supportedExternalLibraries.add("shine");
supportedExternalLibraries.add("snappy");
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
/**
* <p>Lists signals handled by MobileFFmpeg library.
*
* @author Taner Sener
* @since v4.4
*/
public enum Signal {
SIGINT(2),
SIGQUIT(3),
SIGPIPE(13),
SIGTERM(15),
SIGXCPU(24);
private int value;
Signal(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
@@ -20,13 +20,14 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents statistics data.
* <p>Statistics for running executions.
*
* @author Taner Sener
* @since v2.1
*/
public class Statistics {
private long executionId;
private int videoFrameNumber;
private float videoFps;
private float videoQuality;
@@ -36,6 +37,7 @@ public class Statistics {
private double speed;
public Statistics() {
executionId = 0;
videoFrameNumber = 0;
videoFps = 0;
videoQuality = 0;
@@ -45,7 +47,8 @@ public class Statistics {
speed = 0;
}
public Statistics(int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
public Statistics(long executionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
this.executionId = executionId;
this.videoFrameNumber = videoFrameNumber;
this.videoFps = videoFps;
this.videoQuality = videoQuality;
@@ -57,35 +60,44 @@ public class Statistics {
public void update(final Statistics newStatistics) {
if (newStatistics != null) {
this.executionId = newStatistics.getExecutionId();
if (newStatistics.getVideoFrameNumber() > 0) {
this.videoFrameNumber = newStatistics.getVideoFrameNumber();
}
if (newStatistics.getVideoFps() > 0){
if (newStatistics.getVideoFps() > 0) {
this.videoFps = newStatistics.getVideoFps();
}
if (newStatistics.getVideoQuality() > 0){
if (newStatistics.getVideoQuality() > 0) {
this.videoQuality = newStatistics.getVideoQuality();
}
if (newStatistics.getSize() > 0){
if (newStatistics.getSize() > 0) {
this.size = newStatistics.getSize();
}
if (newStatistics.getTime() > 0){
if (newStatistics.getTime() > 0) {
this.time = newStatistics.getTime();
}
if (newStatistics.getBitrate() > 0){
if (newStatistics.getBitrate() > 0) {
this.bitrate = newStatistics.getBitrate();
}
if (newStatistics.getSpeed() > 0){
if (newStatistics.getSpeed() > 0) {
this.speed = newStatistics.getSpeed();
}
}
}
public long getExecutionId() {
return executionId;
}
public void setExecutionId(long executionId) {
this.executionId = executionId;
}
public int getVideoFrameNumber() {
return videoFrameNumber;
}
@@ -142,4 +154,30 @@ public class Statistics {
this.speed = speed;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Statistics{");
stringBuilder.append("executionId=");
stringBuilder.append(executionId);
stringBuilder.append(", videoFrameNumber=");
stringBuilder.append(videoFrameNumber);
stringBuilder.append(", videoFps=");
stringBuilder.append(videoFps);
stringBuilder.append(", videoQuality=");
stringBuilder.append(videoQuality);
stringBuilder.append(", size=");
stringBuilder.append(size);
stringBuilder.append(", time=");
stringBuilder.append(time);
stringBuilder.append(", bitrate=");
stringBuilder.append(bitrate);
stringBuilder.append(", speed=");
stringBuilder.append(speed);
stringBuilder.append('}');
return stringBuilder.toString();
}
}
@@ -20,7 +20,7 @@
package com.arthenica.mobileffmpeg;
/**
* <p>Represents a callback function to receive statistics of running operation.
* <p>Represents a callback function to receive statistics from running executions.
*
* @author Taner Sener
* @since v2.1
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
@@ -19,9 +19,7 @@
package com.arthenica.mobileffmpeg;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.json.JSONObject;
/**
* Stream information class.
@@ -30,68 +28,32 @@ import java.util.Set;
*/
public class StreamInformation {
/**
* Stream index
*/
private Long index;
private String type;
private String codec;
private String fullCodec;
private String format;
private String fullFormat;
private Long width;
private Long height;
private Long bitrate;
private Long sampleRate;
private String sampleFormat;
private String channelLayout;
private static final String KEY_INDEX = "index";
private static final String KEY_TYPE = "codec_type";
private static final String KEY_CODEC = "codec_name";
private static final String KEY_CODEC_LONG = "codec_long_name";
private static final String KEY_FORMAT = "pix_fmt";
private static final String KEY_WIDTH = "width";
private static final String KEY_HEIGHT = "height";
private static final String KEY_BIT_RATE = "bit_rate";
private static final String KEY_SAMPLE_RATE = "sample_rate";
private static final String KEY_SAMPLE_FORMAT = "sample_fmt";
private static final String KEY_CHANNEL_LAYOUT = "channel_layout";
private static final String KEY_SAMPLE_ASPECT_RATIO = "sample_aspect_ratio";
private static final String KEY_DISPLAY_ASPECT_RATIO = "display_aspect_ratio";
private static final String KEY_AVERAGE_FRAME_RATE = "avg_frame_rate";
private static final String KEY_REAL_FRAME_RATE = "r_frame_rate";
private static final String KEY_TIME_BASE = "time_base";
private static final String KEY_CODEC_TIME_BASE = "codec_time_base";
private static final String KEY_TAGS = "tags";
/**
* SAR
* Stores all properties.
*/
private String sampleAspectRatio;
private final JSONObject jsonObject;
/**
* DAR
*/
private String displayAspectRatio;
/**
* fps
*/
private String averageFrameRate;
/**
* tbr
*/
private String realFrameRate;
/**
* tbn
*/
private String timeBase;
/**
* tbc
*/
private String codecTimeBase;
/**
* Metadata map
*/
private final Map<String, String> metadata;
/**
* Side data map
*/
private final Map<String, String> sidedata;
public StreamInformation() {
this.metadata = new HashMap<>();
this.sidedata = new HashMap<>();
public StreamInformation(final JSONObject jsonObject) {
this.jsonObject = jsonObject;
}
/**
@@ -100,16 +62,7 @@ public class StreamInformation {
* @return stream index, starting from zero
*/
public Long getIndex() {
return index;
}
/**
* Sets stream index.
*
* @param index stream index, starting from zero
*/
public void setIndex(Long index) {
this.index = index;
return getNumberProperty(KEY_INDEX);
}
/**
@@ -118,16 +71,7 @@ public class StreamInformation {
* @return stream type; audio or video
*/
public String getType() {
return type;
}
/**
* Sets stream type.
*
* @param type stream type; audio or video
*/
public void setType(String type) {
this.type = type;
return getStringProperty(KEY_TYPE);
}
/**
@@ -136,16 +80,7 @@ public class StreamInformation {
* @return stream codec
*/
public String getCodec() {
return codec;
}
/**
* Sets stream codec.
*
* @param codec stream codec
*/
public void setCodec(String codec) {
this.codec = codec;
return getStringProperty(KEY_CODEC);
}
/**
@@ -154,16 +89,7 @@ public class StreamInformation {
* @return stream codec with additional profile and mode information
*/
public String getFullCodec() {
return fullCodec;
}
/**
* Sets full stream codec.
*
* @param fullCodec stream codec with additional profile and mode information
*/
public void setFullCodec(String fullCodec) {
this.fullCodec = fullCodec;
return getStringProperty(KEY_CODEC_LONG);
}
/**
@@ -172,34 +98,7 @@ public class StreamInformation {
* @return stream format
*/
public String getFormat() {
return format;
}
/**
* Sets stream format.
*
* @param format stream format
*/
public void setFormat(String format) {
this.format = format;
}
/**
* Returns full stream format.
*
* @return stream format with
*/
public String getFullFormat() {
return fullFormat;
}
/**
* Sets full stream format.
*
* @param fullFormat stream format with
*/
public void setFullFormat(String fullFormat) {
this.fullFormat = fullFormat;
return getStringProperty(KEY_FORMAT);
}
/**
@@ -208,16 +107,7 @@ public class StreamInformation {
* @return width in pixels
*/
public Long getWidth() {
return width;
}
/**
* Sets width.
*
* @param width width in pixels
*/
public void setWidth(Long width) {
this.width = width;
return getNumberProperty(KEY_WIDTH);
}
/**
@@ -226,16 +116,7 @@ public class StreamInformation {
* @return height in pixels
*/
public Long getHeight() {
return height;
}
/**
* Sets height.
*
* @param height height in pixels
*/
public void setHeight(Long height) {
this.height = height;
return getNumberProperty(KEY_HEIGHT);
}
/**
@@ -243,17 +124,8 @@ public class StreamInformation {
*
* @return bitrate in kb/s
*/
public Long getBitrate() {
return bitrate;
}
/**
* Sets bitrate.
*
* @param bitrate bitrate in kb/s
*/
public void setBitrate(Long bitrate) {
this.bitrate = bitrate;
public String getBitrate() {
return getStringProperty(KEY_BIT_RATE);
}
/**
@@ -261,17 +133,8 @@ public class StreamInformation {
*
* @return sample rate in hz
*/
public Long getSampleRate() {
return sampleRate;
}
/**
* Sets sample rate.
*
* @param sampleRate sample rate in hz
*/
public void setSampleRate(Long sampleRate) {
this.sampleRate = sampleRate;
public String getSampleRate() {
return getStringProperty(KEY_SAMPLE_RATE);
}
/**
@@ -280,16 +143,7 @@ public class StreamInformation {
* @return sample format
*/
public String getSampleFormat() {
return sampleFormat;
}
/**
* Sets sample format.
*
* @param sampleFormat sample format
*/
public void setSampleFormat(String sampleFormat) {
this.sampleFormat = sampleFormat;
return getStringProperty(KEY_SAMPLE_FORMAT);
}
/**
@@ -298,16 +152,7 @@ public class StreamInformation {
* @return channel layout
*/
public String getChannelLayout() {
return channelLayout;
}
/**
* Sets channel layout.
*
* @param channelLayout channel layout
*/
public void setChannelLayout(String channelLayout) {
this.channelLayout = channelLayout;
return getStringProperty(KEY_CHANNEL_LAYOUT);
}
/**
@@ -316,16 +161,7 @@ public class StreamInformation {
* @return sample aspect ratio
*/
public String getSampleAspectRatio() {
return sampleAspectRatio;
}
/**
* Sets sample aspect ratio.
*
* @param sampleAspectRatio sample aspect ratio
*/
public void setSampleAspectRatio(String sampleAspectRatio) {
this.sampleAspectRatio = sampleAspectRatio;
return getStringProperty(KEY_SAMPLE_ASPECT_RATIO);
}
/**
@@ -334,16 +170,7 @@ public class StreamInformation {
* @return display aspect ratio
*/
public String getDisplayAspectRatio() {
return displayAspectRatio;
}
/**
* Sets display aspect ratio.
*
* @param displayAspectRatio display aspect ratio
*/
public void setDisplayAspectRatio(String displayAspectRatio) {
this.displayAspectRatio = displayAspectRatio;
return getStringProperty(KEY_DISPLAY_ASPECT_RATIO);
}
/**
@@ -352,16 +179,7 @@ public class StreamInformation {
* @return average frame rate in fps
*/
public String getAverageFrameRate() {
return averageFrameRate;
}
/**
* Sets average frame rate.
*
* @param averageFrameRate average frame rate in fps
*/
public void setAverageFrameRate(String averageFrameRate) {
this.averageFrameRate = averageFrameRate;
return getStringProperty(KEY_AVERAGE_FRAME_RATE);
}
/**
@@ -370,16 +188,7 @@ public class StreamInformation {
* @return real frame rate in tbr
*/
public String getRealFrameRate() {
return realFrameRate;
}
/**
* Sets real frame rate.
*
* @param realFrameRate real frame rate in tbr
*/
public void setRealFrameRate(String realFrameRate) {
this.realFrameRate = realFrameRate;
return getStringProperty(KEY_REAL_FRAME_RATE);
}
/**
@@ -388,16 +197,7 @@ public class StreamInformation {
* @return time base in tbn
*/
public String getTimeBase() {
return timeBase;
}
/**
* Sets time base.
*
* @param timeBase time base in tbn
*/
public void setTimeBase(String timeBase) {
this.timeBase = timeBase;
return getStringProperty(KEY_TIME_BASE);
}
/**
@@ -406,74 +206,78 @@ public class StreamInformation {
* @return codec time base in tbc
*/
public String getCodecTimeBase() {
return codecTimeBase;
return getStringProperty(KEY_CODEC_TIME_BASE);
}
/**
* Sets codec time base.
* Returns all tags.
*
* @param codecTimeBase codec time base in tbc
* @return tags dictionary
*/
public void setCodecTimeBase(String codecTimeBase) {
this.codecTimeBase = codecTimeBase;
public JSONObject getTags() {
return getProperties(KEY_TAGS);
}
/**
* Adds metadata.
* Returns the stream property associated with the key.
*
* @param key metadata key
* @param value metadata value
* @param key property key
* @return stream property as string or null if the key is not found
*/
public void addMetadata(String key, String value) {
this.metadata.put(key, value);
public String getStringProperty(final String key) {
JSONObject mediaProperties = getAllProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optString(key);
} else {
return null;
}
}
/**
* Retrieves metadata value associated with this key.
* Returns the stream property associated with the key.
*
* @param key metadata key
* @return metadata value associated with this key
* @param key property key
* @return stream property as Long or null if the key is not found
*/
public String getMetadata(String key) {
return this.metadata.get(key);
public Long getNumberProperty(String key) {
JSONObject mediaProperties = getAllProperties();
if (mediaProperties == null) {
return null;
}
if (mediaProperties.has(key)) {
return mediaProperties.optLong(key);
} else {
return null;
}
}
/**
* Returns all metadata entries.
* Returns the stream properties associated with the key.
*
* @return set of metadata entries
* @param key properties key
* @return stream properties as a JSONObject or null if the key is not found
*/
public Set<Map.Entry<String, String>> getMetadataEntries() {
return this.metadata.entrySet();
public JSONObject getProperties(String key) {
JSONObject mediaProperties = getAllProperties();
if (mediaProperties == null) {
return null;
}
return mediaProperties.optJSONObject(key);
}
/**
* Adds side data.
* Returns all stream properties defined.
*
* @param key side data key
* @param value side data value
* @return all stream properties as a JSONObject or null if no properties are defined
*/
public void addSidedata(String key, String value) {
this.sidedata.put(key, value);
}
/**
* Retrieves side data value associated with this key.
*
* @param key side data key
* @return side data value associated with this key
*/
public String getSidedata(String key) {
return this.sidedata.get(key);
}
/**
* Returns all side data entries.
*
* @return set of site data entries
*/
public Set<Map.Entry<String, String>> getSidedataEntries() {
return this.sidedata.entrySet();
public JSONObject getAllProperties() {
return jsonObject;
}
}
@@ -1,42 +0,0 @@
/*
* 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.player;
public interface AudioHandler {
void initialize();
int audioOpen(final int sampleRate, final boolean is16Bit, final boolean isStereo, final int desiredFrames);
void audioWriteShortBuffer(final short[] buffer);
void audioWriteByteBuffer(final byte[] buffer);
int captureOpen(final int sampleRate, final boolean is16Bit, final boolean isStereo, final int desiredFrames);
int captureReadShortBuffer(final short[] buffer, final boolean blocking);
int captureReadByteBuffer(final byte[] buffer, final boolean blocking);
void audioClose();
void captureClose();
}
@@ -1,39 +0,0 @@
/*
* 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.player;
import android.content.Context;
import android.view.MotionEvent;
public interface ControllerHandler {
void initialize(final Context context);
boolean handleJoystickMotionEvent(final MotionEvent event);
void pollInputDevices();
void pollHapticDevices();
void hapticRun(final int deviceId, final int length);
boolean isDeviceSDLJoystick(final int deviceId);
}
@@ -1,180 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLActivity renamed as FullScreenActivity
*/
package com.arthenica.mobileffmpeg.player;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import com.arthenica.mobileffmpeg.FFplay;
import static com.arthenica.mobileffmpeg.Config.TAG;
import static com.arthenica.mobileffmpeg.player.PlayerSession.FFPLAY_COMMAND;
import static com.arthenica.mobileffmpeg.player.PlayerSession.NativeState;
public class FullScreenActivity extends Activity {
protected PlayerSurface playerSurface;
protected ViewGroup viewLayout;
protected PlayerSession playerSession;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String ffplayCommand = intent.getStringExtra(FFPLAY_COMMAND);
if (ffplayCommand == null) {
Log.i(TAG, "FullScreenActivity created with empty ffplay command.");
} else {
Log.v(TAG, "FullScreenActivity created.");
}
playerSession = new PlayerSession(getRequestedOrientation(), this, ffplayCommand);
playerSurface = new PlayerSurface(this);
playerSurface.init(this, new GenericMotionListener(), playerSession);
viewLayout = new RelativeLayout(this);
viewLayout.addView(playerSurface);
setContentView(viewLayout);
setWindowStyle(false);
}
@Override
protected void onPause() {
Log.v(TAG, "FullScreenActivity paused.");
setNextNativeState(NativeState.PAUSED);
setResumedCalled(false);
handleNativeState();
super.onPause();
}
@Override
protected void onResume() {
Log.v(TAG, "FullScreenActivity resumed.");
setNextNativeState(NativeState.RESUMED);
setResumedCalled(true);
handleNativeState();
super.onResume();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
Log.v(TAG, String.format("FullScreenActivity window focus changed, hasFocus: %s.", hasFocus));
setHasFocus(hasFocus);
if (hasFocus) {
setNextNativeState(NativeState.RESUMED);
} else {
setNextNativeState(NativeState.PAUSED);
}
handleNativeState();
super.onWindowFocusChanged(hasFocus);
}
@Override
public void onLowMemory() {
Log.v(TAG, "FullScreenActivity is on low memory.");
FFplay.playerNativeLowMemory();
super.onLowMemory();
}
@Override
protected void onDestroy() {
Log.v(TAG, "FullScreenActivity destroyed.");
setNextNativeState(NativeState.PAUSED);
handleNativeState();
// Send a quit message to the application
FFplay.playerNativeQuit();
super.onDestroy();
}
@Override
public boolean dispatchKeyEvent(final KeyEvent event) {
int keyCode = event.getKeyCode();
// Ignore certain special keys so they're handled by Android
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_CAMERA ||
keyCode == KeyEvent.KEYCODE_ZOOM_IN ||
keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {
return false;
}
return super.dispatchKeyEvent(event);
}
protected void setNextNativeState(final NativeState nextNativeState) {
PlayerSession playerSession = this.playerSession;
if (playerSession != null) {
playerSession.setNextNativeState(nextNativeState);
}
}
protected void handleNativeState() {
PlayerSurface playerSurface = this.playerSurface;
if (playerSurface != null) {
playerSurface.handleNativeState();
}
}
protected void setHasFocus(final boolean hasFocus) {
PlayerSurface playerSurface = this.playerSurface;
if (playerSurface != null) {
playerSurface.setHasFocus(hasFocus);
}
}
protected void setResumedCalled(final boolean resumedCalled) {
PlayerSurface playerSurface = this.playerSurface;
if (playerSurface != null) {
playerSurface.setResumedCalled(resumedCalled);
}
}
protected void setWindowStyle(final boolean fullScreen) {
PlayerSurface playerSurface = this.playerSurface;
if (playerSurface != null) {
playerSurface.setWindowStyle(fullScreen);
}
}
public PlayerSurface getPlayerSurface() {
return playerSurface;
}
public PlayerSession getPlayerSession() {
return playerSession;
}
}
@@ -1,191 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLAudioHandler renamed as GenericAudioHandler
*/
package com.arthenica.mobileffmpeg.player;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import static com.arthenica.mobileffmpeg.Config.TAG;
public class GenericAudioHandler implements AudioHandler {
protected AudioTrack audioTrack;
protected AudioRecord audioRecord;
public void initialize() {
audioTrack = null;
audioRecord = null;
}
public int audioOpen(final int sampleRate, final boolean is16Bit, final boolean isStereo, int desiredFrames) {
int channelConfig = isStereo ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO;
int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
Log.v(TAG, String.format("AudioHandler audio: wanted %s %s %skHz, %d frames buffer.", isStereo ? "stereo" : "mono", is16Bit ? "16-bit" : "8-bit", sampleRate / 1000f, desiredFrames));
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
if (audioTrack == null) {
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
Log.e(TAG, "AudioHandler failed during initialization of AudioTrack.");
audioTrack = null;
return -1;
}
audioTrack.play();
}
Log.v(TAG, String.format("AudioHandler audio: got %s %s %skHz, %d frames buffer.", (audioTrack.getChannelCount() >= 2) ? "stereo" : "mono", (audioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit", audioTrack.getSampleRate() / 1000f, desiredFrames));
return 0;
}
public void audioWriteShortBuffer(final short[] buffer) {
if (audioTrack == null) {
Log.e(TAG, "AudioHandler attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = audioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "AudioHandler audio: error return from write(short).");
return;
}
}
}
public void audioWriteByteBuffer(final byte[] buffer) {
if (audioTrack == null) {
Log.e(TAG, "AudioHandler attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = audioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "AudioHandler audio: error return from write(byte).");
return;
}
}
}
public int captureOpen(final int sampleRate, final boolean is16Bit, final boolean isStereo, int desiredFrames) {
int channelConfig = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
Log.v(TAG, String.format("AudioHandler capture: wanted %s %s %skHz, %d frames buffer.", isStereo ? "stereo" : "mono", is16Bit ? "16-bit" : "8-bit", sampleRate / 1000f, desiredFrames));
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
if (audioRecord == null) {
audioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize);
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "AudioHandler failed during initialization of AudioRecord.");
audioRecord.release();
audioRecord = null;
return -1;
}
audioRecord.startRecording();
}
Log.v(TAG, String.format("AudioHandler capture: got %s %s %skHz, %d frames buffer.", (audioRecord.getChannelCount() >= 2) ? "stereo" : "mono", (audioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit", audioRecord.getSampleRate() / 1000f, desiredFrames));
return 0;
}
public int captureReadShortBuffer(final short[] buffer, final boolean blocking) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return audioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
} else {
return audioRecord.read(buffer, 0, buffer.length);
}
}
public int captureReadByteBuffer(final byte[] buffer, final boolean blocking) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return audioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
} else {
return audioRecord.read(buffer, 0, buffer.length);
}
}
public void audioClose() {
if (audioTrack != null) {
audioTrack.stop();
audioTrack.release();
audioTrack = null;
}
}
public void captureClose() {
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
}
}
@@ -1,110 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
package com.arthenica.mobileffmpeg.player;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
import static com.arthenica.mobileffmpeg.Config.TAG;
/**
* A Handler class for Messages from native SDL applications.
* It uses current Activities as target (e.g. for the title).
* static to prevent implicit references to enclosing object.
*/
public class GenericCommandHandler extends Handler {
public static final int COMMAND_CHANGE_TITLE = 1;
public static final int COMMAND_CHANGE_WINDOW_STYLE = 2;
public static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
protected final Context context;
public GenericCommandHandler(final Context context) {
this.context = context;
}
@Override
public void handleMessage(final Message message) {
switch (message.arg1) {
case COMMAND_CHANGE_TITLE:
if (context instanceof Activity) {
((Activity) context).setTitle((String) message.obj);
} else {
Log.e(TAG, "CommandHandler error handling message, getContext() returned no Activity.");
}
break;
case COMMAND_CHANGE_WINDOW_STYLE:
if (Build.VERSION.SDK_INT < 19) {
// This version of Android doesn't support the immersive fullscreen mode
break;
}
/* This needs more testing, per bug 4096 - Enabling fullscreen on Android causes the app to toggle fullscreen mode continuously in a loop
***
if (context instanceof Activity) {
Window window = ((Activity) context).getWindow();
if (window != null) {
if ((message.obj instanceof Integer) && (((Integer) message.obj).intValue() != 0)) {
int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
window.getDecorView().setSystemUiVisibility(flags);
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
window.getDecorView().setSystemUiVisibility(flags);
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
} else {
Log.e(TAG, "error handling message, getContext() returned no Activity");
}
***/
break;
case COMMAND_SET_KEEP_SCREEN_ON: {
if (context instanceof Activity) {
Window window = ((Activity) context).getWindow();
if (window != null) {
if ((message.obj instanceof Integer) && (((Integer) message.obj).intValue() != 0)) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
}
break;
}
default:
Log.e(TAG, String.format("CommandHandler error handling message, command is %d.", message.arg1));
}
}
}
@@ -1,81 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLControllerHandler renamed as GenericControllerHandler
*/
package com.arthenica.mobileffmpeg.player;
import android.content.Context;
import android.view.InputDevice;
import android.view.MotionEvent;
public class GenericControllerHandler implements ControllerHandler {
protected GenericJoystickHandler genericJoystickHandler;
protected GenericHapticHandler genericHapticHandler;
public void initialize(final Context context) {
genericJoystickHandler = new GenericJoystickHandler(this);
genericHapticHandler = new GenericHapticHandler(context);
}
/**
* Joystick glue code, just a series of stubs that redirect to the JoystickHandler instance.
*/
public boolean handleJoystickMotionEvent(final MotionEvent event) {
return genericJoystickHandler.handleMotionEvent(event);
}
public void pollInputDevices() {
genericJoystickHandler.pollInputDevices();
}
public void pollHapticDevices() {
genericHapticHandler.pollHapticDevices();
}
public void hapticRun(final int deviceId, final int length) {
genericHapticHandler.run(deviceId, length);
}
/**
* Check if a given device is considered a possible SDL joystick.
*
* @param deviceId device identifier
* @return true if device is a joystick, false otherwise
*/
public boolean isDeviceSDLJoystick(final int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
if ((device == null) || device.isVirtual() || (deviceId < 0)) {
return false;
}
int sources = device.getSources();
return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) ||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
);
}
}
@@ -1,148 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLHapticHandler renamed as GenericHapticHandler
* - SDLHaptic class renamed as Haptic
* - Haptic class refactored
*/
package com.arthenica.mobileffmpeg.player;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Vibrator;
import android.view.InputDevice;
import com.arthenica.mobileffmpeg.FFplay;
import java.util.ArrayList;
/**
* <p>Generic haptic handler for FFplay Controller.
*/
public class GenericHapticHandler {
public static class Haptic {
public final int deviceId;
public final String name;
public final Vibrator vibrator;
public Haptic(final int deviceId, final String name, final Vibrator vibrator) {
this.deviceId = deviceId;
this.name = name;
this.vibrator = vibrator;
}
}
protected final ArrayList<Haptic> hapticList;
protected final Vibrator vibratorService;
public GenericHapticHandler(final Context context) {
hapticList = new ArrayList<>();
vibratorService = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
@SuppressLint("MissingPermission")
public void run(final int deviceId, final int length) {
Haptic haptic = getHaptic(deviceId);
if (haptic != null) {
haptic.vibrator.vibrate(length);
}
}
public void pollHapticDevices() {
final int DEVICE_ID_VIBRATOR_SERVICE = 999999;
boolean hasVibratorService = false;
final int[] deviceIdArray = InputDevice.getDeviceIds();
// It helps processing the device ids in reverse order
// For example, in the case of the XBox 360 wireless dongle,
// so the first controller seen by SDL matches what the receiver
// considers to be the first controller
for (int i = deviceIdArray.length - 1; i > -1; i--) {
Haptic haptic = getHaptic(deviceIdArray[i]);
if (haptic == null) {
InputDevice device = InputDevice.getDevice(deviceIdArray[i]);
Vibrator vib = device.getVibrator();
if (vib.hasVibrator()) {
haptic = new Haptic(deviceIdArray[i], device.getName(), vib);
hapticList.add(haptic);
FFplay.controllerAddHaptic(haptic.deviceId, haptic.name);
}
}
}
/* Check VIBRATOR_SERVICE */
if (vibratorService != null) {
hasVibratorService = vibratorService.hasVibrator();
if (hasVibratorService) {
Haptic haptic = getHaptic(DEVICE_ID_VIBRATOR_SERVICE);
if (haptic == null) {
haptic = new Haptic(DEVICE_ID_VIBRATOR_SERVICE, "VIBRATOR_SERVICE", vibratorService);
hapticList.add(haptic);
FFplay.controllerAddHaptic(haptic.deviceId, haptic.name);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = new ArrayList<>();
for (int i = 0; i < hapticList.size(); i++) {
int deviceId = hapticList.get(i).deviceId;
int j;
for (j = 0; j < deviceIdArray.length; j++) {
if (deviceId == deviceIdArray[j]) break;
}
if (deviceId == DEVICE_ID_VIBRATOR_SERVICE && hasVibratorService) {
// don't remove the vibrator if it is still present
} else if (j == deviceIdArray.length) {
removedDevices.add(deviceId);
}
}
for (int i = 0; i < removedDevices.size(); i++) {
int deviceId = removedDevices.get(i);
FFplay.controllerRemoveHaptic(deviceId);
for (int j = 0; j < hapticList.size(); j++) {
if (hapticList.get(j).deviceId == deviceId) {
hapticList.remove(j);
break;
}
}
}
}
protected Haptic getHaptic(final int deviceId) {
for (int i = 0; i < hapticList.size(); i++) {
if (hapticList.get(i).deviceId == deviceId) {
return hapticList.get(i);
}
}
return null;
}
}
@@ -1,179 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLJoystickHandler renamed as GenericJoystickHandler
* - SDLJoystick class renamed as Joystick
* - Joystick class refactored
*/
package com.arthenica.mobileffmpeg.player;
import android.view.InputDevice;
import android.view.MotionEvent;
import com.arthenica.mobileffmpeg.FFplay;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* <p>Generic joystick handler for FFplay Controller.
*/
public class GenericJoystickHandler {
public static class Joystick {
public final int deviceId;
public final String name;
public final String desc;
public final ArrayList<InputDevice.MotionRange> axes;
public final ArrayList<InputDevice.MotionRange> hats;
public Joystick(final int deviceId, final String name, final String desc, final ArrayList<InputDevice.MotionRange> axes, final ArrayList<InputDevice.MotionRange> hats) {
this.deviceId = deviceId;
this.name = name;
this.desc = desc;
this.axes = axes;
this.hats = hats;
}
}
public static class RangeComparator implements Comparator<InputDevice.MotionRange> {
@Override
public int compare(final InputDevice.MotionRange arg0, final InputDevice.MotionRange arg1) {
return arg0.getAxis() - arg1.getAxis();
}
}
protected final ArrayList<Joystick> joystickList;
protected final ControllerHandler controllerHandler;
public GenericJoystickHandler(final ControllerHandler controllerHandler) {
this.joystickList = new ArrayList<>();
this.controllerHandler = controllerHandler;
}
public void pollInputDevices() {
int[] deviceIds = InputDevice.getDeviceIds();
// It helps processing the device ids in reverse order
// For example, in the case of the XBox 360 wireless dongle,
// so the first controller seen by SDL matches what the receiver
// considers to be the first controller
for (int i = deviceIds.length - 1; i > -1; i--) {
Joystick joystick = getJoystick(deviceIds[i]);
if (joystick == null) {
InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
if (controllerHandler.isDeviceSDLJoystick(deviceIds[i])) {
joystick = new Joystick(deviceIds[i], joystickDevice.getName(), getJoystickDescriptor(joystickDevice), new ArrayList<InputDevice.MotionRange>(), new ArrayList<InputDevice.MotionRange>());
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
Collections.sort(ranges, new RangeComparator());
for (InputDevice.MotionRange range : ranges) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
joystick.hats.add(range);
} else {
joystick.axes.add(range);
}
}
}
joystickList.add(joystick);
FFplay.controllerAddJoystick(joystick.deviceId, joystick.name, joystick.desc, 0, -1, joystick.axes.size(), joystick.hats.size() / 2, 0);
}
}
}
/* Check removed devices */
ArrayList<Integer> removedDevices = new ArrayList<>();
for (int i = 0; i < joystickList.size(); i++) {
int deviceId = joystickList.get(i).deviceId;
int j;
for (j = 0; j < deviceIds.length; j++) {
if (deviceId == deviceIds[j]) break;
}
if (j == deviceIds.length) {
removedDevices.add(deviceId);
}
}
for (int i = 0; i < removedDevices.size(); i++) {
int deviceId = removedDevices.get(i);
FFplay.controllerRemoveJoystick(deviceId);
for (int j = 0; j < joystickList.size(); j++) {
if (joystickList.get(j).deviceId == deviceId) {
joystickList.remove(j);
break;
}
}
}
}
protected Joystick getJoystick(int deviceId) {
for (int i = 0; i < joystickList.size(); i++) {
if (joystickList.get(i).deviceId == deviceId) {
return joystickList.get(i);
}
}
return null;
}
public boolean handleMotionEvent(final MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
Joystick joystick = getJoystick(event.getDeviceId());
if (joystick != null) {
for (int i = 0; i < joystick.axes.size(); i++) {
InputDevice.MotionRange range = joystick.axes.get(i);
/* Normalize the value to -1...1 */
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
FFplay.controllerOnJoy(joystick.deviceId, i, value);
}
for (int i = 0; i < joystick.hats.size(); i += 2) {
int hatX = Math.round(event.getAxisValue(joystick.hats.get(i).getAxis(), actionPointerIndex));
int hatY = Math.round(event.getAxisValue(joystick.hats.get(i + 1).getAxis(), actionPointerIndex));
FFplay.controllerOnHat(joystick.deviceId, i / 2, hatX, hatY);
}
}
}
}
return true;
}
public String getJoystickDescriptor(final InputDevice joystickDevice) {
String desc = joystickDevice.getDescriptor();
if (desc != null && !desc.isEmpty()) {
return desc;
}
return joystickDevice.getName();
}
}
@@ -1,90 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLGenericMotionListener renamed as GenericMotionListener
*/
package com.arthenica.mobileffmpeg.player;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import com.arthenica.mobileffmpeg.FFplay;
/**
* <p>Generic motion listener for FFplay Player.
*/
public class GenericMotionListener implements View.OnGenericMotionListener {
@Override
public boolean onGenericMotion(final View view, final MotionEvent event) {
ControllerHandler controllerHandler = FFplay.getControllerHandler();
float x, y;
int action;
switch (event.getSource()) {
case InputDevice.SOURCE_JOYSTICK:
case InputDevice.SOURCE_GAMEPAD:
case InputDevice.SOURCE_DPAD: {
if (controllerHandler != null) {
return controllerHandler.handleJoystickMotionEvent(event);
} else {
return false;
}
}
case InputDevice.SOURCE_MOUSE: {
if (!FFplay.isSeparateMouseAndTouch()) {
break;
}
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL: {
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
FFplay.playerOnMouse(0, action, x, y);
return true;
}
case MotionEvent.ACTION_HOVER_MOVE: {
x = event.getX(0);
y = event.getY(0);
FFplay.playerOnMouse(0, action, x, y);
return true;
}
default: {
break;
}
}
break;
}
default: {
break;
}
}
return false;
}
}
@@ -1,92 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLClipboardHandler renamed as PlayerClipboard
* - Added null checks for clipboardManager field
*/
package com.arthenica.mobileffmpeg.player;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import com.arthenica.mobileffmpeg.FFplay;
/**
* <p>Clipboard for FFplay Player.
*/
public class PlayerClipboard implements ClipboardManager.OnPrimaryClipChangedListener {
protected final Context context;
protected final ClipboardManager clipboardManager;
public PlayerClipboard(final Context context) {
this.context = context;
this.clipboardManager = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
if (this.clipboardManager != null) {
this.clipboardManager.addPrimaryClipChangedListener(this);
}
}
public boolean clipboardHasText() {
if (clipboardManager != null) {
final ClipData clip = clipboardManager.getPrimaryClip();
return (clip != null) && (clip.getItemCount() > 0);
}
return false;
}
public String clipboardGetText() {
CharSequence text = null;
if (clipboardManager != null) {
ClipData clip = clipboardManager.getPrimaryClip();
if (clip != null && clip.getItemCount() > 0) {
text = clip.getItemAt(0).coerceToText(context);
}
}
if (text != null) {
return text.toString();
} else {
return null;
}
}
public void clipboardSetText(final String string) {
if (clipboardManager != null) {
clipboardManager.removePrimaryClipChangedListener(this);
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, string));
clipboardManager.addPrimaryClipChangedListener(this);
}
}
@Override
public void onPrimaryClipChanged() {
FFplay.playerOnClipboardChanged();
}
}
@@ -1,62 +0,0 @@
/*
* 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.player;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.Surface;
public interface PlayerManager {
void initialize();
boolean setActivityTitle(final String title);
void setWindowStyle(final boolean fullScreen);
void setOrientation(final int w, final int h, final boolean resizable, final String hint);
boolean isScreenKeyboardShown();
boolean sendMessage(final int command, final int param);
Context getContext();
boolean isAndroidTV();
DisplayMetrics getDisplayDPI();
boolean getManifestEnvironmentVariables();
boolean showTextInput(final int x, final int y, final int w, final int h);
Surface getNativeSurface();
int[] inputGetInputDeviceIds(final int sources);
boolean clipboardHasText();
String clipboardGetText();
void clipboardSetText(final String string);
int showMessageBox(final int flags, final String title, final String message, final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors);
}
@@ -1,93 +0,0 @@
/*
* 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.player;
import android.content.Context;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.FFplay;
import com.arthenica.mobileffmpeg.util.AsyncSingleFFplayExecuteTask;
public class PlayerSession {
public static final String FFPLAY_COMMAND = "ffplayCommand";
public enum NativeState {
INIT, RESUMED, PAUSED
}
protected int requestedOrientation;
protected Context context;
protected final String command;
protected NativeState nextNativeState;
protected NativeState currentNativeState;
public PlayerSession(final int requestedOrientation, final Context context, final String command) {
this.requestedOrientation = requestedOrientation;
this.context = context;
this.command = command;
}
public NativeState getNextNativeState() {
return nextNativeState;
}
public void setNextNativeState(final NativeState nextNativeState) {
this.nextNativeState = nextNativeState;
}
public NativeState getCurrentNativeState() {
return currentNativeState;
}
public void setCurrentNativeState(final NativeState currentNativeState) {
this.currentNativeState = currentNativeState;
}
public Context getContext() {
return context;
}
public void setContext(final Context context) {
this.context = context;
}
public int getRequestedOrientation() {
return requestedOrientation;
}
public AsyncTask<String, Integer, Integer> execute() {
FFplay.setAudioHandler(new GenericAudioHandler());
FFplay.setControllerManager(new GenericControllerHandler());
AudioHandler audioHandler = FFplay.getAudioHandler();
if (audioHandler != null) {
audioHandler.initialize();
}
ControllerHandler controllerHandler = FFplay.getControllerHandler();
if (controllerHandler != null) {
controllerHandler.initialize(context);
}
AsyncSingleFFplayExecuteTask task = new AsyncSingleFFplayExecuteTask(command);
return task.execute("");
}
}
@@ -1,683 +0,0 @@
/*
* Simple DirectMedia Layer
* Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
* CHANGES 07.2020
* - SDLSurface renamed as PlayerSurface
*/
package com.arthenica.mobileffmpeg.player;
import android.app.Activity;
import android.app.UiModeManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import com.arthenica.mobileffmpeg.FFplay;
import com.arthenica.mobileffmpeg.player.PlayerSession.NativeState;
import java.util.Arrays;
import static android.content.Context.UI_MODE_SERVICE;
import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
import static com.arthenica.mobileffmpeg.Config.TAG;
import static com.arthenica.mobileffmpeg.player.GenericCommandHandler.COMMAND_CHANGE_TITLE;
import static com.arthenica.mobileffmpeg.player.GenericCommandHandler.COMMAND_CHANGE_WINDOW_STYLE;
/**
* PlayerSurface. This is what we draw on, so we need to know when it's created in order to do
* anything useful.
* <p>
* Because of this, that's where we set up the SDL thread
*/
public class PlayerSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnKeyListener, View.OnTouchListener, SensorEventListener, PlayerManager {
protected Activity activity;
protected Handler commandHandler;
protected PlayerClipboard playerClipboard;
protected SensorManager sensorManager;
protected Display display;
protected float width, height;
protected PlayerSession playerSession;
protected boolean ready;
protected boolean resumedCalled;
protected boolean hasFocus;
public PlayerSurface(final Context context) {
super(context);
onCreated(context);
}
public PlayerSurface(final Context context, final AttributeSet attrs) {
super(context, attrs);
onCreated(context);
}
public PlayerSurface(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
onCreated(context);
}
public PlayerSurface(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
onCreated(context);
}
protected void onCreated(final Context context) {
Log.v(TAG, String.format("PlayerSurface created on device: %s and model: %s.", android.os.Build.DEVICE, android.os.Build.MODEL));
getHolder().addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
commandHandler = new GenericCommandHandler(context);
playerClipboard = new PlayerClipboard(context);
display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
// Some arbitrary defaults to avoid a potential division by zero
width = 1.0f;
height = 1.0f;
ready = false;
}
public void init(final Activity activity, final View.OnGenericMotionListener motionListener, final PlayerSession playerSession) {
this.activity = activity;
setOnGenericMotionListener(motionListener);
FFplay.setPlayerManager(this);
this.playerSession = playerSession;
}
public void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
public void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
public Surface getNativeSurface() {
return getHolder().getSurface();
}
@Override
public void surfaceCreated(final SurfaceHolder ignored) {
}
@Override
public void surfaceDestroyed(final SurfaceHolder holder) {
// Transition to pause, if needed
setNextNativeState(NativeState.PAUSED);
handleNativeState();
ready = false;
FFplay.playerOnSurfaceDestroyed();
}
@Override
public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) {
int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
switch (format) {
case PixelFormat.A_8:
Log.v(TAG, "PlayerSurface using pixel format A_8");
break;
case PixelFormat.LA_88:
Log.v(TAG, "PlayerSurface using pixel format LA_88");
break;
case PixelFormat.L_8:
Log.v(TAG, "PlayerSurface using pixel format L_8");
break;
case PixelFormat.RGBA_4444:
Log.v(TAG, "PlayerSurface using pixel format RGBA_4444");
sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
break;
case PixelFormat.RGBA_5551:
Log.v(TAG, "PlayerSurface using pixel format RGBA_5551");
sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
break;
case PixelFormat.RGBA_8888:
Log.v(TAG, "PlayerSurface using pixel format RGBA_8888");
sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
break;
case PixelFormat.RGBX_8888:
Log.v(TAG, "PlayerSurface using pixel format RGBX_8888");
sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
break;
case PixelFormat.RGB_332:
Log.v(TAG, "PlayerSurface using pixel format RGB_332");
sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
break;
case PixelFormat.RGB_565:
Log.v(TAG, "PlayerSurface using pixel format RGB_565");
sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
break;
case PixelFormat.RGB_888:
Log.v(TAG, "PlayerSurface using pixel format RGB_888");
// Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
break;
default:
Log.v(TAG, String.format("PlayerSurface using pixel format unknown %d", format));
break;
}
this.width = width;
this.height = height;
FFplay.playerOnResize(width, height, sdlFormat, display.getRefreshRate());
Log.v(TAG, String.format("PlayerSurface window size: %dx%d.", width, height));
boolean skip = false;
int requestedOrientation = getRequestedOrientation();
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
// Accept any
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
if (this.width > this.height) {
skip = true;
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (this.width < this.height) {
skip = true;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if (skip) {
double min = Math.min(this.width, this.height);
double max = Math.max(this.width, this.height);
if (max / min < 1.20) {
Log.v(TAG, "PlayerSurface don't skip on such aspect-ratio. Could be a square resolution.");
skip = false;
}
}
if (skip) {
Log.v(TAG, "PlayerSurface skip .. Surface is not ready.");
ready = false;
return;
}
/* Surface is ready */
ready = true;
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
FFplay.playerOnSurfaceChanged();
handleNativeState();
}
@Override
public boolean onKey(final View v, final int keyCode, final KeyEvent event) {
final ControllerHandler controllerHandler = FFplay.getControllerHandler();
// Dispatch the different events depending on where they come from
// Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
// So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
//
// Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
// SOURCE_JOYSTICK, while its key events arrive from the keyboard source
// So, retrieve the device itself and check all of its sources
if (controllerHandler != null && controllerHandler.isDeviceSDLJoystick(event.getDeviceId())) {
// Note that we process events with specific key codes here
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (FFplay.controllerOnPadDown(event.getDeviceId(), keyCode) == 0) {
return true;
}
} else if (event.getAction() == KeyEvent.ACTION_UP) {
if (FFplay.controllerOnPadUp(event.getDeviceId(), keyCode) == 0) {
return true;
}
}
}
if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (isTextInputEvent(event)) {
FFplay.inputCommitText(String.valueOf((char) event.getUnicodeChar()), 1);
}
FFplay.playerOnKeyDown(keyCode);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
FFplay.playerOnKeyUp(keyCode);
return true;
}
}
if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
// they are ignored here because sending them as mouse input to SDL is messy
if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
case KeyEvent.ACTION_UP:
// mark the event as handled or it will be handled by system
// handling KEYCODE_BACK by system will call onBackPressed()
return true;
}
}
}
return false;
}
@Override
public boolean onTouch(final View v, final MotionEvent event) {
final int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerFingerId;
int mouseButton;
int i = -1;
float x, y, p;
if (event.getSource() == InputDevice.SOURCE_MOUSE && FFplay.isSeparateMouseAndTouch()) {
try {
mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
} catch (Exception e) {
mouseButton = 1; // oh well.
}
FFplay.playerOnMouse(mouseButton, action, event.getX(0), event.getY(0));
} else {
switch (action) {
case MotionEvent.ACTION_MOVE:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / width;
y = event.getY(i) / height;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
FFplay.playerOnTouch(touchDevId, pointerFingerId, action, x, y, p);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
// Primary pointer up/down, the index is always zero
i = 0;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN:
// Non primary pointer up/down
if (i == -1) {
i = event.getActionIndex();
}
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / width;
y = event.getY(i) / height;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
FFplay.playerOnTouch(touchDevId, pointerFingerId, action, x, y, p);
break;
case MotionEvent.ACTION_CANCEL:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / width;
y = event.getY(i) / height;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
FFplay.playerOnTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
}
break;
default:
break;
}
}
return true;
}
@Override
public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
// TODO
}
@Override
public void onSensorChanged(final SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x, y;
switch (display.getRotation()) {
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
break;
case Surface.ROTATION_180:
x = -event.values[1];
y = -event.values[0];
break;
default:
x = event.values[0];
y = event.values[1];
break;
}
FFplay.playerOnAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
event.values[2] / SensorManager.GRAVITY_EARTH);
}
}
public void enableSensor(final int sensorType, final boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
sensorManager.registerListener(this,
sensorManager.getDefaultSensor(sensorType),
SensorManager.SENSOR_DELAY_GAME, null);
} else {
sensorManager.unregisterListener(this, sensorManager.getDefaultSensor(sensorType));
}
}
int getRequestedOrientation() {
final PlayerSession playerSession = this.playerSession;
if (playerSession == null) {
return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
} else {
return playerSession.getRequestedOrientation();
}
}
public void setNextNativeState(final NativeState nextNativeState) {
final PlayerSession playerSession = this.playerSession;
if (playerSession != null) {
playerSession.setNextNativeState(nextNativeState);
}
}
public void setCurrentNativeState(final NativeState currentNativeState) {
final PlayerSession playerSession = this.playerSession;
if (playerSession != null) {
playerSession.setCurrentNativeState(currentNativeState);
}
}
public static boolean isTextInputEvent(final KeyEvent event) {
if (event.isCtrlPressed()) {
return false;
}
return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
}
public void handleNativeState() {
Log.v(TAG, String.format("PlayerSurface handling nativeState with ready: %s, hasFocus: %s, resumed: %s.", ready, hasFocus, resumedCalled));
NativeState currentNativeState = null;
NativeState nextNativeState = null;
if (playerSession != null) {
currentNativeState = playerSession.getCurrentNativeState();
nextNativeState = playerSession.getNextNativeState();
}
Log.v(TAG, String.format("PlayerSurface handling nativeState with current:%s, next: %s.", currentNativeState, nextNativeState));
if (nextNativeState == currentNativeState) {
// Already in same state, discard.
return;
}
// Try a transition to init state
if (nextNativeState == NativeState.INIT) {
setCurrentNativeState(nextNativeState);
return;
}
// Try a transition to paused state
if (nextNativeState == NativeState.PAUSED) {
FFplay.playerNativePause();
handlePause();
setCurrentNativeState(nextNativeState);
return;
}
// Try a transition to resumed state
if (nextNativeState == NativeState.RESUMED) {
play();
}
}
public void play() {
if (ready && resumedCalled) {
initialize();
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
if (playerSession != null) {
playerSession.execute();
}
FFplay.playerNativeResume();
handleResume();
setCurrentNativeState(NativeState.RESUMED);
} else {
Log.v(TAG, String.format("PlayerSurface play failed for ready:%s, hasFocus: %s, resumed: %s.", ready, hasFocus, resumedCalled));
}
}
public void setHasFocus(final boolean hasFocus) {
this.hasFocus = hasFocus;
}
public void setResumedCalled(final boolean resumedCalled) {
this.resumedCalled = resumedCalled;
}
public boolean sendCommand(final int command, final Object data) {
Message message = commandHandler.obtainMessage();
message.arg1 = command;
message.obj = data;
return commandHandler.sendMessage(message);
}
public void initialize() {
setHasFocus(true);
setNextNativeState(NativeState.INIT);
setCurrentNativeState(NativeState.INIT);
}
public boolean setActivityTitle(final String title) {
return sendCommand(COMMAND_CHANGE_TITLE, title);
}
public void setWindowStyle(final boolean fullScreen) {
sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullScreen ? 1 : 0);
}
public void setOrientation(final int w, final int h, final boolean resizable, final String hint) {
int orientation = -1;
if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
} else if (hint.contains("LandscapeRight")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
} else if (hint.contains("LandscapeLeft")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
} else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
} else if (hint.contains("Portrait")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else if (hint.contains("PortraitUpsideDown")) {
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
}
/* no valid hint */
if (orientation == -1) {
if (resizable) {
/* no fixed orientation */
} else {
if (w > h) {
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
} else {
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
}
}
}
Log.v(TAG, String.format("PlayerSurface set orientation:%d, width:%d, height:%d, resizable:%s and hint:%s.", orientation, w, h, resizable, hint));
if (orientation != -1) {
activity.setRequestedOrientation(orientation);
}
}
public boolean isScreenKeyboardShown() {
return false;
}
public boolean sendMessage(final int command, final int param) {
return sendCommand(command, param);
}
public boolean isAndroidTV() {
UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);
return (uiModeManager.getCurrentModeType() == UI_MODE_TYPE_TELEVISION);
}
public DisplayMetrics getDisplayDPI() {
return getContext().getResources().getDisplayMetrics();
}
public boolean getManifestEnvironmentVariables() {
try {
ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = applicationInfo.metaData;
if (bundle == null) {
return false;
}
String prefix = "SDL_ENV.";
final int trimLength = prefix.length();
for (String key : bundle.keySet()) {
if (key.startsWith(prefix)) {
String name = key.substring(trimLength);
String value = bundle.get(key).toString();
FFplay.playerNativeSetenv(name, value);
}
}
return true;
} catch (final Exception e) {
Log.i(TAG, "PlayerSurface failed to set environment variables. " + e.toString());
}
return false;
}
public boolean showTextInput(final int x, final int y, final int w, final int h) {
return false;
}
public int[] inputGetInputDeviceIds(final int sources) {
int[] ids = InputDevice.getDeviceIds();
int[] filtered = new int[ids.length];
int used = 0;
for (int id : ids) {
InputDevice device = InputDevice.getDevice(id);
if ((device != null) && ((device.getSources() & sources) != 0)) {
filtered[used++] = device.getId();
}
}
return Arrays.copyOf(filtered, used);
}
public int showMessageBox(
final int flags,
final String title,
final String message,
final int[] buttonFlags,
final int[] buttonIds,
final String[] buttonTexts,
final int[] colors) {
Log.i(TAG, String.format("PlayerSurface was asked to showMessageBox for title: %s, message: %s and %d buttons: %s.", title, message, (buttonIds != null) ? buttonIds.length : 0, Arrays.toString(buttonTexts)));
return -1;
}
public boolean clipboardHasText() {
return playerClipboard.clipboardHasText();
}
public String clipboardGetText() {
return playerClipboard.clipboardGetText();
}
public void clipboardSetText(final String string) {
playerClipboard.clipboardSetText(string);
}
}
@@ -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,52 +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 android.util.Log;
import com.arthenica.mobileffmpeg.FFplay;
import static com.arthenica.mobileffmpeg.Config.TAG;
public class AsyncSingleFFplayExecuteTask extends AsyncTask<String, Integer, Integer> {
private final String command;
public AsyncSingleFFplayExecuteTask(final String command) {
this.command = command;
}
@Override
protected Integer doInBackground(final String... ignored) {
Log.v(TAG, String.format("Running FFplay for %s.", command));
int rc = FFplay.execute(command.split(" "));
Log.v(TAG, String.format("Finished running FFplay for %s.", command));
return rc;
}
@Override
protected void onPostExecute(final Integer ignored) {
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (c) 2018-2019 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFprobe;
public class AsyncSingleFFprobeExecuteTask extends AsyncTask<String, Integer, Integer> {
private final String command;
private final SingleExecuteCallback singleExecuteCallback;
public AsyncSingleFFprobeExecuteTask(final String command, final SingleExecuteCallback singleExecuteCallback) {
this.command = command;
this.singleExecuteCallback = singleExecuteCallback;
}
@Override
protected Integer doInBackground(final String... arguments) {
return FFprobe.execute(command);
}
@Override
protected void onPostExecute(final Integer rc) {
if (singleExecuteCallback != null) {
singleExecuteCallback.apply(rc, Config.getLastCommandOutput());
}
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (c) 2018-2019 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
import android.os.AsyncTask;
import com.arthenica.mobileffmpeg.FFprobe;
import com.arthenica.mobileffmpeg.MediaInformation;
public class AsyncSingleGetMediaInformationTask extends AsyncTask<String, MediaInformation, MediaInformation> {
private final String path;
private final SingleGetMediaInformationCallback singleGetMediaInformationCallback;
public AsyncSingleGetMediaInformationTask(final String path, final SingleGetMediaInformationCallback singleGetMediaInformationCallback) {
this.path = path;
this.singleGetMediaInformationCallback = singleGetMediaInformationCallback;
}
@Override
protected MediaInformation doInBackground(final String... arguments) {
return FFprobe.getMediaInformation(path);
}
@Override
protected void onPostExecute(final MediaInformation mediaInformation) {
if (singleGetMediaInformationCallback != null) {
singleGetMediaInformationCallback.apply(mediaInformation);
}
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (c) 2018 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
/**
* <p>Represents a callback function to receive a single execution result.
*
* @author Taner Sener
* @since v2.1
*/
@FunctionalInterface
public interface SingleExecuteCallback {
void apply(int returnCode, String executeOutput);
}
@@ -1,34 +0,0 @@
/*
* Copyright (c) 2018 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg.util;
import com.arthenica.mobileffmpeg.MediaInformation;
/**
* <p>Represents a callback function to receive a single getMediaInformation result.
*
* @author Taner Sener
*/
@FunctionalInterface
public interface SingleGetMediaInformationCallback {
void apply(MediaInformation mediaInformation);
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Taner Sener
* Copyright (c) 2018, 2020 Taner Sener
*
* This file is part of MobileFFmpeg.
*
File diff suppressed because it is too large Load Diff
@@ -1,299 +0,0 @@
/*
* Copyright (c) 2018 Taner Sener
*
* This file is part of MobileFFmpeg.
*
* MobileFFmpeg is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MobileFFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.mobileffmpeg;
import com.arthenica.mobileffmpeg.util.Pair;
import com.arthenica.mobileffmpeg.util.Trio;
import org.junit.Assert;
import org.junit.Test;
public class MediaInformationTest {
@Test
public void parseVideoStream() {
parseVideoStreamBlock(" Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 2560x1708 [SAR 1:1 DAR 640:427], 25 tbr, 25 tbn, 25 tbc", 0L, "mjpeg", "mjpeg", "yuvj420p", "yuvj420p(pc, bt470bg/unknown/unknown)", 2560L, 1708L, "1:1", "640:427", null, null, "25", "25", "25");
parseVideoStreamBlock(" Stream #0:0: Video: gif, bgra, 420x236, 6 fps, 6 tbr, 100 tbn, 100 tbc", 0L, "gif",
"gif", "bgra", "bgra", 420L, 236L, null, null, null, "6", "6", "100", "100");
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 7762 kb/s, 25 fps, 30 tbr, 15360 tbn, 60 tbc (default)", 0L, "h264", "h264 (main) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1280L, 720L, "1:1", "16:9", 7762L, "25", "30", "15360", "60");
parseVideoStreamBlock(" Stream #0:0: Video: png, rgba(pc), 544x184, 25 tbr, 25 tbn, 25 tbc", 0L, "png", "png", "rgba", "rgba(pc)", 544L, 184L, null, null, null, null, "25", "25", "25");
parseVideoStreamBlock(" Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 25 fps, 25 tbr, 1200k tbn, 50 tbc", 0L, "h264", "h264 (main)", "yuv420p", "yuv420p(tv, bt709, progressive)", 1920L, 1080L, null, null, null, "25", "25", "1200k", "50");
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 3840x4320 [SAR 1:1 DAR 8:9], 9902 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 3840L, 4320L, "1:1", "8:9", 9902L, "30", "30", "30k", "60");
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3992 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1920L, 1080L, "1:1", "16:9", 3992L, "30", "30", "30k", "60");
parseVideoStreamBlock(" Stream #0:0: Video: theora, yuv420p(bt470bg/bt470bg/bt709), 720x400, 25 fps, 25 tbr, 25 tbn, 25 tbc", 0L, "theora", "theora", "yuv420p", "yuv420p(bt470bg/bt470bg/bt709)", 720L, 400L, null, null, null, "25", "25", "25", "25");
}
@Test
public void parseAudioStream() {
parseAudioStreamBlock("Stream #0:0: Audio: adpcm_ms ([2][0][0][0] / 0x0002), 22050 Hz, stereo, s16, 176 kb/s", 0L, "adpcm_ms", "adpcm_ms ([2][0][0][0] / 0x0002)", 22050L, "stereo", "s16", 176L);
parseAudioStreamBlock("Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, mono, s16, 705 kb/s", 0L, "pcm_s16le", "pcm_s16le ([1][0][0][0] / 0x0001)", 44100L, "mono", "s16", 705L);
parseAudioStreamBlock("Stream #0:0: Audio: pcm_s24le ([1][0][0][0] / 0x0001), 48000 Hz, mono, s32 (24 bit), 1152 kb/s", 0L, "pcm_s24le", "pcm_s24le ([1][0][0][0] / 0x0001)", 48000L, "mono", "s32 (24 bit)", 1152L);
parseAudioStreamBlock("Stream #0:0: Audio: mp3, 48000 Hz, stereo, fltp, 192 kb/s", 0L, "mp3", "mp3", 48000L, "stereo", "fltp", 192L);
parseAudioStreamBlock("Stream #0:0: Audio: vorbis, 44100 Hz, stereo, fltp, 128 kb/s", 0L, "vorbis", "vorbis", 44100L, "stereo", "fltp", 128L);
parseAudioStreamBlock("Stream #0:0: Audio: pcm_u8 ([1][0][0][0] / 0x0001), 44100 Hz, stereo, u8, 705 kb/s", 0L, "pcm_u8", "pcm_u8 ([1][0][0][0] / 0x0001)", 44100L, "stereo", "u8", 705L);
parseAudioStreamBlock("Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
parseAudioStreamBlock("Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
parseAudioStreamBlock("Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
parseAudioStreamBlock("Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
}
@Test
public void index() {
Assert.assertEquals(7, MediaInformationParser.index("one:two:three:", ":", 0, 2));
Assert.assertEquals(13, MediaInformationParser.index("one:two:three:", ":", 0, 3));
Assert.assertEquals(8, MediaInformationParser.index("one::two::three::", "::", 0, 2));
}
@Test
public void count() {
Assert.assertEquals(3, MediaInformationParser.count("one:two:three:", ":"));
Assert.assertEquals(2, MediaInformationParser.count("one,two:three,four", ","));
}
@Test
public void parseAverageFrameRate() {
Assert.assertEquals("24", "24 fps".replaceAll("fps", "").trim());
Assert.assertEquals("30", "30 fps".replaceAll("fps", "").trim());
}
@Test
public void parseBitrate() {
Assert.assertEquals((Long) 3992L, MediaInformationParser.toLongObject("3992 kb/s".replaceAll("kb/s", "").trim()));
Assert.assertEquals((Long) 7762L, MediaInformationParser.toLongObject("7762 kb/s".replaceAll("kb/s", "").trim()));
}
@Test
public void parseVideoStreamDisplayAspectRatio() {
Assert.assertNull(MediaInformationParser.parseVideoStreamDisplayAspectRatio(""));
Assert.assertNull(MediaInformationParser.parseVideoStreamDisplayAspectRatio("544x184"));
Assert.assertEquals("640:427", MediaInformationParser.parseVideoStreamDisplayAspectRatio("2560x1708 [SAR 1:1 DAR 640:427]"));
Assert.assertEquals("8:9", MediaInformationParser.parseVideoStreamDisplayAspectRatio("3840x4320 [SAR 1:1 DAR 8:9]"));
}
@Test
public void parseVideoStreamSampleAspectRatio() {
Assert.assertNull(MediaInformationParser.parseVideoStreamSampleAspectRatio(""));
Assert.assertNull(MediaInformationParser.parseVideoStreamSampleAspectRatio("544x184"));
Assert.assertEquals("1:1", MediaInformationParser.parseVideoStreamSampleAspectRatio("2560x1708 [SAR 1:1 DAR 640:427]"));
Assert.assertEquals("1:1", MediaInformationParser.parseVideoStreamSampleAspectRatio("3840x4320 [SAR 1:1 DAR 8:9]"));
}
@Test
public void parseVideoDimensions() {
parseVideoDimensions("", null, null);
parseVideoDimensions("544x184", 544L, 184L);
parseVideoDimensions("720x400", 720L, 400L);
parseVideoDimensions("2560x1708 [SAR 1:1 DAR 640:427]", 2560L, 1708L);
parseVideoDimensions("3840x4320 [SAR 1:1 DAR 8:9]", 3840L, 4320L);
}
@Test
public void parseAudioSampleRate() {
Assert.assertEquals((Long) 44000L, MediaInformationParser.parseAudioStreamSampleRate("44000"));
Assert.assertEquals((Long) 44000L, MediaInformationParser.parseAudioStreamSampleRate("44 khz"));
Assert.assertEquals((Long) 44100L, MediaInformationParser.parseAudioStreamSampleRate("44100"));
Assert.assertEquals((Long) 5000000L, MediaInformationParser.parseAudioStreamSampleRate("5 mhz"));
}
@Test
public void parseAudioStreamType() {
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: theora"));
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: png"));
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: h264 (Main)"));
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: mp3 (mp4a / 0x6134706D)"));
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
}
@Test
public void parseStreamCodec() {
Assert.assertEquals("theora", MediaInformationParser.parseStreamCodec("Video: theora"));
Assert.assertEquals("png", MediaInformationParser.parseStreamCodec("Video: png"));
Assert.assertEquals("h264", MediaInformationParser.parseStreamCodec("Video: h264 (Main)"));
Assert.assertEquals("adpcm_ms", MediaInformationParser.parseStreamCodec("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
Assert.assertEquals("mp3", MediaInformationParser.parseStreamCodec("Audio: mp3 (mp4a / 0x6134706D)"));
Assert.assertEquals("pcm_u8", MediaInformationParser.parseStreamCodec("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
}
@Test
public void parseStreamFullCodec() {
Assert.assertEquals("theora", MediaInformationParser.parseStreamFullCodec("Video: theora"));
Assert.assertEquals("png", MediaInformationParser.parseStreamFullCodec("Video: png"));
Assert.assertEquals("h264 (main)", MediaInformationParser.parseStreamFullCodec("Video: h264 (Main)"));
Assert.assertEquals("adpcm_ms ([2][0][0][0] / 0x0002)", MediaInformationParser.parseStreamFullCodec("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
Assert.assertEquals("mp3 (mp4a / 0x6134706d)", MediaInformationParser.parseStreamFullCodec("Audio: mp3 (mp4a / 0x6134706D)"));
Assert.assertEquals("pcm_u8 ([1][0][0][0] / 0x0001)", MediaInformationParser.parseStreamFullCodec("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
}
@Test
public void parseStreamIndex() {
Assert.assertEquals((Long) 0L, MediaInformationParser.parseStreamIndex("Stream #0:0(und): Audio"));
Assert.assertEquals((Long) 1L, MediaInformationParser.parseStreamIndex("Stream #0:1(und): Video"));
Assert.assertEquals((Long) 2L, MediaInformationParser.parseStreamIndex("Stream #0:2(und): Audio"));
Assert.assertEquals((Long) 0L, MediaInformationParser.parseStreamIndex("Stream #0:0: Video"));
Assert.assertEquals((Long) 1L, MediaInformationParser.parseStreamIndex("Stream #0:1: Audio"));
Assert.assertEquals((Long) 2L, MediaInformationParser.parseStreamIndex("Stream #0:2: Video"));
}
@Test
public void parseDuration() {
parseDuration(null, null);
parseDuration("", null);
parseDuration("N/A", null);
parseDuration("00:03:33.24", 213240L);
parseDuration("00:10:34.53", 634530L);
parseDuration("00:00:00.04", 40L);
parseDuration("00:00:15.00", 15000L);
}
@Test
public void parseStartTime() {
parseStartTime(null, null);
parseStartTime("", null);
parseStartTime("N/A", null);
parseStartTime("0.000000", 0L);
parseStartTime("10.003000", 10003L);
parseStartTime("324.000000", 324000L);
parseStartTime("-4.000000", -4000L);
parseStartTime("14.00030", 14001L);
parseStartTime("14.00080", 14001L);
}
@Test
public void parseDurationBlock() {
parseDurationBlock(" Duration: 00:03:33.24, start: 0.000000, bitrate: 320 kb/s", 213240L, 0L, 320L);
parseDurationBlock(" Duration: 00:00:00.04, start: 0.000000, bitrate: 391187 kb/s", 40L, 0L, 391187L);
parseDurationBlock(" Duration: N/A, bitrate: N/A", null, null, null);
parseDurationBlock(" Duration: 00:00:15.00, start: 0.000000, bitrate: 7764 kb/s", 15000L, 0L, 7764L);
parseDurationBlock(" Duration: 00:10:34.53, start: 0.000000, bitrate: 4474 kb/s", 634530L, 0L, 4474L);
}
@Test
public void parseInputBlock() {
parseInputBlock("Input #0,", null, null);
parseInputBlock("Input #0, ogg, from 'trailer_400p.ogg':", "ogg", "trailer_400p.ogg");
parseInputBlock("Input #0, mp3, from 'beethoven_-_symphony_no_9.mp3':", "mp3", "beethoven_-_symphony_no_9.mp3");
parseInputBlock("Input #0, image2, from '/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg':", "image2", "/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg");
parseInputBlock("Input #0, gif, from 'advanced_zoom_in_and_pan_with_fade_in_out.gif':", "gif", "advanced_zoom_in_and_pan_with_fade_in_out.gif");
parseInputBlock("Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'transition_rotate.mp4':", "mov,mp4,m4a,3gp,3g2,mj2", "transition_rotate.mp4");
parseInputBlock("Input #0, png_pipe, from 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png':", "png_pipe", "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png");
}
@Test
public void parseMetadataBlock() {
parseMetadataBlock(" ENCODER:", "ENCODER", "");
parseMetadataBlock(" ENCODER:ffmpeg2theora 0.19", "ENCODER", "ffmpeg2theora 0.19");
parseMetadataBlock(" ENCODER : ffmpeg2theora 0.19", "ENCODER", "ffmpeg2theora 0.19");
parseMetadataBlock(" creation_time : 2013-12-16T17:50:04.000000Z", "creation_time", "2013-12-16T17:50:04.000000Z");
parseMetadataBlock(" handler_name : GPAC ISO Audio Handler", "handler_name", "GPAC ISO Audio Handler");
parseMetadataBlock(" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net", "comment", "Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net");
parseMetadataBlock(" minor_version : 1", "minor_version", "1");
parseMetadataBlock(" encoder : Lavf58.12.100", "encoder", "Lavf58.12.100");
parseMetadataBlock(" title : Planet X", "title", "Planet X");
parseMetadataBlock(" compatible_brands: isomiso2avc1mp41", "compatible_brands", "isomiso2avc1mp41");
}
private void parseVideoStreamBlock(String input, Long index, String codec, String fullCodec, String format, String fullFormat, Long width, Long height, String sampleAspectRatio, String displayAspectRatio, Long bitrate, String averageFrameRate, String realFrameRate, String timeBase, String codecTimeBase) {
StreamInformation stream = MediaInformationParser.parseStreamBlock(input);
Assert.assertNotNull(stream);
Assert.assertEquals(index, stream.getIndex());
Assert.assertEquals("video", stream.getType());
Assert.assertEquals(codec, stream.getCodec());
Assert.assertEquals(fullCodec, stream.getFullCodec());
Assert.assertEquals(format, stream.getFormat());
Assert.assertEquals(fullFormat, stream.getFullFormat());
Assert.assertEquals(width, stream.getWidth());
Assert.assertEquals(height, stream.getHeight());
Assert.assertEquals(sampleAspectRatio, stream.getSampleAspectRatio());
Assert.assertEquals(displayAspectRatio, stream.getDisplayAspectRatio());
Assert.assertEquals(bitrate, stream.getBitrate());
Assert.assertEquals(averageFrameRate, stream.getAverageFrameRate());
Assert.assertEquals(realFrameRate, stream.getRealFrameRate());
Assert.assertEquals(timeBase, stream.getTimeBase());
Assert.assertEquals(codecTimeBase, stream.getCodecTimeBase());
}
private void parseAudioStreamBlock(String input, Long index, String codec, String fullCodec, Long sampleRate, String channelLayout, String sampleFormat, Long bitrate) {
StreamInformation stream = MediaInformationParser.parseStreamBlock(input);
Assert.assertNotNull(stream);
Assert.assertEquals(index, stream.getIndex());
Assert.assertEquals("audio", stream.getType());
Assert.assertEquals(codec, stream.getCodec());
Assert.assertEquals(fullCodec, stream.getFullCodec());
Assert.assertEquals(sampleRate, stream.getSampleRate());
Assert.assertEquals(channelLayout, stream.getChannelLayout());
Assert.assertEquals(sampleFormat, stream.getSampleFormat());
Assert.assertEquals(bitrate, stream.getBitrate());
}
private void parseVideoDimensions(String value, Long width, Long height) {
Pair<Long, Long> videoDimensions = MediaInformationParser.parseVideoDimensions(value);
Assert.assertNotNull(videoDimensions);
Assert.assertEquals(width, videoDimensions.getFirst());
Assert.assertEquals(height, videoDimensions.getSecond());
}
private void parseDuration(String value, Long expected) {
Long duration = MediaInformationParser.parseDuration(value);
Assert.assertEquals(expected, duration);
}
private void parseStartTime(String value, Long expected) {
Long duration = MediaInformationParser.parseStartTime(value);
Assert.assertEquals(expected, duration);
}
private void parseDurationBlock(String value, Long first, Long second, Long third) {
Trio<Long, Long, Long> pair = MediaInformationParser.parseDurationBlock(value);
Assert.assertNotNull(pair);
Assert.assertEquals(first, pair.getFirst());
Assert.assertEquals(second, pair.getSecond());
Assert.assertEquals(third, pair.getThird());
}
private void parseInputBlock(String value, String first, String second) {
Pair<String, String> pair = MediaInformationParser.parseInputBlock(value);
Assert.assertNotNull(pair);
Assert.assertEquals(first, pair.getFirst());
Assert.assertEquals(second, pair.getSecond());
}
private void parseMetadataBlock(String value, String first, String second) {
Pair<String, String> pair = MediaInformationParser.parseMetadataBlock(value);
Assert.assertNotNull(pair);
Assert.assertNotNull(pair.getFirst());
Assert.assertNotNull(pair.getSecond());
Assert.assertEquals(first, pair.getFirst());
Assert.assertEquals(second, pair.getSecond());
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.android.tools.build:gradle:4.0.1'
// NOTE: Do not place your application dependencies here; they belong
// 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
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+74 -48
View File
@@ -1,28 +1,32 @@
LOCAL_PATH := $(call my-dir)
$(call import-add-path, $(LOCAL_PATH))
MY_LOCAL_PATH := $(call my-dir)
$(call import-add-path, $(MY_LOCAL_PATH))
MY_ARMV7 := false
MY_ARMV7_NEON := false
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
ifeq ("$(shell test -e $(MY_LOCAL_PATH)/../build/.armv7 && echo armv7)","armv7")
MY_ARMV7 := true
endif
ifeq ("$(shell test -e $(MY_LOCAL_PATH)/../build/.armv7neon && echo armv7neon)","armv7neon")
MY_ARMV7_NEON := true
endif
endif
ifeq ($(MY_ARMV7_NEON), true)
FFMPEG_INCLUDES := $(MY_LOCAL_PATH)/../../prebuilt/android-$(TARGET_ARCH)/neon/ffmpeg/include
$(call import-module, cpu-features/neon)
else
FFMPEG_INCLUDES := $(MY_LOCAL_PATH)/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
$(call import-module, cpu-features)
endif
MY_ARM_MODE := arm
MY_ARM_NEON := false
MY_PATH := ../app/src/main/cpp
MY_LIBRARY_LTS_SOURCE := $(MY_PATH)/android_lts_support.c
MY_LIBRARY_SOURCE := $(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_LIBRARY_SHARED_LIBRARIES := libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
MY_LIBRARY_LDLIBS := -llog -lz -landroid
MY_LIBRARY_INCLUDES :=
MY_LIBRARY_WHOLE_STATIC_LIBRARIES :=
# ENABLE FFPLAY
ifeq ("$(shell test -e $(LOCAL_PATH)/../build/.ffplay && echo ffplay)","ffplay")
MY_LIBRARY_SOURCE += $(MY_PATH)/fftools_ffplay.c $(MY_PATH)/mobileffplay.c
MY_LIBRARY_WHOLE_STATIC_LIBRARIES += sdl
MY_LIBRARY_LDLIBS += -lGLESv1_CM -lGLESv2
MY_LIBRARY_INCLUDES := -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/sdl/include/SDL2 -I${LOCAL_PATH}/../../src/sdl/src/core/android
endif
LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
# DEFINE ARCH FLAGS
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
MY_ARCH_FLAGS := ARM_V7A
MY_ARM_NEON := true
MY_ARM_NEON := false
endif
ifeq ($(TARGET_ARCH_ABI), arm64-v8a)
MY_ARCH_FLAGS := ARM64_V8A
@@ -35,44 +39,66 @@ ifeq ($(TARGET_ARCH_ABI), x86_64)
MY_ARCH_FLAGS := X86_64
endif
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := cpufeatures
LOCAL_SRC_FILES := $(NDK_ROOT)/sources/android/cpufeatures/cpu-features.c
LOCAL_CFLAGS := -Wall -Wextra -Werror
LOCAL_EXPORT_C_INCLUDES := $(NDK_ROOT)/sources/android/cpufeatures
LOCAL_EXPORT_LDLIBS := -ldl
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg_abidetect
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg_abidetect.c
LOCAL_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include -I$(NDK_ROOT)/sources/android/cpufeatures -DMOBILE_FFMPEG_${MY_ARCH_FLAGS}
LOCAL_SRC_FILES := mobileffmpeg_abidetect.c
LOCAL_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -DMOBILE_FFMPEG_${MY_ARCH_FLAGS}
LOCAL_C_INCLUDES := $(FFMPEG_INCLUDES)
LOCAL_LDLIBS := -llog -lz -landroid
LOCAL_SHARED_LIBRARIES := cpufeatures
LOCAL_STATIC_LIBRARIES := cpu-features
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg
LOCAL_SRC_FILES := $(MY_LIBRARY_SOURCE)
ifeq ($(TARGET_PLATFORM),android-16)
LOCAL_SRC_FILES += $(MY_LIBRARY_LTS_SOURCE)
MY_SRC_FILES := mobileffmpeg.c mobileffprobe.c android_lts_support.c mobileffmpeg_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
else ifeq ($(TARGET_PLATFORM),android-17)
LOCAL_SRC_FILES += $(MY_LIBRARY_LTS_SOURCE)
endif
LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include ${MY_LIBRARY_INCLUDES}
LOCAL_LDLIBS := ${MY_LIBRARY_LDLIBS}
LOCAL_SHARED_LIBRARIES := ${MY_LIBRARY_SHARED_LIBRARIES}
LOCAL_WHOLE_STATIC_LIBRARIES := ${MY_LIBRARY_WHOLE_STATIC_LIBRARIES}
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
ifeq ("$(shell test -e $(LOCAL_PATH)/../build/.ffplay && echo ffplay)","ffplay")
$(call import-module, sdl)
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
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
$(call import-module, ffmpeg)
MY_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare
MY_LDLIBS := -llog -lz -landroid
MY_BUILD_GENERIC_MOBILE_FFMPEG := true
ifeq ($(MY_ARMV7_NEON), true)
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg_armv7a_neon
LOCAL_SRC_FILES := $(MY_SRC_FILES)
LOCAL_CFLAGS := $(MY_CFLAGS)
LOCAL_LDLIBS := $(MY_LDLIBS)
LOCAL_SHARED_LIBRARIES := libavcodec_neon libavfilter_neon libswscale_neon libavformat_neon libavutil_neon libswresample_neon libavdevice_neon
ifeq ($(APP_STL), c++_shared)
LOCAL_SHARED_LIBRARIES += c++_shared # otherwise NDK will not add the library for packaging
endif
LOCAL_ARM_NEON := true
include $(BUILD_SHARED_LIBRARY)
$(call import-module, ffmpeg/neon)
ifneq ($(MY_ARMV7), true)
MY_BUILD_GENERIC_MOBILE_FFMPEG := false
endif
endif
ifeq ($(MY_BUILD_GENERIC_MOBILE_FFMPEG), true)
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := mobileffmpeg
LOCAL_SRC_FILES := $(MY_SRC_FILES)
LOCAL_CFLAGS := $(MY_CFLAGS)
LOCAL_LDLIBS := $(MY_LDLIBS)
LOCAL_SHARED_LIBRARIES := libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
ifeq ($(APP_STL), c++_shared)
LOCAL_SHARED_LIBRARIES += c++_shared # otherwise NDK will not add the library for packaging
endif
LOCAL_ARM_NEON := ${MY_ARM_NEON}
include $(BUILD_SHARED_LIBRARY)
$(call import-module, ffmpeg)
endif
+8
View File
@@ -0,0 +1,8 @@
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/cpu-features/lib
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := cpu-features
LOCAL_SRC_FILES := libndk_compat.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include/ndk_compat
include $(PREBUILT_STATIC_LIBRARY)
+8
View File
@@ -0,0 +1,8 @@
LOCAL_PATH := $(call my-dir)/../../../../prebuilt/android-$(TARGET_ARCH)/neon/cpu-features/lib
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := cpu-features
LOCAL_SRC_FILES := libndk_compat.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include/ndk_compat
include $(PREBUILT_STATIC_LIBRARY)
+9 -9
View File
@@ -1,46 +1,46 @@
LOCAL_PATH := $(call my-dir)
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/lib
MY_ARM_MODE := arm
MY_FFMPEG_LIB := ../../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/lib
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavcodec.so
LOCAL_SRC_FILES := libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavfilter
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavfilter.so
LOCAL_SRC_FILES := libavfilter.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavdevice
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavdevice.so
LOCAL_SRC_FILES := libavdevice.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavformat.so
LOCAL_SRC_FILES := libavformat.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavutil.so
LOCAL_SRC_FILES := libavutil.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswresample.so
LOCAL_SRC_FILES := libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswscale.so
LOCAL_SRC_FILES := libswscale.so
include $(PREBUILT_SHARED_LIBRARY)
+17 -9
View File
@@ -1,53 +1,61 @@
LOCAL_PATH := $(call my-dir)
LOCAL_PATH := $(call my-dir)/../../../../prebuilt/android-$(TARGET_ARCH)/neon/ffmpeg/lib
MY_ARM_MODE := arm
MY_FFMPEG_LIB := ../../../../prebuilt/android-$(TARGET_ARCH)/neon/ffmpeg/lib
MY_ARM_NEON := true
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavcodec_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavcodec.so
LOCAL_SRC_FILES := libavcodec_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavfilter_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavfilter.so
LOCAL_SRC_FILES := libavfilter_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavdevice_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavdevice.so
LOCAL_SRC_FILES := libavdevice_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavformat_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavformat.so
LOCAL_SRC_FILES := libavformat_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libavutil_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libavutil.so
LOCAL_SRC_FILES := libavutil_neon.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libswresample_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswresample.so
LOCAL_SRC_FILES := libswresample_neon.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_ARM_NEON := ${MY_ARM_NEON}
LOCAL_MODULE := libswscale_neon
LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
LOCAL_SRC_FILES := $(MY_FFMPEG_LIB)/libswscale.so
LOCAL_SRC_FILES := libswscale_neon.so
include $(PREBUILT_SHARED_LIBRARY)
-10
View File
@@ -1,10 +0,0 @@
LOCAL_PATH := $(call my-dir)
MY_ARM_MODE := arm
MY_SDL_LIB := ../../../prebuilt/android-$(TARGET_ARCH)/sdl/lib
include $(CLEAR_VARS)
LOCAL_ARM_MODE := $(MY_ARM_MODE)
LOCAL_MODULE := sdl
LOCAL_SRC_FILES := $(MY_SDL_LIB)/libSDL2.a
include $(PREBUILT_STATIC_LIBRARY)
+3
View File
@@ -1 +1,4 @@
include ':app', ':test-app'
include ':mobile-ffmpeg'
project(':mobile-ffmpeg').projectDir = new File('..')
rootProject.name='Mobile FFmpeg (android)'
+8 -7
View File
@@ -9,13 +9,13 @@ android {
keyPassword 'android'
}
}
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
applicationId "com.arthenica.mobileffmpeg.test"
minSdkVersion 24
targetSdkVersion 29
versionCode 240431
versionName "4.3.1"
minSdkVersion 16
targetSdkVersion 30
versionCode 160440
versionName "4.4.LTS"
}
buildTypes {
debug {
@@ -51,8 +51,9 @@ android.applicationVariants.all { variant ->
}
dependencies {
implementation project(':app')
// implementation 'com.arthenica:mobile-ffmpeg-full:4.3.1'
// implementation project(':app')
implementation 'com.arthenica:mobile-ffmpeg-full:4.4.LTS'
implementation 'com.arthenica:smart-exception-java:0.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}
@@ -26,14 +26,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.arthenica.mobileffmpeg.player.FullScreenActivity"
android:label="play"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="landscape"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
</activity>
</application>
</manifest>
@@ -39,7 +39,7 @@ import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import java.io.File;
import java.util.concurrent.Callable;
@@ -103,7 +103,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
@Override
public void apply(final LogMessage message) {
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -145,17 +145,17 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
clearLog();
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -164,7 +164,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
android.util.Log.d(TAG, "Encode completed successfully.");
} else {
Popup.show(requireContext(), "Encode failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Encode failed with rc=%d", returnCode));
android.util.Log.d(TAG, String.format("Encode failed with rc=%d.", returnCode));
}
return null;
@@ -181,11 +181,11 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
final String ffmpegCommand = String.format("-v quiet -i %s -f chromaprint -fp_format 2 -", audioSampleFile.getAbsolutePath());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
int returnCode = FFmpeg.execute(ffmpegCommand);
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
}
public void createAudioSample() {
@@ -198,14 +198,14 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
String ffmpegCommand = String.format("-hide_banner -y -f lavfi -i sine=frequency=1000:duration=5 -c:a pcm_s16le %s", audioSampleFile.getAbsolutePath());
android.util.Log.d(TAG, String.format("Sample file is created with '%s'", ffmpegCommand));
android.util.Log.d(TAG, String.format("Sample file is created with '%s'.", ffmpegCommand));
int result = FFmpeg.execute(ffmpegCommand);
if (result == 0) {
encodeButton.setEnabled(true);
android.util.Log.d(TAG, "AUDIO sample created");
} else {
android.util.Log.d(TAG, String.format("Creating AUDIO sample failed with rc=%d", result));
android.util.Log.d(TAG, String.format("Creating AUDIO sample failed with rc=%d.", result));
Popup.show(requireContext(), "Creating AUDIO sample failed. Please check log for the details.");
}
}
@@ -228,7 +228,10 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
case "opus":
extension = "opus";
break;
case "amr":
case "amr-nb":
extension = "amr";
break;
case "amr-wb":
extension = "amr";
break;
case "ilbc":
@@ -260,7 +263,7 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
disableLogCallback();
createAudioSample();
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) {
@@ -295,8 +298,10 @@ public class AudioTabFragment extends Fragment implements AdapterView.OnItemSele
return String.format("-hide_banner -y -i %s -c:a libvorbis -b:a 64k %s", audioSampleFile, audioOutputFile);
case "opus":
return String.format("-hide_banner -y -i %s -c:a libopus -b:a 64k -vbr on -compression_level 10 %s", audioSampleFile, audioOutputFile);
case "amr":
case "amr-nb":
return String.format("-hide_banner -y -i %s -ar 8000 -ab 12.2k -c:a libopencore_amrnb %s", audioSampleFile, audioOutputFile);
case "amr-wb":
return String.format("-hide_banner -y -i %s -ar 8000 -ab 12.2k -c:a libvo_amrwbenc -strict experimental %s", audioSampleFile, audioOutputFile);
case "ilbc":
return String.format("-hide_banner -y -i %s -c:a ilbc -ar 8000 -b:a 15200 %s", audioSampleFile, audioOutputFile);
case "speex":
@@ -112,13 +112,15 @@ public class CommandTabFragment extends Fragment {
final String ffmpegCommand = String.format("%s", commandText.getText().toString());
android.util.Log.d(MainActivity.TAG, String.format("Current log level is %s.", Config.getLogLevel()));
android.util.Log.d(MainActivity.TAG, "Testing FFmpeg COMMAND synchronously.");
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process started with arguments\n\'%s\'", ffmpegCommand));
int result = FFmpeg.execute(ffmpegCommand);
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process exited with rc %d", result));
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process exited with rc %d.", result));
if (result != 0) {
Popup.show(requireContext(), "Command failed. Please check output for the details.");
@@ -136,7 +138,7 @@ public class CommandTabFragment extends Fragment {
int result = FFprobe.execute(ffprobeCommand);
android.util.Log.d(MainActivity.TAG, String.format("FFprobe process exited with rc %d", result));
android.util.Log.d(MainActivity.TAG, String.format("FFprobe process exited with rc %d.", result));
if (result != 0) {
Popup.show(requireContext(), "Command failed. Please check output for the details.");
@@ -146,7 +148,7 @@ public class CommandTabFragment extends Fragment {
private void setActive() {
Log.i(MainActivity.TAG, "Command Tab Activated");
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) {
@@ -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.StreamInformation;
import java.util.Map;
import java.util.Set;
import org.json.JSONObject;
import java.util.Iterator;
import java.util.concurrent.Callable;
public class HttpsTabFragment extends Fragment {
@@ -105,9 +106,9 @@ public class HttpsTabFragment extends Fragment {
if (testUrl.isEmpty()) {
testUrl = HTTPS_TEST_DEFAULT_URL;
urlText.setText(testUrl);
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with default url '%s'", testUrl));
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with default url '%s'.", testUrl));
} else {
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with url '%s'", testUrl));
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with url '%s'.", testUrl));
}
// HTTPS COMMAND ARGUMENTS
@@ -115,7 +116,7 @@ public class HttpsTabFragment extends Fragment {
if (information == null) {
appendLog("Get media information failed\n");
} else {
appendLog("Media information for " + information.getPath() + "\n");
appendLog("Media information for " + information.getFilename() + "\n");
if (information.getFormat() != null) {
appendLog("Format: " + information.getFormat() + "\n");
@@ -129,10 +130,14 @@ public class HttpsTabFragment extends Fragment {
if (information.getStartTime() != null) {
appendLog("Start time: " + information.getStartTime() + "\n");
}
if (information.getMetadataEntries() != null) {
Set<Map.Entry<String, String>> entries = information.getMetadataEntries();
for (Map.Entry<String, String> entry : entries) {
appendLog("Metadata: " + entry.getKey() + ":" + entry.getValue() + "\n");
if (information.getTags() != null) {
JSONObject tags = information.getTags();
if (tags != null) {
Iterator<String> keys = tags.keys();
while (keys.hasNext()) {
String next = keys.next();
appendLog("Tag: " + next + ":" + tags.optString(next) + "\n");
}
}
}
if (information.getStreams() != null) {
@@ -152,9 +157,6 @@ public class HttpsTabFragment extends Fragment {
if (stream.getFormat() != null) {
appendLog("Stream format: " + stream.getFormat() + "\n");
}
if (stream.getFullFormat() != null) {
appendLog("Stream full format: " + stream.getFullFormat() + "\n");
}
if (stream.getWidth() != null) {
appendLog("Stream width: " + stream.getWidth() + "\n");
@@ -196,10 +198,14 @@ public class HttpsTabFragment extends Fragment {
appendLog("Stream codec time base: " + stream.getCodecTimeBase() + "\n");
}
if (stream.getMetadataEntries() != null) {
Set<Map.Entry<String, String>> entries = stream.getMetadataEntries();
for (Map.Entry<String, String> entry : entries) {
appendLog("Stream metadata: " + entry.getKey() + ":" + entry.getValue() + "\n");
if (stream.getTags() != null) {
JSONObject tags = stream.getTags();
if (tags != null) {
Iterator<String> keys = tags.keys();
while (keys.hasNext()) {
String next = keys.next();
appendLog(String.format("Stream tag: %s:%s\n", next, tags.optString(next)));
}
}
}
}
@@ -210,7 +216,7 @@ public class HttpsTabFragment extends Fragment {
public void setActive() {
Log.i(MainActivity.TAG, "Https Tab Activated");
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) {
@@ -32,10 +32,13 @@ import androidx.core.app.ActivityCompat;
import androidx.viewpager.widget.PagerTabStrip;
import androidx.viewpager.widget.ViewPager;
import com.arthenica.mobileffmpeg.AsyncFFmpegExecuteTask;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.util.AsyncSingleFFmpegExecuteTask;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.Level;
import com.arthenica.mobileffmpeg.Signal;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -56,7 +59,11 @@ public class MainActivity extends AppCompatActivity {
Manifest.permission.CAMERA
};
protected static final Queue<Callable> actionQueue = new ConcurrentLinkedQueue<>();
static {
Exceptions.registerRootPackage("com.arthenica");
}
protected static final Queue<Callable<Object>> actionQueue = new ConcurrentLinkedQueue<>();
protected static final Handler handler = new Handler();
@@ -64,7 +71,7 @@ public class MainActivity extends AppCompatActivity {
@Override
public void run() {
Callable callable;
Callable<Object> callable;
do {
callable = actionQueue.poll();
@@ -72,7 +79,7 @@ public class MainActivity extends AppCompatActivity {
try {
callable.call();
} catch (final Exception e) {
android.util.Log.e(MainActivity.TAG, "Running UI action received error.", e);
android.util.Log.e(TAG, String.format("Running UI action received error.%s.", Exceptions.getStackTraceString(e)));
}
}
} while (callable != null);
@@ -123,11 +130,13 @@ public class MainActivity extends AppCompatActivity {
registerAppFont();
Log.d(TAG, "Application fonts registered.");
} catch (final IOException e) {
Log.e(TAG, "Font registration failed.", e);
Log.e(TAG, String.format("Font registration failed.%s.", Exceptions.getStackTraceString(e)));
}
Log.d(TAG, "Listing supported camera ids.");
listSupportedCameraIds();
Config.ignoreSignal(Signal.SIGXCPU);
Config.setLogLevel(Level.AV_LOG_DEBUG);
}
@Override
@@ -140,11 +149,11 @@ public class MainActivity extends AppCompatActivity {
/**
* <p>Starts a new asynchronous FFmpeg operation with command provided.
*
* @param singleExecuteCallback callback function to receive result of this execution
* @param command FFmpeg command
* @param ExecuteCallback callback function to receive result of this execution
* @param command FFmpeg command
*/
public static void executeAsync(final SingleExecuteCallback singleExecuteCallback, final String command) {
final AsyncSingleFFmpegExecuteTask asyncCommandTask = new AsyncSingleFFmpegExecuteTask(command, singleExecuteCallback);
public static void executeAsync(final ExecuteCallback ExecuteCallback, final String command) {
final AsyncFFmpegExecuteTask asyncCommandTask = new AsyncFFmpegExecuteTask(command, ExecuteCallback);
asyncCommandTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -152,7 +161,7 @@ public class MainActivity extends AppCompatActivity {
handler.postDelayed(runnable, 250);
}
public static void addUIAction(final Callable callable) {
public static void addUIAction(final Callable<Object> callable) {
actionQueue.add(callable);
}
@@ -166,7 +175,7 @@ public class MainActivity extends AppCompatActivity {
final HashMap<String, String> fontNameMapping = new HashMap<>();
fontNameMapping.put("MyFontName", "Doppio One");
Config.setFontDirectory(this, cacheDirectory.getAbsolutePath(), fontNameMapping);
// Config.setFontDirectory(this, cacheDirectory.getAbsolutePath(), null);
Config.setEnvironmentVariable("FFREPORT", String.format("file=%s", new File(cacheDirectory.getAbsolutePath(), "ffreport.txt").getAbsolutePath()));
}
protected void listSupportedCameraIds() {
@@ -26,7 +26,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
public class PagerAdapter extends FragmentPagerAdapter {
private static final int NUMBER_OF_TABS = 7;
private static final int NUMBER_OF_TABS = 8;
private final Context context;
@@ -59,6 +59,9 @@ public class PagerAdapter extends FragmentPagerAdapter {
case 6: {
return PipeTabFragment.newInstance();
}
case 7: {
return ConcurrentExecutionTabFragment.newInstance();
}
default: {
return null;
}
@@ -94,6 +97,9 @@ public class PagerAdapter extends FragmentPagerAdapter {
case 6: {
return context.getString(R.string.pipe_tab);
}
case 7: {
return context.getString(R.string.concurrent_tab);
}
default: {
return null;
}
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.Statistics;
@@ -42,7 +43,7 @@ import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.AsyncCatImageTask;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -107,7 +108,7 @@ public class PipeTabFragment extends Fragment {
@Override
public void apply(final Statistics newStatistics) {
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -154,17 +155,17 @@ public class PipeTabFragment extends Fragment {
final String ffmpegCommand = Video.generateCreateVideoWithPipesScript(pipe1, pipe2, pipe3, videoFile.getAbsolutePath());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -173,7 +174,7 @@ public class PipeTabFragment extends Fragment {
playVideo();
} else {
Popup.show(requireContext(), "Create failed. Please check log for the details.");
Log.d(TAG, String.format("Create failed with rc=%d", returnCode));
Log.d(TAG, String.format("Create failed with rc=%d.", returnCode));
}
return null;
@@ -188,7 +189,7 @@ public class PipeTabFragment extends Fragment {
startAsyncCatImageProcess(image3File.getAbsolutePath(), pipe3);
} catch (IOException e) {
Log.e(TAG, "Create video failed", e);
Log.e(TAG, String.format("Create video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Create video failed");
}
}
@@ -225,7 +226,7 @@ public class PipeTabFragment extends Fragment {
Log.i(MainActivity.TAG, "Pipe Tab Activated");
enableLogCallback();
enableStatisticsCallback();
Popup.show(requireContext(), Tooltip.PIPE_TEST_TOOLTIP_TEXT);
Popup.show(requireContext(), getString(R.string.pipe_test_tooltip_text));
}
protected void showProgressDialog() {
@@ -250,7 +251,7 @@ public class PipeTabFragment extends Fragment {
TextView textView = progressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Creating video: %% %s", completePercentage));
textView.setText(String.format("Creating video: %% %s.", completePercentage));
}
}
}
@@ -258,7 +259,7 @@ public class PipeTabFragment extends Fragment {
protected void hideProgressDialog() {
progressDialog.dismiss();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.FFmpeg;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
@@ -41,7 +42,7 @@ import com.arthenica.mobileffmpeg.Statistics;
import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -65,6 +66,7 @@ public class SubtitleTabFragment extends Fragment {
private AlertDialog burnProgressDialog;
private Statistics statistics;
private State state;
private Long executionId;
public SubtitleTabFragment() {
super(R.layout.fragment_subtitle_tab);
@@ -103,7 +105,7 @@ public class SubtitleTabFragment extends Fragment {
@Override
public void apply(LogMessage message) {
android.util.Log.d(MainActivity.TAG, message.getText());
Log.d(MainActivity.TAG, message.getText());
}
});
}
@@ -157,23 +159,24 @@ public class SubtitleTabFragment extends Fragment {
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath(), "mpeg4", "");
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
state = State.CREATING;
MainActivity.executeAsync(new SingleExecuteCallback() {
executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideCreateProgressDialog();
MainActivity.addUIAction(new Callable() {
if (returnCode == RETURN_CODE_SUCCESS) {
@Override
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
Log.d(TAG, "Create completed successfully; burning subtitles.");
@@ -181,19 +184,19 @@ public class SubtitleTabFragment extends Fragment {
showBurnProgressDialog();
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", burnSubtitlesCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", burnSubtitlesCommand));
state = State.BURNING;
MainActivity.executeAsync(new SingleExecuteCallback() {
FFmpeg.executeAsync(burnSubtitlesCommand, new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideBurnProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -205,31 +208,26 @@ public class SubtitleTabFragment extends Fragment {
Log.e(TAG, "Burn subtitles operation cancelled");
} else {
Popup.show(requireContext(), "Burn subtitles failed. Please check log for the details.");
Log.e(TAG, String.format("Burn subtitles failed with rc=%d", returnCode));
Log.e(TAG, String.format("Burn subtitles failed with rc=%d.", returnCode));
}
return null;
}
});
}
}, burnSubtitlesCommand);
});
} else if (returnCode == RETURN_CODE_CANCEL) {
Popup.show(requireContext(), "Create operation cancelled.");
Log.e(TAG, "Create operation cancelled");
} else {
Popup.show(requireContext(), "Create video failed. Please check log for the details.");
Log.e(TAG, String.format("Create failed with rc=%d", returnCode));
return null;
}
return null;
}
});
});
}
}
}, ffmpegCommand);
});
Log.d(TAG, String.format("FFmpeg started execution id: %d.", executionId));
} catch (IOException e) {
Log.e(TAG, "Burn subtitles failed", e);
Log.e(TAG, String.format("Burn subtitles failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Burn subtitles failed");
}
}
@@ -274,7 +272,7 @@ public class SubtitleTabFragment extends Fragment {
Log.i(MainActivity.TAG, "Subtitle Tab Activated");
enableLogCallback();
enableStatisticsCallback();
Popup.show(requireContext(), Tooltip.SUBTITLE_TEST_TOOLTIP_TEXT);
Popup.show(requireContext(), getString(R.string.subtitle_test_tooltip_text));
}
protected void showCreateProgressDialog() {
@@ -287,7 +285,10 @@ public class SubtitleTabFragment extends Fragment {
@Override
public void onClick(View v) {
FFmpeg.cancel();
if (executionId != null) {
Log.d(TAG, String.format("Cancelling FFmpeg execution with executionId %d.", executionId));
FFmpeg.cancel(executionId);
}
}
});
createProgressDialog.show();
@@ -306,12 +307,12 @@ public class SubtitleTabFragment extends Fragment {
if (state == State.CREATING) {
TextView textView = createProgressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Creating video: %% %s", completePercentage));
textView.setText(String.format("Creating video: %% %s.", completePercentage));
}
} else if (state == State.BURNING) {
TextView textView = burnProgressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Burning subtitles: %% %s", completePercentage));
textView.setText(String.format("Burning subtitles: %% %s.", completePercentage));
}
}
@@ -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,8 @@ import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -92,7 +93,7 @@ public class VidStabTabFragment extends Fragment {
@Override
public void apply(LogMessage message) {
android.util.Log.d(MainActivity.TAG, message.getText());
Log.d(MainActivity.TAG, message.getText());
}
});
}
@@ -121,7 +122,7 @@ public class VidStabTabFragment extends Fragment {
stabilizedVideoFile.delete();
}
android.util.Log.d(TAG, "Testing VID.STAB");
Log.d(TAG, "Testing VID.STAB");
showCreateProgressDialog();
@@ -131,60 +132,60 @@ public class VidStabTabFragment extends Fragment {
final String ffmpegCommand = Video.generateShakingVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath());
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideCreateProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) {
android.util.Log.d(TAG, "Create completed successfully; stabilizing video.");
Log.d(TAG, "Create completed successfully; stabilizing video.");
final String analyzeVideoCommand = String.format("-y -i %s -vf vidstabdetect=shakiness=10:accuracy=15:result=%s -f null -", videoFile.getAbsolutePath(), shakeResultsFile.getAbsolutePath());
showStabilizeProgressDialog();
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", analyzeVideoCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", analyzeVideoCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
if (returnCode == RETURN_CODE_SUCCESS) {
final String stabilizeVideoCommand = String.format("-y -i %s -vf vidstabtransform=smoothing=30:input=%s -c:v mpeg4 %s", videoFile.getAbsolutePath(), shakeResultsFile.getAbsolutePath(), stabilizedVideoFile.getAbsolutePath());
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", stabilizeVideoCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", stabilizeVideoCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
MainActivity.executeAsync(new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
hideStabilizeProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
if (returnCode == RETURN_CODE_SUCCESS) {
android.util.Log.d(TAG, "Stabilize video completed successfully; playing videos.");
Log.d(TAG, "Stabilize video completed successfully; playing videos.");
playVideo();
playStabilizedVideo();
} else {
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
Log.d(TAG, String.format("Stabilize video failed with rc=%d.", returnCode));
}
return null;
@@ -196,14 +197,14 @@ public class VidStabTabFragment extends Fragment {
} else {
hideStabilizeProgressDialog();
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
Log.d(TAG, String.format("Stabilize video failed with rc=%d.", returnCode));
}
}
}, analyzeVideoCommand);
} else {
Popup.show(requireContext(), "Create video failed. Please check log for the details.");
android.util.Log.d(TAG, String.format("Create failed with rc=%d", returnCode));
Log.d(TAG, String.format("Create failed with rc=%d.", returnCode));
}
return null;
@@ -213,7 +214,7 @@ public class VidStabTabFragment extends Fragment {
}, ffmpegCommand);
} catch (IOException e) {
android.util.Log.e(TAG, "Stabilize video failed", e);
Log.e(TAG, String.format("Stabilize video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Stabilize video failed");
}
}
@@ -281,7 +282,7 @@ public class VidStabTabFragment extends Fragment {
public void setActive() {
Log.i(MainActivity.TAG, "VidStab Tab Activated");
enableLogCallback();
Popup.show(requireContext(), Tooltip.VIDSTAB_TEST_TOOLTIP_TEXT);
Popup.show(requireContext(), getString(R.string.vidstab_test_tooltip_text));
}
protected void showCreateProgressDialog() {
@@ -20,33 +20,33 @@
package com.arthenica.mobileffmpeg.test;
import android.app.AlertDialog;
import android.content.pm.ActivityInfo;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.MediaController;
import android.widget.Spinner;
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.FFplay;
import com.arthenica.mobileffmpeg.LogCallback;
import com.arthenica.mobileffmpeg.LogMessage;
import com.arthenica.mobileffmpeg.Statistics;
import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.player.GenericMotionListener;
import com.arthenica.mobileffmpeg.player.PlayerSession;
import com.arthenica.mobileffmpeg.player.PlayerSurface;
import com.arthenica.mobileffmpeg.util.DialogUtil;
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
import com.arthenica.mobileffmpeg.util.SingleExecuteCallback;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.IOException;
@@ -58,13 +58,11 @@ import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
public class VideoTabFragment extends Fragment implements AdapterView.OnItemSelectedListener {
private VideoView videoView;
private AlertDialog progressDialog;
private String selectedCodec;
private Statistics statistics;
private PlayerSession playerSession;
private PlayerSurface playerSurface;
public VideoTabFragment() {
super(R.layout.fragment_video_tab);
}
@@ -86,20 +84,23 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
@Override
public void onClick(View v) {
playVideo();
// encodeVideo();
encodeVideo();
// encodeWebp();
}
});
}
videoView = view.findViewById(R.id.videoPlayerFrame);
progressDialog = DialogUtil.createProgressDialog(requireContext(), "Encoding video");
selectedCodec = getResources().getStringArray(R.array.video_codec)[0];
}
playerSession = new PlayerSession(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, getContext(), getVideoFile().getAbsolutePath());
playerSurface = view.findViewById(R.id.videoPlayerFrame);
playerSurface.init(getActivity(), new GenericMotionListener(), playerSession);
@Override
public void onResume() {
super.onResume();
setActive();
}
public static VideoTabFragment newInstance() {
@@ -121,7 +122,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
@Override
public void apply(final Statistics newStatistics) {
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -155,7 +156,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
try {
// IF VIDEO IS PLAYING STOP PLAYBACK
// videoView.stopPlayback();
videoView.stopPlayback();
if (videoFile.exists()) {
videoFile.delete();
@@ -173,13 +174,13 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath(), getSelectedVideoCodec(), getCustomOptions());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
MainActivity.executeAsync(new SingleExecuteCallback() {
long executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
@Override
public void apply(final int returnCode, final String commandOutput) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
public void apply(final long executionId, final int returnCode) {
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
Log.d(TAG, "FFmpeg process output:");
@@ -187,7 +188,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
hideProgressDialog();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -196,17 +197,19 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
playVideo();
} else {
Popup.show(requireContext(), "Encode failed. Please check log for the details.");
Log.d(TAG, String.format("Encode failed with rc=%d", returnCode));
Log.d(TAG, String.format("Encode failed with rc=%d.", returnCode));
}
return null;
}
});
}
}, ffmpegCommand);
});
Log.d(TAG, String.format("Async FFmpeg process started with executionId %d.", executionId));
} catch (IOException e) {
Log.e(TAG, "Encode video failed", e);
Log.e(TAG, String.format("Encode video failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode video failed");
}
}
@@ -222,19 +225,19 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
final String ffmpegCommand = String.format("-hide_banner -i %s %s", imageFile.getAbsolutePath(), outputFile.getAbsolutePath());
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
int returnCode = FFmpeg.execute(ffmpegCommand);
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
} catch (IOException e) {
Log.e(TAG, "Encode webp failed", e);
Log.e(TAG, String.format("Encode webp failed %s.", Exceptions.getStackTraceString(e)));
Popup.show(requireContext(), "Encode webp failed");
}
}
protected void playVideo() {
/* MediaController mediaController = new MediaController(requireContext());
MediaController mediaController = new MediaController(requireContext());
mediaController.setAnchorView(videoView);
videoView.setVideoURI(Uri.parse("file://" + getVideoFile().getAbsolutePath()));
videoView.setMediaController(mediaController);
@@ -254,12 +257,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
return false;
}
});
videoView.start();*/
// Intent intent = new Intent(getContext(), FullScreenActivity.class);
// intent.putExtra(PlayerSession.FFPLAY_COMMAND, getVideoFile().getAbsolutePath());
// startActivity(intent);
playerSurface.play();
videoView.start();
}
public String getSelectedVideoCodec() {
@@ -273,6 +271,9 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
case "x264":
videoCodec = "libx264";
break;
case "openh264":
videoCodec = "libopenh264";
break;
case "x265":
videoCodec = "libx265";
break;
@@ -355,7 +356,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
Log.i(MainActivity.TAG, "Video Tab Activated");
enableLogCallback();
enableStatisticsCallback();
Popup.show(requireContext(), Tooltip.VIDEO_TEST_TOOLTIP_TEXT);
Popup.show(requireContext(), getString(R.string.video_test_tooltip_text));
}
protected void showProgressDialog() {
@@ -380,7 +381,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
TextView textView = progressDialog.findViewById(R.id.progressDialogText);
if (textView != null) {
textView.setText(String.format("Encoding video: %% %s", completePercentage));
textView.setText(String.format("Encoding video: %% %s.", completePercentage));
}
}
}
@@ -388,7 +389,7 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
protected void hideProgressDialog() {
progressDialog.dismiss();
MainActivity.addUIAction(new Callable() {
MainActivity.addUIAction(new Callable<Object>() {
@Override
public Object call() {
@@ -398,41 +399,4 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
});
}
@Override
public void onPause() {
Log.v(Config.TAG, "VideoTabFragment paused.");
playerSurface.setNextNativeState(PlayerSession.NativeState.PAUSED);
playerSurface.setResumedCalled(false);
playerSurface.handleNativeState();
super.onPause();
}
@Override
public void onResume() {
Log.v(Config.TAG, "VideoTabFragment resumed.");
setActive();
playerSurface.setNextNativeState(PlayerSession.NativeState.RESUMED);
playerSurface.setResumedCalled(true);
playerSurface.handleNativeState();
super.onResume();
}
@Override
public void onLowMemory() {
Log.v(Config.TAG, "VideoTabFragment is on low memory.");
FFplay.playerNativeLowMemory();
super.onLowMemory();
}
@Override
public void onDestroy() {
Log.v(Config.TAG, "VideoTabFragment destroyed.");
playerSurface.setNextNativeState(PlayerSession.NativeState.PAUSED);
playerSurface.handleNativeState();
// Send a quit message to the application
FFplay.playerNativeQuit();
super.onDestroy();
}
}
@@ -22,6 +22,8 @@ package com.arthenica.mobileffmpeg.util;
import android.os.AsyncTask;
import android.util.Log;
import com.arthenica.smartexception.java.Exceptions;
import java.io.IOException;
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
@@ -41,7 +43,7 @@ public class AsyncCatImageTask extends AsyncTask<String, Integer, Integer> {
return rc;
} catch (final IOException | InterruptedException e) {
Log.e(TAG, String.format("Async cat image command failed for %s.", inputs[0]), e);
Log.e(TAG, String.format("Async cat image command failed for %s.%s", inputs[0], Exceptions.getStackTraceString(e)));
return -1;
}
}
@@ -25,12 +25,15 @@ import android.graphics.BitmapFactory;
import android.util.Log;
import com.arthenica.mobileffmpeg.test.MainActivity;
import com.arthenica.smartexception.java.Exceptions;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
public class ResourcesUtil {
public static void resourceToFile(Resources resources, final int resourceId, final File file) throws IOException {
Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId);
@@ -60,7 +63,7 @@ public class ResourcesUtil {
outputStream.write(buffer, 0, readSize);
}
} catch (final IOException e) {
Log.e(MainActivity.TAG, "Saving raw resource failed.", e);
Log.e(TAG, String.format("Saving raw resource failed.%s", Exceptions.getStackTraceString(e)));
} finally {
inputStream.close();
outputStream.flush();
@@ -42,7 +42,7 @@
android:background="@drawable/dialog_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/dialog_cancel_button_text"
android:text="@string/cancel_button_text"
android:textAlignment="center"
android:textColor="@android:color/black"
android:textSize="16sp"
@@ -45,7 +45,7 @@
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/audio_encode_button_text"
android:text="@string/encode_button_text"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".ConcurrentExecutionTabFragment">
<LinearLayout
android:id="@+id/videoEncodeSpinnerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:layout_marginTop="40dp"
android:gravity="center"
android:orientation="horizontal"/>
<LinearLayout
android:id="@+id/encodeButtonLayout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/encodeButton1"
android:layout_width="90dp"
android:layout_height="36dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/encode_button_text_1"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/encodeButton2"
android:layout_width="90dp"
android:layout_height="36dp"
android:layout_margin="20dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/encode_button_text_2"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/encodeButton3"
android:layout_width="90dp"
android:layout_height="36dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/encode_button_text_3"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/cancelButtonLayout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/cancelButton1"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_1"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/cancelButton2"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_2"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/cancelButton3"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_3"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/cancelButtonAll"
android:layout_width="90dp"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/cancel_button_text_all"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/outputText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:layout_marginBottom="20dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:background="@drawable/rounded_output_frame"
android:fontFamily="sans-serif"
android:gravity="bottom"
android:overScrollMode="ifContentScrolls"
android:scrollbars="vertical"
android:textColor="@android:color/black"
android:textSize="14sp"
android:typeface="sans"
tools:targetApi="jelly_bean" />
</LinearLayout>
@@ -45,14 +45,14 @@
android:background="@drawable/rounded_button"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/video_encode_button_text"
android:text="@string/encode_button_text"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<com.arthenica.mobileffmpeg.player.PlayerSurface
<VideoView
android:id="@+id/videoPlayerFrame"
android:layout_width="wrap_content"
android:background="@drawable/rounded_video_frame"
@@ -6,7 +6,8 @@
<item>mp3 (libshine)</item>
<item>vorbis</item>
<item>opus</item>
<item>amr</item>
<item>amr-nb</item>
<item>amr-wb</item>
<item>ilbc</item>
<item>soxr</item>
<item>speex</item>
@@ -7,15 +7,22 @@
<string name="subtitle_tab">SUBTITLE</string>
<string name="vidstab_tab">VID.STAB</string>
<string name="pipe_tab">PIPE</string>
<string name="concurrent_tab">CONCURRENT</string>
<string name="command_text_input_placeholder">Enter command</string>
<string name="command_run_ffmpeg_button_text">RUN FFMPEG</string>
<string name="command_run_ffprobe_button_text">RUN FFPROBE</string>
<string name="video_encode_button_text">ENCODE</string>
<string name="encode_button_text">ENCODE</string>
<string name="encode_button_text_1">ENCODE 1</string>
<string name="encode_button_text_2">ENCODE 2</string>
<string name="encode_button_text_3">ENCODE 3</string>
<string name="video_create_button_text">CREATE</string>
<string name="https_get_info_button_text">GET INFO</string>
<string name="https_text_input_placeholder">Enter https url</string>
<string name="audio_encode_button_text">ENCODE</string>
<string name="subtitle_burn_subtitles_button_text">BURN SUBTITLES</string>
<string name="vidstab_stabilize_video_button_text">STABILIZE VIDEO</string>
<string name="dialog_cancel_button_text">Cancel</string>
<string name="cancel_button_text">Cancel</string>
<string name="cancel_button_text_1">Cancel 1</string>
<string name="cancel_button_text_2">Cancel 2</string>
<string name="cancel_button_text_3">Cancel 3</string>
<string name="cancel_button_text_all">Cancel All</string>
</resources>
@@ -0,0 +1,10 @@
<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>
</resources>
@@ -3,6 +3,7 @@
<string-array name="video_codec">
<item>mpeg4</item>
<item>x264</item>
<item>openh264</item>
<item>x265</item>
<item>xvid</item>
<item>vp8</item>
+3 -3
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="chromaprint"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
CFLAGS=$(get_cflags ${LIB_NAME})
CXXFLAGS=$(get_cxxflags ${LIB_NAME})
LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -64,6 +64,6 @@ cmake -Wno-dev \
make -j$(get_cpu_count) || exit 1
# CREATE PACKAGE CONFIG MANUALLY
create_chromaprint_package_config "1.4.3"
create_chromaprint_package_config "1.5.0"
make install || exit 1
+92 -346
View File
@@ -1,20 +1,6 @@
#!/bin/bash
get_cpu_count() {
if [ "$(uname)" == "Darwin" ]; then
echo $(sysctl -n hw.physicalcpu)
else
echo $(nproc)
fi
}
prepare_inline_sed() {
if [ "$(uname)" == "Darwin" ]; then
export SED_INLINE="sed -i .tmp"
else
export SED_INLINE="sed -i"
fi
}
source "${BASEDIR}/build/arch-common.sh"
get_library_name() {
case $1 in
@@ -40,28 +26,32 @@ get_library_name() {
19) echo "xvidcore" ;;
20) echo "x265" ;;
21) echo "libvidstab" ;;
22) echo "libilbc" ;;
23) echo "opus" ;;
24) echo "snappy" ;;
25) echo "soxr" ;;
26) echo "libaom" ;;
27) echo "chromaprint" ;;
28) echo "twolame" ;;
29) echo "sdl" ;;
30) echo "tesseract" ;;
31) echo "openh264" ;;
32) echo "giflib" ;;
33) echo "jpeg" ;;
34) echo "libogg" ;;
35) echo "libpng" ;;
36) echo "libuuid" ;;
37) echo "nettle" ;;
38) echo "tiff" ;;
39) echo "expat" ;;
40) echo "libsndfile" ;;
41) echo "leptonica" ;;
42) echo "android-zlib" ;;
43) echo "android-media-codec" ;;
22) echo "rubberband" ;;
23) echo "libilbc" ;;
24) echo "opus" ;;
25) echo "snappy" ;;
26) echo "soxr" ;;
27) echo "libaom" ;;
28) echo "chromaprint" ;;
29) echo "twolame" ;;
30) echo "sdl" ;;
31) echo "tesseract" ;;
32) echo "openh264" ;;
33) echo "vo-amrwbenc" ;;
34) echo "giflib" ;;
35) echo "jpeg" ;;
36) echo "libogg" ;;
37) echo "libpng" ;;
38) echo "libuuid" ;;
39) echo "nettle" ;;
40) echo "tiff" ;;
41) echo "expat" ;;
42) echo "libsndfile" ;;
43) echo "leptonica" ;;
44) echo "libsamplerate" ;;
45) echo "android-zlib" ;;
46) echo "android-media-codec" ;;
47) echo "cpu-features" ;;
esac
}
@@ -75,7 +65,7 @@ get_arch_name() {
esac
}
get_target_host() {
get_build_host() {
case ${ARCH} in
arm-v7a | arm-v7a-neon)
echo "arm-linux-androideabi"
@@ -151,11 +141,7 @@ get_target_build() {
echo "arm"
;;
arm-v7a-neon)
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]]; then
echo "arm/neon"
else
echo "arm"
fi
echo "arm/neon"
;;
arm64-v8a)
echo "arm64"
@@ -204,7 +190,7 @@ get_android_arch() {
}
get_common_includes() {
echo "-I${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/include -I${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/local/include"
echo ""
}
get_common_cflags() {
@@ -250,27 +236,27 @@ get_size_optimization_cflags() {
ARCH_OPTIMIZATION="${LINK_TIME_OPTIMIZATION_FLAGS} -O2 -ffunction-sections -fdata-sections"
;;
*)
ARCH_OPTIMIZATION="-O2 -ffunction-sections -fdata-sections"
ARCH_OPTIMIZATION="-Os -ffunction-sections -fdata-sections"
;;
esac
;;
arm64-v8a)
case $1 in
ffmpeg)
ARCH_OPTIMIZATION="${LINK_TIME_OPTIMIZATION_FLAGS} -fuse-ld=bfd -O2 -ffunction-sections -fdata-sections"
ARCH_OPTIMIZATION="${LINK_TIME_OPTIMIZATION_FLAGS} -fuse-ld=gold -O2 -ffunction-sections -fdata-sections"
;;
*)
ARCH_OPTIMIZATION="-O2 -ffunction-sections -fdata-sections"
ARCH_OPTIMIZATION="-Os -ffunction-sections -fdata-sections"
;;
esac
;;
x86 | x86-64)
case $1 in
ffmpeg)
ARCH_OPTIMIZATION="${LINK_TIME_OPTIMIZATION_FLAGS} -O2 -ffunction-sections -fdata-sections"
ARCH_OPTIMIZATION="${LINK_TIME_OPTIMIZATION_FLAGS} -Os -ffunction-sections -fdata-sections"
;;
*)
ARCH_OPTIMIZATION="-O2 -ffunction-sections -fdata-sections"
ARCH_OPTIMIZATION="-Os -ffunction-sections -fdata-sections"
;;
esac
;;
@@ -282,7 +268,6 @@ get_size_optimization_cflags() {
}
get_app_specific_cflags() {
local APP_FLAGS=""
case $1 in
xvidcore)
@@ -294,12 +279,12 @@ get_app_specific_cflags() {
kvazaar)
APP_FLAGS="-std=gnu99 -Wno-unused-function"
;;
rubberband)
APP_FLAGS="-std=c99 -Wno-unused-function"
;;
shine)
APP_FLAGS="-Wno-unused-function"
;;
sdl)
APP_FLAGS="-std=c11 -Wno-unused-function"
;;
soxr | snappy | libwebp)
APP_FLAGS="-std=gnu99 -Wno-unused-function -DPIC"
;;
@@ -333,7 +318,7 @@ get_cxxflags() {
fi
if [[ -z ${MOBILE_FFMPEG_DEBUG} ]]; then
local OPTIMIZATION_FLAGS="-O2 -ffunction-sections -fdata-sections"
local OPTIMIZATION_FLAGS="-Os -ffunction-sections -fdata-sections"
else
local OPTIMIZATION_FLAGS="${MOBILE_FFMPEG_DEBUG}"
fi
@@ -355,6 +340,9 @@ get_cxxflags() {
x265)
echo "-std=c++11 -fno-exceptions ${OPTIMIZATION_FLAGS}"
;;
rubberband)
echo "-std=c++11 ${OPTIMIZATION_FLAGS}"
;;
*)
echo "-std=c++11 -fno-exceptions -fno-rtti ${OPTIMIZATION_FLAGS}"
;;
@@ -362,7 +350,7 @@ get_cxxflags() {
}
get_common_linked_libraries() {
local COMMON_LIBRARY_PATHS="-L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/${TARGET_HOST}/lib -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/lib/${TARGET_HOST}/${API} -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/lib"
local COMMON_LIBRARY_PATHS="-L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/${BUILD_HOST}/lib -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/lib/${BUILD_HOST}/${API} -L${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/lib"
case $1 in
ffmpeg)
@@ -395,10 +383,10 @@ get_size_optimization_ldflags() {
arm64-v8a)
case $1 in
ffmpeg)
echo "-Wl,--gc-sections ${LINK_TIME_OPTIMIZATION_FLAGS} -fuse-ld=bfd -O2 -ffunction-sections -fdata-sections -finline-functions"
echo "-Wl,--gc-sections ${LINK_TIME_OPTIMIZATION_FLAGS} -fuse-ld=gold -O2 -ffunction-sections -fdata-sections -finline-functions"
;;
*)
echo "-Wl,--gc-sections -O2 -ffunction-sections -fdata-sections"
echo "-Wl,--gc-sections -Os -ffunction-sections -fdata-sections"
;;
esac
;;
@@ -408,7 +396,7 @@ get_size_optimization_ldflags() {
echo "-Wl,--gc-sections,--icf=safe ${LINK_TIME_OPTIMIZATION_FLAGS} -O2 -ffunction-sections -fdata-sections -finline-functions"
;;
*)
echo "-Wl,--gc-sections,--icf=safe -O2 -ffunction-sections -fdata-sections"
echo "-Wl,--gc-sections,--icf=safe -Os -ffunction-sections -fdata-sections"
;;
esac
;;
@@ -677,25 +665,6 @@ Cflags: -I\${includedir}
EOF
}
create_libwebp_package_config() {
local LIB_WEBP_VERSION="$1"
cat > "${INSTALL_PKG_CONFIG_DIR}/libwebp.pc" << EOF
prefix=${BASEDIR}/prebuilt/android-$(get_target_build)/libwebp
exec_prefix=\${prefix}
libdir=\${prefix}/lib
includedir=\${prefix}/include
Name: libwebp
Description: webp codec library
Version: ${LIB_WEBP_VERSION}
Requires:
Libs: -L\${libdir} -lwebp -lwebpdecoder -lwebpdemux
Cflags: -I\${includedir}
EOF
}
create_libxml2_package_config() {
local LIBXML2_VERSION="$1"
@@ -834,7 +803,7 @@ Cflags: -I\${includedir}
EOF
}
create_zlib_package_config() {
create_zlib_system_package_config() {
ZLIB_VERSION=$(grep '#define ZLIB_VERSION' ${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot/usr/include/zlib.h | grep -Eo '\".*\"' | sed -e 's/\"//g')
cat > "${INSTALL_PKG_CONFIG_DIR}/zlib.pc" << EOF
@@ -854,168 +823,75 @@ EOF
}
create_cpufeatures_package_config() {
cat > "${INSTALL_PKG_CONFIG_DIR}/cpufeatures.pc" << EOF
prefix=${ANDROID_NDK_ROOT}/sources/android/cpufeatures
exec_prefix=\${prefix}
libdir=\${exec_prefix}
includedir=\${prefix}
cat > "${INSTALL_PKG_CONFIG_DIR}/cpu-features.pc" << EOF
prefix=${BASEDIR}/prebuilt/android-$(get_target_build)/cpu-features
exec_prefix=\${prefix}/bin
libdir=\${prefix}/lib
includedir=\${prefix}/include/ndk_compat
Name: cpufeatures
Description: cpu features Android utility
URL: https://github.com/google/cpu_features
Description: cpu_features Android compatibility library
Version: 1.${API}
Requires:
Libs: -L\${libdir} -lcpufeatures
Libs: -L\${libdir} -lndk_compat
Cflags: -I\${includedir}
EOF
}
#
# download <url> <local file name> <on error action>
#
download() {
if [ ! -d "${MOBILE_FFMPEG_TMPDIR}" ]; then
mkdir -p "${MOBILE_FFMPEG_TMPDIR}"
fi
(curl --fail --location $1 -o ${MOBILE_FFMPEG_TMPDIR}/$2 1>>${BASEDIR}/build.log 2>&1)
local RC=$?
if [ ${RC} -eq 0 ]; then
echo -e "\nDEBUG: Downloaded $1 to ${MOBILE_FFMPEG_TMPDIR}/$2\n" 1>>${BASEDIR}/build.log 2>&1
else
rm -f ${MOBILE_FFMPEG_TMPDIR}/$2 1>>${BASEDIR}/build.log 2>&1
echo -e -n "\nINFO: Failed to download $1 to ${MOBILE_FFMPEG_TMPDIR}/$2, rc=${RC}. " 1>>${BASEDIR}/build.log 2>&1
if [ "$3" == "exit" ]; then
echo -e "DEBUG: Build will now exit.\n" 1>>${BASEDIR}/build.log 2>&1
exit 1
else
echo -e "DEBUG: Build will continue.\n" 1>>${BASEDIR}/build.log 2>&1
fi
fi
echo ${RC}
}
download_gpl_library_source() {
local GPL_LIB_URL=""
local GPL_LIB_FILE=""
local GPL_LIB_ORIG_DIR=""
local GPL_LIB_DEST_DIR=""
echo -e "\nDEBUG: Downloading GPL library source: $1\n" 1>>${BASEDIR}/build.log 2>&1
case $1 in
libvidstab)
GPL_LIB_URL="https://github.com/georgmartius/vid.stab/archive/v1.1.0.tar.gz"
GPL_LIB_FILE="v1.1.0.tar.gz"
GPL_LIB_ORIG_DIR="vid.stab-1.1.0"
GPL_LIB_DEST_DIR="libvidstab"
android_ndk_abi() { # to be used with CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake
case ${ARCH} in
arm-v7a | arm-v7a-neon)
echo "armeabi-v7a"
;;
x264)
GPL_LIB_URL="https://code.videolan.org/videolan/x264/-/archive/1771b556ee45207f8711744ccbd5d42a3949b14c/x264-1771b556ee45207f8711744ccbd5d42a3949b14c.tar.bz2"
GPL_LIB_FILE="x264-1771b556ee45207f8711744ccbd5d42a3949b14c.tar.bz2"
GPL_LIB_ORIG_DIR="x264-1771b556ee45207f8711744ccbd5d42a3949b14c"
GPL_LIB_DEST_DIR="x264"
arm64-v8a)
echo "arm64-v8a"
;;
x265)
GPL_LIB_URL="https://bitbucket.org/multicoreware/x265/downloads/x265_3.2.1.tar.gz"
GPL_LIB_FILE="x265-3.2.1.tar.gz"
GPL_LIB_ORIG_DIR="x265_3.2.1"
GPL_LIB_DEST_DIR="x265"
x86)
echo "x86"
;;
xvidcore)
GPL_LIB_URL="https://downloads.xvid.com/downloads/xvidcore-1.3.7.tar.gz"
GPL_LIB_FILE="xvidcore-1.3.7.tar.gz"
GPL_LIB_ORIG_DIR="xvidcore"
GPL_LIB_DEST_DIR="xvidcore"
x86-64)
echo "x86_64"
;;
esac
}
local GPL_LIB_SOURCE_PATH="${BASEDIR}/src/${GPL_LIB_DEST_DIR}"
android_build_dir() {
echo ${BASEDIR}/android/build/${LIB_NAME}/$(get_target_build)
}
if [ -d "${GPL_LIB_SOURCE_PATH}" ]; then
echo -e "INFO: $1 already downloaded. Source folder found at ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
echo 0
return
android_ndk_cmake() {
local cmake=$(find ${ANDROID_HOME}/cmake -path \*/bin/cmake -type f -print -quit)
if [[ -z ${cmake} ]]; then
cmake=$(which cmake)
fi
if [[ -z ${cmake} ]]; then
cmake="missing_cmake"
fi
local GPL_LIB_PACKAGE_PATH="${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_FILE}"
echo -e "DEBUG: $1 source not found. Checking if library package ${GPL_LIB_FILE} is downloaded at ${GPL_LIB_PACKAGE_PATH} \n" 1>>${BASEDIR}/build.log 2>&1
if [ ! -f "${GPL_LIB_PACKAGE_PATH}" ]; then
echo -e "DEBUG: $1 library package not found. Downloading from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
local DOWNLOAD_RC=$(download "${GPL_LIB_URL}" "${GPL_LIB_FILE}")
if [ ${DOWNLOAD_RC} -ne 0 ]; then
echo -e "INFO: Downloading GPL library $1 failed. Can not get library package from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
echo ${DOWNLOAD_RC}
return
else
echo -e "DEBUG: $1 library package downloaded\n" 1>>${BASEDIR}/build.log 2>&1
fi
else
echo -e "DEBUG: $1 library package already downloaded\n" 1>>${BASEDIR}/build.log 2>&1
fi
local EXTRACT_COMMAND=""
if [[ ${GPL_LIB_FILE} == *bz2 ]]; then
EXTRACT_COMMAND="tar jxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
else
EXTRACT_COMMAND="tar zxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
fi
echo -e "DEBUG: Extracting library package ${GPL_LIB_FILE} inside ${MOBILE_FFMPEG_TMPDIR}\n" 1>>${BASEDIR}/build.log 2>&1
${EXTRACT_COMMAND} 1>>${BASEDIR}/build.log 2>&1
local EXTRACT_RC=$?
if [ ${EXTRACT_RC} -ne 0 ]; then
echo -e "\nINFO: Downloading GPL library $1 failed. Extract for library package ${GPL_LIB_FILE} completed with rc=${EXTRACT_RC}. Deleting failed files.\n" 1>>${BASEDIR}/build.log 2>&1
rm -f ${GPL_LIB_PACKAGE_PATH} 1>>${BASEDIR}/build.log 2>&1
rm -rf ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} 1>>${BASEDIR}/build.log 2>&1
echo ${EXTRACT_RC}
return
fi
echo -e "DEBUG: Extract completed. Copying library source to ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
COPY_COMMAND="cp -r ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} ${GPL_LIB_SOURCE_PATH}"
${COPY_COMMAND} 1>>${BASEDIR}/build.log 2>&1
local COPY_RC=$?
if [ ${COPY_RC} -eq 0 ]; then
echo -e "DEBUG: Downloading GPL library source $1 completed successfully\n" 1>>${BASEDIR}/build.log 2>&1
else
echo -e "\nINFO: Downloading GPL library $1 failed. Copying library source to ${GPL_LIB_SOURCE_PATH} completed with rc=${COPY_RC}\n" 1>>${BASEDIR}/build.log 2>&1
rm -rf ${GPL_LIB_SOURCE_PATH} 1>>${BASEDIR}/build.log 2>&1
echo ${COPY_RC}
return
fi
echo ${cmake} \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake \
-H${BASEDIR}/src/${LIB_NAME} \
-B$(android_build_dir) \
-DANDROID_ABI=$(android_ndk_abi) \
-DANDROID_PLATFORM=android-${API} \
-DCMAKE_INSTALL_PREFIX=${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME}
}
set_toolchain_clang_paths() {
export PATH=$PATH:${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/bin
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export AR=${TARGET_HOST}-ar
export AR=${BUILD_HOST}-ar
export CC=$(get_clang_target_host)-clang
export CXX=$(get_clang_target_host)-clang++
if [ "$1" == "x264" ]; then
export AS=${CC}
else
export AS=${TARGET_HOST}-as
export AS=${BUILD_HOST}-as
fi
case ${ARCH} in
@@ -1024,9 +900,9 @@ set_toolchain_clang_paths() {
;;
esac
export LD=${TARGET_HOST}-ld
export RANLIB=${TARGET_HOST}-ranlib
export STRIP=${TARGET_HOST}-strip
export LD=${BUILD_HOST}-ld
export RANLIB=${BUILD_HOST}-ranlib
export STRIP=${BUILD_HOST}-strip
export INSTALL_PKG_CONFIG_DIR="${BASEDIR}/prebuilt/android-$(get_target_build)/pkgconfig"
export ZLIB_PACKAGE_CONFIG_PATH="${INSTALL_PKG_CONFIG_DIR}/zlib.pc"
@@ -1036,36 +912,12 @@ set_toolchain_clang_paths() {
fi
if [ ! -f ${ZLIB_PACKAGE_CONFIG_PATH} ]; then
create_zlib_package_config
create_zlib_system_package_config
fi
prepare_inline_sed
}
build_cpufeatures() {
# CLEAN OLD BUILD
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.a 1>>${BASEDIR}/build.log 2>&1
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.so 1>>${BASEDIR}/build.log 2>&1
echo -e "\nINFO: Building cpu-features for ${ARCH}\n" 1>>${BASEDIR}/build.log 2>&1
set_toolchain_clang_paths "cpu-features"
TARGET_HOST=$(get_target_host)
export CFLAGS=$(get_cflags "cpu-features")
export CXXFLAGS=$(get_cxxflags "cpu-features")
export LDFLAGS=$(get_ldflags "cpu-features")
# THEN BUILD FOR THIS ABI
$(get_clang_target_host)-clang -c ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.c -o ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
${TARGET_HOST}-ar rcs ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.a ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
$(get_clang_target_host)-clang -shared ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o -o ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.so 1>>${BASEDIR}/build.log 2>&1
create_cpufeatures_package_config
}
build_android_lts_support() {
# CLEAN OLD BUILD
@@ -1079,117 +931,11 @@ build_android_lts_support() {
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
CFLAGS=$(get_cflags ${LIB_NAME})
LDFLAGS=$(get_ldflags ${LIB_NAME})
# THEN BUILD FOR THIS ABI
$(get_clang_target_host)-clang ${CFLAGS} -Wno-unused-command-line-argument -c ${BASEDIR}/android/app/src/main/cpp/android_lts_support.c -o ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o ${LDFLAGS} 1>>${BASEDIR}/build.log 2>&1
${TARGET_HOST}-ar rcs ${BASEDIR}/android/app/src/main/cpp/libandroidltssupport.a ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o 1>>${BASEDIR}/build.log 2>&1
}
autoreconf_library() {
echo -e "\nDEBUG: Running full autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY FULL RECONF
(autoreconf --force --install)
local EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Full autoreconf failed. Running full autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY FULL RECONF WITH m4
(autoreconf --force --install -I m4)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Full autoreconf with include failed. Running autoreconf without force for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY RECONF WITHOUT FORCE
(autoreconf --install)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Autoreconf without force failed. Running autoreconf without force with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY RECONF WITHOUT FORCE WITH m4
(autoreconf --install -I m4)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Autoreconf without force with include failed. Running default autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY DEFAULT RECONF
(autoreconf)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
echo -e "\nDEBUG: Default autoreconf failed. Running default autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
# TRY DEFAULT RECONF WITH m4
(autoreconf -I m4)
EXTRACT_RC=$?
if [ ${EXTRACT_RC} -eq 0 ]; then
return
fi
}
library_is_installed() {
local INSTALL_PATH=$1
local LIB_NAME=$2
echo -e "DEBUG: Checking if ${LIB_NAME} is already built and installed at ${INSTALL_PATH}/${LIB_NAME}\n" 1>>${BASEDIR}/build.log 2>&1
if [ ! -d ${INSTALL_PATH}/${LIB_NAME} ]; then
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME} directory not found\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/lib ]; then
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/lib directory not found\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/include ]; then
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/include directory not found\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
local HEADER_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/include | wc -l)
local LIB_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/lib | wc -l)
if [[ ${HEADER_COUNT} -eq 0 ]]; then
echo -e "DEBUG: No headers found under ${INSTALL_PATH}/${LIB_NAME}/include\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
if [[ ${LIB_COUNT} -eq 0 ]]; then
echo -e "DEBUG: No libraries found under ${INSTALL_PATH}/${LIB_NAME}/lib\n" 1>>${BASEDIR}/build.log 2>&1
echo 1
return
fi
echo -e "INFO: ${LIB_NAME} library is already built and installed\n" 1>>${BASEDIR}/build.log 2>&1
echo 0
${BUILD_HOST}-ar rcs ${BASEDIR}/android/app/src/main/cpp/libandroidltssupport.a ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o 1>>${BASEDIR}/build.log 2>&1
}
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
echo -e "(*) ANDROID_NDK_ROOT not defined\n"
exit 1
fi
if [[ -z ${ARCH} ]]; then
echo -e "(*) ARCH not defined\n"
exit 1
fi
if [[ -z ${API} ]]; then
echo -e "(*) API not defined\n"
exit 1
fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
LIB_NAME="cpu-features"
set_toolchain_clang_paths ${LIB_NAME}
# DOWNLOAD LIBRARY
DOWNLOAD_RESULT=$(download_library_source ${LIB_NAME})
if [[ ${DOWNLOAD_RESULT} -ne 0 ]]; then
exit 1
fi
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
$(android_ndk_cmake) -DBUILD_PIC=ON || exit 1
make -C $(android_build_dir) install || exit 1
create_cpufeatures_package_config
+4 -4
View File
@@ -23,12 +23,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="expat"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
export CFLAGS=$(get_cflags ${LIB_NAME})
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
export LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -37,7 +37,7 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
make distclean 2>/dev/null 1>/dev/null
# RECONFIGURING IF REQUESTED
# RECONFIGURE IF REQUESTED
if [[ ${RECONF_expat} -eq 1 ]]; then
autoreconf_library ${LIB_NAME}
fi
@@ -51,7 +51,7 @@ fi
--enable-static \
--disable-shared \
--disable-fast-install \
--host=${TARGET_HOST} || exit 1
--host=${BUILD_HOST} || exit 1
make -j$(get_cpu_count) || exit 1
+42 -27
View File
@@ -29,12 +29,12 @@ fi
# ENABLE COMMON FUNCTIONS
. ${BASEDIR}/build/android-common.sh
# PREPARING PATHS & DEFINING ${INSTALL_PKG_CONFIG_DIR}
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
LIB_NAME="ffmpeg"
set_toolchain_clang_paths ${LIB_NAME}
# PREPARING FLAGS
TARGET_HOST=$(get_target_host)
BUILD_HOST=$(get_build_host)
CFLAGS=$(get_cflags ${LIB_NAME})
CXXFLAGS=$(get_cxxflags ${LIB_NAME})
LDFLAGS=$(get_ldflags ${LIB_NAME})
@@ -42,41 +42,41 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
TARGET_CPU=""
TARGET_ARCH=""
ASM_FLAGS=""
ARCH_OPTIONS=""
case ${ARCH} in
arm-v7a)
TARGET_CPU="armv7-a"
TARGET_ARCH="armv7-a"
ASM_FLAGS=" --disable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --disable-neon --enable-asm --enable-inline-asm"
;;
arm-v7a-neon)
TARGET_CPU="armv7-a"
TARGET_ARCH="armv7-a"
ASM_FLAGS=" --enable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --enable-neon --enable-asm --enable-inline-asm --build-suffix=_neon"
;;
arm64-v8a)
TARGET_CPU="armv8-a"
TARGET_ARCH="aarch64"
ASM_FLAGS=" --enable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --enable-neon --enable-asm --enable-inline-asm"
;;
x86)
TARGET_CPU="i686"
TARGET_ARCH="i686"
# asm disabled due to this ticker https://trac.ffmpeg.org/ticket/4928
ASM_FLAGS=" --disable-neon --disable-asm --disable-inline-asm"
ARCH_OPTIONS=" --disable-neon --disable-asm --disable-inline-asm"
;;
x86-64)
TARGET_CPU="x86_64"
TARGET_ARCH="x86_64"
ASM_FLAGS=" --disable-neon --enable-asm --enable-inline-asm"
ARCH_OPTIONS=" --disable-neon --enable-asm --enable-inline-asm"
;;
esac
CONFIGURE_POSTFIX=""
HIGH_PRIORITY_INCLUDES=""
for library in {1..44}
for library in {1..49}
do
if [[ ${!library} -eq 1 ]]; then
ENABLED_LIBRARY=$(get_library_name $((library - 1)))
@@ -163,7 +163,7 @@ do
libvpx)
CFLAGS+=" $(pkg-config --cflags vpx)"
LDFLAGS+=" $(pkg-config --libs vpx)"
LDFLAGS+=" $(pkg-config --libs --static cpufeatures)"
LDFLAGS+=" $(pkg-config --libs cpu-features)"
CONFIGURE_POSTFIX+=" --enable-libvpx"
;;
libwebp)
@@ -178,11 +178,8 @@ do
;;
opencore-amr)
CFLAGS+=" $(pkg-config --cflags opencore-amrnb)"
CFLAGS+=" $(pkg-config --cflags opencore-amrwb)"
LDFLAGS+=" $(pkg-config --libs --static opencore-amrnb)"
LDFLAGS+=" $(pkg-config --libs --static opencore-amrwb)"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrnb"
CONFIGURE_POSTFIX+=" --enable-libopencore-amrwb"
;;
openh264)
FFMPEG_CFLAGS+=" $(pkg-config --cflags openh264)"
@@ -194,6 +191,11 @@ do
LDFLAGS+=" $(pkg-config --libs --static opus)"
CONFIGURE_POSTFIX+=" --enable-libopus"
;;
rubberband)
CFLAGS+=" $(pkg-config --cflags rubberband)"
LDFLAGS+=" $(pkg-config --libs --static rubberband)"
CONFIGURE_POSTFIX+=" --enable-librubberband --enable-gpl"
;;
shine)
CFLAGS+=" $(pkg-config --cflags shine)"
LDFLAGS+=" $(pkg-config --libs --static shine)"
@@ -231,6 +233,11 @@ do
LDFLAGS+=" $(pkg-config --libs --static twolame)"
CONFIGURE_POSTFIX+=" --enable-libtwolame"
;;
vo-amrwbenc)
CFLAGS+=" $(pkg-config --cflags vo-amrwbenc)"
LDFLAGS+=" $(pkg-config --libs --static vo-amrwbenc)"
CONFIGURE_POSTFIX+=" --enable-libvo-amrwbenc"
;;
wavpack)
CFLAGS+=" $(pkg-config --cflags wavpack)"
LDFLAGS+=" $(pkg-config --libs --static wavpack)"
@@ -280,14 +287,14 @@ do
;;
android-media-codec)
CONFIGURE_POSTFIX+=" --enable-mediacodec"
;;
esac
else
# THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT
if [[ ${library} -eq 30 ]]; then
# NOTE THAT IDS MUST BE +1 OF THE INDEX VALUE
if [[ ${library} -eq 31 ]]; then
CONFIGURE_POSTFIX+=" --disable-sdl2"
elif [[ ${library} -eq 43 ]]; then
elif [[ ${library} -eq 46 ]]; then
CONFIGURE_POSTFIX+=" --disable-zlib"
fi
fi
@@ -317,13 +324,19 @@ if [[ -z ${MOBILE_FFMPEG_DEBUG} ]]; then
DEBUG_OPTIONS="--disable-debug --disable-lto";
fi
else
DEBUG_OPTIONS="--enable-debug";
DEBUG_OPTIONS="--enable-debug --disable-stripping";
fi
echo -n -e "\n${LIB_NAME}: "
# DOWNLOAD LIBRARY
DOWNLOAD_RESULT=$(download_library_source ${LIB_NAME})
if [[ ${DOWNLOAD_RESULT} -ne 0 ]]; then
exit 1
fi
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
echo -n -e "\n${LIB_NAME}: "
if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then
echo -e "INFO: Cleaning workspace for ${LIB_NAME}" 1>>${BASEDIR}/build.log 2>&1
make distclean 2>/dev/null 1>/dev/null
@@ -336,12 +349,15 @@ export LDFLAGS="${LDFLAGS}"
# USE HIGHER LIMITS FOR FFMPEG LINKING
ulimit -n 2048 1>>${BASEDIR}/build.log 2>&1
# Workaround for issue #328
rm -f ${BASEDIR}/src/${LIB_NAME}/libswscale/aarch64/hscale.S 1>>${BASEDIR}/build.log 2>&1
cp ${BASEDIR}/tools/make/ffmpeg/libswscale/aarch64/hscale.S ${BASEDIR}/src/${LIB_NAME}/libswscale/aarch64/hscale.S 1>>${BASEDIR}/build.log 2>&1
########################### CUSTOMIZATIONS #######################
# 1. Use thread local log level
${SED_INLINE} 's/static int av_log_level/__thread int av_log_level/g' ${BASEDIR}/src/${LIB_NAME}/libavutil/log.c 1>>${BASEDIR}/build.log 2>&1
###################################################################
./configure \
--cross-prefix="${TARGET_HOST}-" \
--cross-prefix="${BUILD_HOST}-" \
--sysroot="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot" \
--prefix="${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME}" \
--pkg-config="${HOST_PKG_CONFIG_PATH}" \
@@ -350,18 +366,17 @@ cp ${BASEDIR}/tools/make/ffmpeg/libswscale/aarch64/hscale.S ${BASEDIR}/src/${LIB
--cpu="${TARGET_CPU}" \
--cc="${CC}" \
--cxx="${CXX}" \
--extra-libs="$(pkg-config --libs --static cpu-features)" \
--target-os=android \
${ASM_FLAGS} \
${ARCH_OPTIONS} \
--enable-cross-compile \
--enable-pic \
--enable-jni \
--enable-optimizations \
--enable-swscale \
--enable-shared \
--disable-v4l2-m2m \
--disable-outdev=v4l2 \
--enable-v4l2-m2m \
--disable-outdev=fbdev \
--disable-indev=v4l2 \
--disable-indev=fbdev \
${SIZE_OPTIONS} \
--disable-openssl \

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