Compare commits
118 Commits
master
...
development
| Author | SHA1 | Date | |
|---|---|---|---|
| 3327609fde | |||
| bd5dfc1241 | |||
| 6064b7dd02 | |||
| 9d1aefd1ea | |||
| 729ef3e496 | |||
| ce3a906b4e | |||
| dc4feaceb4 | |||
| 035ad8cd39 | |||
| b898574b53 | |||
| ef39b78068 | |||
| 3490446b1b | |||
| 0f911a26ed | |||
| 55700fb8bb | |||
| 4be74be2a2 | |||
| 70b8686eee | |||
| 870959fbd3 | |||
| 64b92f9d85 | |||
| 495e0490a9 | |||
| dbebf28b10 | |||
| 5b9637f517 | |||
| 5c3409484c | |||
| 8bbbf4b0a7 | |||
| 83fca5d126 | |||
| 56a8d28225 | |||
| 48836fc94a | |||
| db2fe927e6 | |||
| e9a6f5fb21 | |||
| 13a2d0e264 | |||
| 79cda81490 | |||
| 704ee05c3b | |||
| 8dd51a619d | |||
| 0dbdec7ae5 | |||
| 2bae484aff | |||
| 05880133ec | |||
| 091b4c62fb | |||
| 1c0dcf92ae | |||
| 47a64b3d7f | |||
| 7e289a014d | |||
| 4f6a80a5bb | |||
| abddac04ea | |||
| effa46c2d1 | |||
| 6f22bd47bd | |||
| 6f4e7577ba | |||
| b97fddb0ed | |||
| 258fa6e40a | |||
| 36be20cdf0 | |||
| 6d3189eee0 | |||
| ed65d00f59 | |||
| b4710f7025 | |||
| c2ffdd7a10 | |||
| 2e0ac940dc | |||
| 60892d4a84 | |||
| e3a1fbcc99 | |||
| accd5976cd | |||
| c888eee1c0 | |||
| c075268d0b | |||
| d46274f5af | |||
| 2ea824c983 | |||
| 074bcd553a | |||
| 13f72226d8 | |||
| 2469b8e85b | |||
| 1bb6cd532a | |||
| e52bad8a35 | |||
| 3efd2ecee5 | |||
| f6189815f0 | |||
| f382a0c3a0 | |||
| 1706d2aead | |||
| c43aa5bcdf | |||
| ca2e6f0ae0 | |||
| 8307c30491 | |||
| 986ad8c094 | |||
| 66539ab037 | |||
| 70ae95f5fa | |||
| 9095e3262e | |||
| 9a2e0a75f2 | |||
| ce10a88194 | |||
| f041df28a9 | |||
| d216310c9c | |||
| 8a1653fc7b | |||
| 687863efc8 | |||
| 7a3a94ee9b | |||
| 4a343d8b87 | |||
| b9c2b573df | |||
| 79d313e201 | |||
| 888d622662 | |||
| 6aeb10570d | |||
| 90ebed56f6 | |||
| ffccbc889d | |||
| 68bba7b7f2 | |||
| de7f3fcef7 | |||
| 555bee6f36 | |||
| df02234ece | |||
| 2fb22fd3d4 | |||
| 740fd2e92a | |||
| 8047e66a52 | |||
| 58336842e4 | |||
| dc75101660 | |||
| b20a66521f | |||
| e59955e164 | |||
| e6a694ad6d | |||
| e77f897159 | |||
| 5139538028 | |||
| 42d2f302d4 | |||
| c4a5c88f55 | |||
| 95ff39f588 | |||
| 0808b30494 | |||
| 9df1b5263a | |||
| bf1f4744ce | |||
| f279c540c5 | |||
| 7b80cd367a | |||
| 87a7373383 | |||
| 6e094b71e4 | |||
| 51c1534de3 | |||
| 116d8c662d | |||
| f4148f716e | |||
| e888ce39d7 | |||
| 5efd74e033 | |||
| 5e7c741ed3 |
+8
-9
@@ -4,6 +4,7 @@ branches:
|
||||
- development
|
||||
git:
|
||||
quiet: true
|
||||
depth: false
|
||||
jobs:
|
||||
include:
|
||||
- name: "Android Main Build"
|
||||
@@ -24,7 +25,6 @@ jobs:
|
||||
- gperf
|
||||
- texinfo
|
||||
- yasm
|
||||
- nasm
|
||||
- bison
|
||||
- autogen
|
||||
- patch
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
- extra-google-m2repository
|
||||
- extra-android-m2repository
|
||||
install:
|
||||
- echo y | sdkmanager "ndk;21.0.6113669"
|
||||
- echo y | sdkmanager "ndk;21.3.6528147"
|
||||
- echo y | sdkmanager "cmake;3.10.2.4988404"
|
||||
- echo y | sdkmanager "lldb;3.1"
|
||||
before_install:
|
||||
- touch $HOME/.android/repositories.cfg
|
||||
before_script:
|
||||
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/21.0.6113669
|
||||
- export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
|
||||
- rm -f ./build.log
|
||||
- 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:
|
||||
@@ -71,7 +71,6 @@ jobs:
|
||||
- gperf
|
||||
- texinfo
|
||||
- yasm
|
||||
- nasm
|
||||
- bison
|
||||
- autogen
|
||||
- patch
|
||||
@@ -86,14 +85,14 @@ jobs:
|
||||
- extra-google-m2repository
|
||||
- extra-android-m2repository
|
||||
install:
|
||||
- echo y | sdkmanager "ndk;21.0.6113669"
|
||||
- echo y | sdkmanager "ndk;21.3.6528147"
|
||||
- echo y | sdkmanager "cmake;3.10.2.4988404"
|
||||
- echo y | sdkmanager "lldb;3.1"
|
||||
before_install:
|
||||
- touch $HOME/.android/repositories.cfg
|
||||
before_script:
|
||||
- export ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/21.0.6113669
|
||||
- export ANDROID_NDK_ROOT=${ANDROID_HOME}ndk/21.3.6528147
|
||||
- rm -f ./build.log
|
||||
- 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:
|
||||
@@ -172,4 +171,4 @@ jobs:
|
||||
- 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# MobileFFmpeg [](https://opencollective.com/mobile-ffmpeg)    [](https://travis-ci.org/tanersener/mobile-ffmpeg)
|
||||
# MobileFFmpeg [](https://opencollective.com/mobile-ffmpeg)    [](https://travis-ci.org/tanersener/mobile-ffmpeg)
|
||||
|
||||
FFmpeg for Android, iOS and tvOS
|
||||
|
||||
@@ -9,10 +9,10 @@ FFmpeg for Android, iOS and tvOS
|
||||
- Use binaries available at `Github`/`JCenter`/`CocoaPods` or build your own version with external libraries you need
|
||||
- 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`
|
||||
|
||||
- 5 external libraries with GPL license
|
||||
|
||||
@@ -21,9 +21,9 @@ FFmpeg for Android, iOS and tvOS
|
||||
- Concurrent execution
|
||||
|
||||
- Exposes both FFmpeg library and MobileFFmpeg wrapper library capabilities
|
||||
- Includes cross-compile instructions for 46 open-source libraries
|
||||
- Includes cross-compile instructions for 47 open-source libraries
|
||||
|
||||
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `leptonica`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libsamplerate`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `openh264`, `opus`, `rubberband`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `tiff`, `twolame`, `vid.stab`, `wavpack`, `x264`, `x265`, `xvidcore`
|
||||
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `leptonica`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libsamplerate`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `openh264`, `opus`, `rubberband`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `tiff`, `twolame`, `vid.stab`, `vo-amrwbenc`, `wavpack`, `x264`, `x265`, `xvidcore`
|
||||
|
||||
- Licensed under LGPL 3.0, can be customized to support GPL v3.0
|
||||
|
||||
@@ -37,7 +37,7 @@ FFmpeg for Android, iOS and tvOS
|
||||
|
||||
#### 1.2 iOS
|
||||
- Builds `armv7`, `armv7s`, `arm64`, `arm64e`, `i386`, `x86_64` and `x86_64` (Mac Catalyst) architectures
|
||||
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` system frameworks
|
||||
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox`, `AVFoundation` system frameworks
|
||||
- Objective-C API
|
||||
- Camera access
|
||||
- `ARC` enabled library
|
||||
@@ -47,7 +47,7 @@ FFmpeg for Android, iOS and tvOS
|
||||
|
||||
#### 1.3 tvOS
|
||||
- Builds `arm64` and `x86_64` architectures
|
||||
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox` system frameworks
|
||||
- Supports `bzip2`, `iconv`, `libuuid`, `zlib` system libraries and `AudioToolbox`, `VideoToolbox` system frameworks
|
||||
- Objective-C API
|
||||
- `ARC` enabled library
|
||||
- Built with `-fembed-bitcode` flag
|
||||
@@ -55,7 +55,10 @@ FFmpeg for Android, iOS and tvOS
|
||||
- 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.2'
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:4.4'
|
||||
}
|
||||
```
|
||||
|
||||
2. Execute FFmpeg commands.
|
||||
2. Execute synchronous FFmpeg commands.
|
||||
```
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
@@ -141,7 +146,27 @@ Please remember that some parts of `FFmpeg` are licensed under the `GPL` and onl
|
||||
}
|
||||
```
|
||||
|
||||
3. Execute FFprobe commands.
|
||||
3. Execute asynchronous FFmpeg commands.
|
||||
```
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
|
||||
long executionId = FFmpeg.executeAsync("-i file1.mp4 -c:v mpeg4 file2.mp4", new ExecuteCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final long executionId, final int returnCode) {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
Log.i(Config.TAG, "Async command execution completed successfully.");
|
||||
} else if (returnCode == RETURN_CODE_CANCEL) {
|
||||
Log.i(Config.TAG, "Async command execution cancelled by user.");
|
||||
} else {
|
||||
Log.i(Config.TAG, String.format("Async command execution failed with returnCode=%d.", returnCode));
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
4. Execute FFprobe commands.
|
||||
```
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.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.2'
|
||||
pod 'mobile-ffmpeg-full', '~> 4.4'
|
||||
```
|
||||
|
||||
- tvOS
|
||||
```
|
||||
pod 'mobile-ffmpeg-tvos-full', '~> 4.3.2'
|
||||
pod 'mobile-ffmpeg-tvos-full', '~> 4.4'
|
||||
```
|
||||
|
||||
2. Execute FFmpeg commands.
|
||||
2. Execute synchronous FFmpeg commands.
|
||||
```
|
||||
#import <mobileffmpeg/MobileFFmpegConfig.h>
|
||||
#import <mobileffmpeg/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,8 @@ 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 |
|
||||
@@ -398,6 +471,7 @@ This table shows the differences between two variants.
|
||||
| Android Architectures | arm-v7a-neon<br/>arm64-v8a<br/>x86<br/>x86-64 | arm-v7a<br/>arm-v7a-neon<br/>arm64-v8a<br/>x86<br/>x86-64 |
|
||||
| Xcode Support | 10.1 | 7.3.1 |
|
||||
| iOS SDK | 12.1 | 9.3 |
|
||||
| iOS AVFoundation | Yes | - |
|
||||
| iOS Architectures | arm64<br/>arm64e<sup>1</sup><br/>x86-64<br/>x86-64-mac-catalyst<sup>2</sup> | armv7<br/>arm64<br/>i386<br/>x86-64 |
|
||||
| tvOS SDK | 10.2 | 9.2 |
|
||||
| tvOS Architectures | arm64<br/>x86-64 | arm64<br/>x86-64 |
|
||||
@@ -538,34 +612,47 @@ Support this project with your organization. Your logo will show up here with a
|
||||
|
||||
### 8. License
|
||||
|
||||
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
|
||||
|
||||
It is not clearly explained in their documentation but it is believed that `FFmpeg`, `kvazaar`, `x264` and `x265`
|
||||
include algorithms which are subject to software patents. If you live in a country where software algorithms are
|
||||
patentable then you'll probably need to pay royalty fees to patent holders. We are not lawyers though, so we recommend
|
||||
that you seek legal advice first. See [FFmpeg Patent Mini-FAQ](https://ffmpeg.org/legal.html).
|
||||
|
||||
`openh264` clearly states that it uses patented algorithms. Therefore, if you build mobile-ffmpeg with openh264 and
|
||||
distribute that library, then you are subject to pay MPEG LA licensing fees. Refer to
|
||||
[OpenH264 FAQ](https://www.openh264.org/faq.html) page for the details.
|
||||
|
||||
### 10. Contributing
|
||||
|
||||
Feel free to submit issues or pull requests.
|
||||
|
||||
Please note that `master` branch includes only the latest released source code. Changes planned for the next release
|
||||
are implemented under the `development` branch. So, if you want to create a pull request, please open it against the
|
||||
`development`.
|
||||
are implemented under the `development` branch. Therefore, if you want to create a pull request, please open it against
|
||||
the `development`.
|
||||
|
||||
### 10. See Also
|
||||
### 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)
|
||||
|
||||
+121
-74
@@ -41,34 +41,36 @@ LIBRARY_TWOLAME=29
|
||||
LIBRARY_SDL=30
|
||||
LIBRARY_TESSERACT=31
|
||||
LIBRARY_OPENH264=32
|
||||
LIBRARY_GIFLIB=33
|
||||
LIBRARY_JPEG=34
|
||||
LIBRARY_LIBOGG=35
|
||||
LIBRARY_LIBPNG=36
|
||||
LIBRARY_LIBUUID=37
|
||||
LIBRARY_NETTLE=38
|
||||
LIBRARY_TIFF=39
|
||||
LIBRARY_EXPAT=40
|
||||
LIBRARY_SNDFILE=41
|
||||
LIBRARY_LEPTONICA=42
|
||||
LIBRARY_LIBSAMPLERATE=43
|
||||
LIBRARY_ZLIB=44
|
||||
LIBRARY_MEDIA_CODEC=45
|
||||
LIBRARY_VO_AMRWBENC=33
|
||||
LIBRARY_GIFLIB=34
|
||||
LIBRARY_JPEG=35
|
||||
LIBRARY_LIBOGG=36
|
||||
LIBRARY_LIBPNG=37
|
||||
LIBRARY_LIBUUID=38
|
||||
LIBRARY_NETTLE=39
|
||||
LIBRARY_TIFF=40
|
||||
LIBRARY_EXPAT=41
|
||||
LIBRARY_SNDFILE=42
|
||||
LIBRARY_LEPTONICA=43
|
||||
LIBRARY_LIBSAMPLERATE=44
|
||||
LIBRARY_ZLIB=45
|
||||
LIBRARY_MEDIA_CODEC=46
|
||||
LIBRARY_CPU_FEATURES=47
|
||||
|
||||
# ENABLE ARCH
|
||||
ENABLED_ARCHITECTURES=(1 1 1 1 1)
|
||||
|
||||
# ENABLE LIBRARIES
|
||||
ENABLED_LIBRARIES=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
|
||||
ENABLED_LIBRARIES=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1)
|
||||
|
||||
export BASEDIR=$(pwd)
|
||||
export MOBILE_FFMPEG_TMPDIR="${BASEDIR}/.tmp"
|
||||
|
||||
# USING API LEVEL 24 / Android 7.0 (NOUGAT)
|
||||
export API=24
|
||||
|
||||
RECONF_LIBRARIES=()
|
||||
REBUILD_LIBRARIES=()
|
||||
REDOWNLOAD_LIBRARIES=()
|
||||
|
||||
get_mobile_ffmpeg_version() {
|
||||
local MOBILE_FFMPEG_VERSION=$(grep '#define MOBILE_FFMPEG_VERSION' ${BASEDIR}/android/app/src/main/cpp/mobileffmpeg.h | grep -Eo '\".*\"' | sed -e 's/\"//g')
|
||||
@@ -99,7 +101,7 @@ When compilation ends an Android Archive (AAR) file is created under the prebuil
|
||||
|
||||
echo -e "Licensing options:"
|
||||
|
||||
echo -e " --enable-gpl\t\t\tallow use of GPL libraries, resulting libs will be licensed under GPLv3.0 [no]\n"
|
||||
echo -e " --enable-gpl\t\t\tallow use of GPL libraries, created libs will be licensed under GPLv3.0 [no]\n"
|
||||
|
||||
echo -e "Platforms:"
|
||||
|
||||
@@ -141,6 +143,7 @@ When compilation ends an Android Archive (AAR) file is created under the prebuil
|
||||
echo -e " --enable-speex\t\tbuild with speex [no]"
|
||||
echo -e " --enable-tesseract\t\tbuild with tesseract [no]"
|
||||
echo -e " --enable-twolame\t\tbuild with twolame [no]"
|
||||
echo -e " --enable-vo-amrwbenc\t\tbuild with vo-amrwbenc [no]"
|
||||
echo -e " --enable-wavpack\t\tbuild with wavpack [no]\n"
|
||||
|
||||
echo -e "GPL libraries:"
|
||||
@@ -154,7 +157,9 @@ When compilation ends an Android Archive (AAR) file is created under the prebuil
|
||||
echo -e "Advanced options:"
|
||||
|
||||
echo -e " --reconf-LIBRARY\t\trun autoreconf before building LIBRARY [no]"
|
||||
echo -e " --redownload-LIBRARY\t\tdownload LIBRARY even it is detected as already downloaded [no]"
|
||||
echo -e " --rebuild-LIBRARY\t\tbuild LIBRARY even it is detected as already built [no]\n"
|
||||
echo -e " --no-archive\t\tdo not build Android archive [no]\n"
|
||||
}
|
||||
|
||||
display_version() {
|
||||
@@ -211,7 +216,7 @@ reconf_library() {
|
||||
local RECONF_VARIABLE=$(echo "RECONF_$1" | sed "s/\-/\_/g")
|
||||
local library_supported=0
|
||||
|
||||
for library in {1..44}; do
|
||||
for library in {1..45}; do
|
||||
library_name=$(get_library_name $((library - 1)))
|
||||
|
||||
if [[ $1 != "ffmpeg" ]] && [[ ${library_name} == $1 ]]; then
|
||||
@@ -230,8 +235,8 @@ rebuild_library() {
|
||||
local REBUILD_VARIABLE=$(echo "REBUILD_$1" | sed "s/\-/\_/g")
|
||||
local library_supported=0
|
||||
|
||||
for library in {1..44}; do
|
||||
library_name=$(get_library_name $((library - 1)))
|
||||
for library in {0..47}; do
|
||||
library_name=$(get_library_name ${library})
|
||||
|
||||
if [[ $1 != "ffmpeg" ]] && [[ ${library_name} == $1 ]]; then
|
||||
export ${REBUILD_VARIABLE}=1
|
||||
@@ -245,6 +250,31 @@ rebuild_library() {
|
||||
fi
|
||||
}
|
||||
|
||||
redownload_library() {
|
||||
local REDOWNLOAD_VARIABLE=$(echo "REDOWNLOAD_$1" | sed "s/\-/\_/g")
|
||||
local library_supported=0
|
||||
|
||||
for library in {0..47}; do
|
||||
library_name=$(get_library_name ${library})
|
||||
|
||||
if [[ ${library_name} == $1 ]]; then
|
||||
export ${REDOWNLOAD_VARIABLE}=1
|
||||
REDOWNLOAD_LIBRARIES+=($1)
|
||||
library_supported=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "ffmpeg" == $1 ]]; then
|
||||
export ${REDOWNLOAD_VARIABLE}=1
|
||||
REDOWNLOAD_LIBRARIES+=($1)
|
||||
library_supported=1
|
||||
fi
|
||||
|
||||
if [[ ${library_supported} -eq 0 ]]; then
|
||||
echo -e "INFO: --redownload flag detected for library $1 is not supported.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
enable_library() {
|
||||
set_library $1 1
|
||||
}
|
||||
@@ -384,6 +414,9 @@ set_library() {
|
||||
ENABLED_LIBRARIES[LIBRARY_TWOLAME]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_SNDFILE]=$2
|
||||
;;
|
||||
vo-amrwbenc)
|
||||
ENABLED_LIBRARIES[LIBRARY_VO_AMRWBENC]=$2
|
||||
;;
|
||||
wavpack)
|
||||
ENABLED_LIBRARIES[LIBRARY_WAVPACK]=$2
|
||||
;;
|
||||
@@ -481,19 +514,7 @@ print_enabled_libraries() {
|
||||
|
||||
let enabled=0
|
||||
|
||||
# FIRST BUILT-IN LIBRARIES
|
||||
for library in {44..45}; do
|
||||
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
|
||||
if [[ ${enabled} -ge 1 ]]; then
|
||||
echo -n ", "
|
||||
fi
|
||||
echo -n $(get_library_name $library)
|
||||
enabled=$((${enabled} + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# THEN EXTERNAL LIBRARIES
|
||||
for library in {0..32}; do
|
||||
for library in {45..47} {0..33}; do
|
||||
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
|
||||
if [[ ${enabled} -ge 1 ]]; then
|
||||
echo -n ", "
|
||||
@@ -550,6 +571,26 @@ print_rebuild_requested_libraries() {
|
||||
fi
|
||||
}
|
||||
|
||||
print_redownload_requested_libraries() {
|
||||
local counter=0
|
||||
|
||||
for REDOWNLOAD_LIBRARY in "${REDOWNLOAD_LIBRARIES[@]}"; do
|
||||
if [[ ${counter} -eq 0 ]]; then
|
||||
echo -n "Redownload: "
|
||||
else
|
||||
echo -n ", "
|
||||
fi
|
||||
|
||||
echo -n ${REDOWNLOAD_LIBRARY}
|
||||
|
||||
counter=$((${counter} + 1))
|
||||
done
|
||||
|
||||
if [[ ${counter} -gt 0 ]]; then
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
build_application_mk() {
|
||||
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]]; then
|
||||
local LTS_BUILD_FLAG="-DMOBILE_FFMPEG_LTS "
|
||||
@@ -559,8 +600,6 @@ build_application_mk() {
|
||||
local APP_STL="c++_shared"
|
||||
else
|
||||
local APP_STL="none"
|
||||
|
||||
${SED_INLINE} 's/c++_shared //g' ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
|
||||
local BUILD_DATE="-DMOBILE_FFMPEG_BUILD_DATE=$(date +%Y%m%d 2>>${BASEDIR}/build.log)"
|
||||
@@ -582,13 +621,23 @@ APP_LDFLAGS := -Wl,--hash-style=both
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
|
||||
echo "ANDROID_NDK_ROOT not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${ANDROID_HOME} ]]; then
|
||||
echo "ANDROID_HOME not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ENABLE COMMON FUNCTIONS
|
||||
. ${BASEDIR}/build/android-common.sh
|
||||
|
||||
DETECTED_NDK_VERSION=$(grep -Eo Revision.* ${ANDROID_NDK_ROOT}/source.properties | sed 's/Revision//g;s/=//g;s/ //g')
|
||||
|
||||
echo -e "\nINFO: Using Android NDK v${DETECTED_NDK_VERSION} provided at ${ANDROID_NDK_ROOT}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo -e "INFO: Build options: $@\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo -e "INFO: Build options: $*\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# CLEAR OLD NATIVE LIBS
|
||||
rm -rf ${BASEDIR}/android/libs 1>>${BASEDIR}/build.log 2>&1
|
||||
@@ -597,6 +646,7 @@ rm -rf ${BASEDIR}/android/obj 1>>${BASEDIR}/build.log 2>&1
|
||||
GPL_ENABLED="no"
|
||||
DISPLAY_HELP=""
|
||||
BUILD_LTS=""
|
||||
BUILD_FULL=""
|
||||
BUILD_TYPE_ID=""
|
||||
BUILD_VERSION=$(git describe --tags 2>>${BASEDIR}/build.log)
|
||||
|
||||
@@ -648,12 +698,16 @@ while [ ! $# -eq 0 ]; do
|
||||
|
||||
rebuild_library ${BUILD_LIBRARY}
|
||||
;;
|
||||
--redownload-*)
|
||||
DOWNLOAD_LIBRARY=$(echo $1 | sed -e 's/^--[A-Za-z]*-//g')
|
||||
|
||||
redownload_library ${DOWNLOAD_LIBRARY}
|
||||
;;
|
||||
--no-archive)
|
||||
NO_ARCHIVE="1"
|
||||
;;
|
||||
--full)
|
||||
for library in {0..45}; do
|
||||
if [[ $library -ne 18 ]] && [[ $library -ne 19 ]] && [[ $library -ne 20 ]] && [[ $library -ne 21 ]] && [[ $library -ne 22 ]]; then
|
||||
enable_library $(get_library_name $library)
|
||||
fi
|
||||
done
|
||||
BUILD_FULL="1"
|
||||
;;
|
||||
--enable-gpl)
|
||||
GPL_ENABLED="yes"
|
||||
@@ -676,22 +730,14 @@ while [ ! $# -eq 0 ]; do
|
||||
done
|
||||
|
||||
# DETECT BUILD TYPE
|
||||
rm -f ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -f ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1
|
||||
if [[ ! -z ${BUILD_LTS} ]]; then
|
||||
enable_lts_build
|
||||
BUILD_TYPE_ID+="LTS "
|
||||
|
||||
cp ${BASEDIR}/tools/ndk/Android.lts.mk ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
|
||||
cp ${BASEDIR}/tools/release/android/build.lts.gradle ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1
|
||||
else
|
||||
cp ${BASEDIR}/tools/ndk/Android.mk ${BASEDIR}/android/jni/Android.mk 1>>${BASEDIR}/build.log 2>&1
|
||||
cp ${BASEDIR}/tools/release/android/build.gradle ${BASEDIR}/android/app/build.gradle 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [[ -z ${BUILD_FORCE} ]] && [[ ${ENABLED_ARCHITECTURES[${ARCH_ARM_V7A}]} -eq 1 ]]; then
|
||||
echo -e "INFO: Disabled arm-v7a architecture which is not included in Main releases.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
disable_arch "arm-v7a"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -z ${DISPLAY_HELP} ]]; then
|
||||
@@ -699,14 +745,16 @@ if [[ ! -z ${DISPLAY_HELP} ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
|
||||
echo "ANDROID_NDK_ROOT not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${ANDROID_HOME} ]]; then
|
||||
echo "ANDROID_HOME not defined"
|
||||
exit 1
|
||||
if [[ -n ${BUILD_FULL} ]]; then
|
||||
for library in {0..46}; do
|
||||
if [ ${GPL_ENABLED} == "yes" ]; then
|
||||
enable_library $(get_library_name $library)
|
||||
else
|
||||
if [[ $library -ne 18 ]] && [[ $library -ne 19 ]] && [[ $library -ne 20 ]] && [[ $library -ne 21 ]] && [[ $library -ne 22 ]]; then
|
||||
enable_library $(get_library_name $library)
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z ${BUILD_VERSION} ]]; then
|
||||
@@ -718,20 +766,13 @@ echo -e "\nBuilding mobile-ffmpeg ${BUILD_TYPE_ID}library for Android\n"
|
||||
echo -e -n "INFO: Building mobile-ffmpeg ${BUILD_VERSION} ${BUILD_TYPE_ID}library for Android: " 1>>${BASEDIR}/build.log 2>&1
|
||||
echo -e $(date) 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# PERFORM THIS CHECK ONLY ON LTS
|
||||
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${ENABLED_ARCHITECTURES[0]} -eq 0 ]] && [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
|
||||
ENABLED_ARCHITECTURES[ARCH_ARM_V7A]=1
|
||||
|
||||
echo -e "(*) arm-v7a architecture enabled since arm-v7a-neon will be built\n"
|
||||
echo -e "(*) arm-v7a architecture enabled since arm-v7a-neon will be built\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
|
||||
print_enabled_architectures
|
||||
print_enabled_libraries
|
||||
print_reconfigure_requested_libraries
|
||||
print_rebuild_requested_libraries
|
||||
print_redownload_requested_libraries
|
||||
|
||||
# CHECKING GPL LIBRARIES
|
||||
# CHECK GPL LIBRARIES
|
||||
for gpl_library in {18,19,20,21,22}; do
|
||||
if [[ ${ENABLED_LIBRARIES[$gpl_library]} -eq 1 ]]; then
|
||||
library_name=$(get_library_name ${gpl_library})
|
||||
@@ -756,7 +797,7 @@ export ORIGINAL_API=${API}
|
||||
|
||||
for run_arch in {0..4}; do
|
||||
if [[ ${ENABLED_ARCHITECTURES[$run_arch]} -eq 1 ]]; then
|
||||
if [[ (${run_arch} -eq ${ARCH_ARM64_V8A} || ${run_arch} -eq ${ARCH_X86_64}) && ${API} < 21 ]]; then
|
||||
if [[ (${run_arch} -eq ${ARCH_ARM64_V8A} || ${run_arch} -eq ${ARCH_X86_64}) && ${API} -lt 21 ]]; then
|
||||
|
||||
# 64 bit ABIs supported after API 21
|
||||
export API=21
|
||||
@@ -771,8 +812,8 @@ for run_arch in {0..4}; do
|
||||
. ${BASEDIR}/build/main-android.sh "${ENABLED_LIBRARIES[@]}" || exit 1
|
||||
|
||||
# CLEAR FLAGS
|
||||
for library in {1..46}; do
|
||||
library_name=$(get_library_name $((library - 1)))
|
||||
for library in {0..47}; do
|
||||
library_name=$(get_library_name ${library})
|
||||
unset $(echo "OK_${library_name}" | sed "s/\-/\_/g")
|
||||
unset $(echo "DEPENDENCY_REBUILT_${library_name}" | sed "s/\-/\_/g")
|
||||
done
|
||||
@@ -781,15 +822,21 @@ done
|
||||
|
||||
export API=${ORIGINAL_API}
|
||||
|
||||
rm -f ${BASEDIR}/android/build/.neon 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -f ${BASEDIR}/android/build/.armv7 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -f ${BASEDIR}/android/build/.armv7neon 1>>${BASEDIR}/build.log 2>&1
|
||||
ANDROID_ARCHITECTURES=""
|
||||
if [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
|
||||
if [[ ${ENABLED_ARCHITECTURES[0]} -eq 1 ]] || [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
|
||||
ANDROID_ARCHITECTURES+="$(get_android_arch 0) "
|
||||
fi
|
||||
if [[ ${ENABLED_ARCHITECTURES[0]} -eq 1 ]]; then
|
||||
mkdir -p ${BASEDIR}/android/build 1>>${BASEDIR}/build.log 2>&1
|
||||
cat >"${BASEDIR}/android/build/.neon" <<EOF
|
||||
cat >"${BASEDIR}/android/build/.armv7" <<EOF
|
||||
EOF
|
||||
fi
|
||||
if [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
|
||||
mkdir -p ${BASEDIR}/android/build 1>>${BASEDIR}/build.log 2>&1
|
||||
cat >"${BASEDIR}/android/build/.armv7neon" <<EOF
|
||||
EOF
|
||||
elif [[ ${ENABLED_ARCHITECTURES[0]} -eq 1 ]]; then
|
||||
ANDROID_ARCHITECTURES+="$(get_android_arch 0) "
|
||||
fi
|
||||
if [[ ${ENABLED_ARCHITECTURES[2]} -eq 1 ]]; then
|
||||
ANDROID_ARCHITECTURES+="$(get_android_arch 2) "
|
||||
@@ -801,7 +848,7 @@ if [[ ${ENABLED_ARCHITECTURES[4]} -eq 1 ]]; then
|
||||
ANDROID_ARCHITECTURES+="$(get_android_arch 4) "
|
||||
fi
|
||||
|
||||
if [[ ! -z ${ANDROID_ARCHITECTURES} ]]; then
|
||||
if [[ ! -z ${ANDROID_ARCHITECTURES} ]] && [ -z ${NO_ARCHIVE} ]; then
|
||||
|
||||
echo -n -e "\nmobile-ffmpeg: "
|
||||
|
||||
|
||||
+9
-6
@@ -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
@@ -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.2
|
||||
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.
|
||||
|
||||
+10
-12
@@ -1,14 +1,14 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
ndkVersion "21.0.6113669"
|
||||
compileSdkVersion 30
|
||||
ndkVersion "21.3.6528147"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 29
|
||||
versionCode 240432
|
||||
versionName "4.3.2"
|
||||
targetSdkVersion 30
|
||||
versionCode 240440
|
||||
versionName "4.4"
|
||||
project.archivesBaseName = "mobile-ffmpeg"
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
@@ -31,13 +31,11 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
task javadoc(type: Javadoc) {
|
||||
title = 'MobileFFmpeg'
|
||||
destinationDir = file("${projectDir}/../../docs/android/javadoc")
|
||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||
source = android.sourceSets.main.java.srcDirs
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation "androidx.test.ext:junit:1.1.1"
|
||||
testImplementation "org.json:json:20190722"
|
||||
}
|
||||
|
||||
if (System.properties.containsKey('releaseMobileFFmpeg')) {
|
||||
apply from: "${rootProject.projectDir}/../tools/release/android/release.template.gradle"
|
||||
}
|
||||
|
||||
Vendored
+3
-2
@@ -7,8 +7,9 @@
|
||||
|
||||
-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);
|
||||
void closeParcelFileDescriptor(int);
|
||||
}
|
||||
|
||||
-keep class com.arthenica.mobileffmpeg.AbiDetect {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libswscale/swscale.h"
|
||||
|
||||
#include "saf_wrapper.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef main /* We don't want SDL to override our main() */
|
||||
#endif
|
||||
@@ -334,6 +336,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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* CHANGES 06.2020
|
||||
* - cancel_operation() method signature updated with id
|
||||
*
|
||||
* CHANGES 01.2020
|
||||
* - ffprobe support changes
|
||||
*
|
||||
@@ -685,7 +688,7 @@ int hwaccel_decode_init(AVCodecContext *avctx);
|
||||
|
||||
void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double));
|
||||
|
||||
void cancel_operation();
|
||||
void cancel_operation(long id);
|
||||
|
||||
int opt_map(void *optctx, const char *opt, const char *arg);
|
||||
int opt_map_channel(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -101,6 +79,9 @@ static jmethodID logMethod;
|
||||
/** Global reference of statistics redirection method in Java */
|
||||
static jmethodID statisticsMethod;
|
||||
|
||||
/** Global reference of closeParcelFileDescriptor method in Java */
|
||||
static jmethodID closeParcelFileDescriptorMethod;
|
||||
|
||||
/** Global reference of String class in Java */
|
||||
static jclass stringClass;
|
||||
|
||||
@@ -113,6 +94,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 +115,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 +226,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 +251,10 @@ void logUnInit() {
|
||||
pthread_mutex_destroy(&logMutex);
|
||||
}
|
||||
|
||||
void executionMapLockUnInit() {
|
||||
pthread_mutex_destroy(&executionMapMutex);
|
||||
}
|
||||
|
||||
void mutexLock() {
|
||||
pthread_mutex_lock(&lockMutex);
|
||||
}
|
||||
@@ -257,6 +263,10 @@ void lastCommandOutputLock() {
|
||||
pthread_mutex_lock(&logMutex);
|
||||
}
|
||||
|
||||
void executionMapLock() {
|
||||
pthread_mutex_lock(&executionMapMutex);
|
||||
}
|
||||
|
||||
void mutexUnlock() {
|
||||
pthread_mutex_unlock(&lockMutex);
|
||||
}
|
||||
@@ -265,50 +275,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) {
|
||||
@@ -325,6 +309,8 @@ void monitorWait(int milliSeconds) {
|
||||
ts.tv_nsec = tp.tv_usec * 1000;
|
||||
ts.tv_sec += milliSeconds / 1000;
|
||||
ts.tv_nsec += (milliSeconds % 1000)*1000000;
|
||||
ts.tv_sec += ts.tv_nsec / 1000000000L;
|
||||
ts.tv_nsec = ts.tv_nsec % 1000000000L;
|
||||
|
||||
pthread_mutex_lock(&monitorMutex);
|
||||
pthread_cond_timedwait(&monitorCondition, &monitorMutex, &ts);
|
||||
@@ -343,15 +329,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 +371,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 +405,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 +451,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 +495,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 +509,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 +575,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 +646,27 @@ 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;
|
||||
}
|
||||
|
||||
closeParcelFileDescriptorMethod = (*env)->GetStaticMethodID(env, localConfigClass, "closeParcelFileDescriptor", "(I)V");
|
||||
if (logMethod == NULL) {
|
||||
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "closeParcelFileDescriptor");
|
||||
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 +679,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;
|
||||
}
|
||||
@@ -649,8 +699,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
* @param object reference to the class on which this method is invoked
|
||||
* @param level log level
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) {
|
||||
av_log_set_level(level);
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) {
|
||||
configuredLogLevel = level;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,7 +710,7 @@ JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(
|
||||
* @param object reference to the class on which this method is invoked
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object) {
|
||||
return av_log_get_level();
|
||||
return configuredLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -741,14 +791,18 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersio
|
||||
*
|
||||
* @param env pointer to native method interface
|
||||
* @param 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 +831,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 +860,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 +906,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 +921,43 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* used by saf_wrapper; is expected to be called from a Java thread, therefore we don't need attach/detach
|
||||
*/
|
||||
void closeParcelFileDescriptor(int fd) {
|
||||
JNIEnv *env = NULL;
|
||||
(*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
|
||||
(*env)->CallStaticVoidMethod(env, configClass, closeParcelFileDescriptorMethod, fd);
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "libavutil/ffversion.h"
|
||||
|
||||
/** Library version string */
|
||||
#define MOBILE_FFMPEG_VERSION "4.3.2"
|
||||
#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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avstring.h"
|
||||
|
||||
#include "saf_wrapper.h"
|
||||
|
||||
/** JNI wrapper in mobileffmpeg.c */
|
||||
void closeParcelFileDescriptor(int fd);
|
||||
|
||||
// in these wrappers, we call the original functions, so we remove the shadow defines
|
||||
#undef avio_closep
|
||||
#undef avformat_close_input
|
||||
#undef avio_open
|
||||
#undef avio_open2
|
||||
#undef avformat_open_input
|
||||
|
||||
static int fd_read_packet(void* opaque, uint8_t* buf, int buf_size) {
|
||||
int fd = (int)opaque;
|
||||
return read(fd, buf, buf_size);
|
||||
}
|
||||
|
||||
static int fd_write_packet(void* opaque, uint8_t* buf, int buf_size) {
|
||||
int fd = (int)opaque;
|
||||
return write(fd, buf, buf_size);
|
||||
}
|
||||
|
||||
static int64_t fd_seek(void *opaque, int64_t offset, int whence) {
|
||||
int fd = (int)opaque;
|
||||
|
||||
if (fd < 0) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
int64_t ret;
|
||||
if (whence == AVSEEK_SIZE) {
|
||||
struct stat st;
|
||||
ret = fstat(fd, &st);
|
||||
return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size);
|
||||
}
|
||||
|
||||
ret = lseek(fd, offset, whence);
|
||||
|
||||
return ret < 0 ? AVERROR(errno) : ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns NULL if the filename is not of expected format (e.g. 'saf:72/video.md4')
|
||||
*/
|
||||
static AVIOContext *create_fd_avio_context(const char *filename, int flags) {
|
||||
union {int fd; void* opaque;} fdunion;
|
||||
fdunion.fd = -1;
|
||||
const char *fd_ptr = NULL;
|
||||
if (av_strstart(filename, "saf:", &fd_ptr)) {
|
||||
char *final;
|
||||
fdunion.fd = strtol(fd_ptr, &final, 10);
|
||||
if (fd_ptr == final) { /* No digits found */
|
||||
fdunion.fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (fdunion.fd >= 0) {
|
||||
int write_flag = flags & AVIO_FLAG_WRITE ? 1 : 0;
|
||||
return avio_alloc_context(av_malloc(4096), 4096, write_flag, fdunion.opaque, fd_read_packet, write_flag ? fd_write_packet : NULL, fd_seek);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void close_fd_avio_context(AVIOContext *ctx) {
|
||||
if (fd_seek(ctx->opaque, 0, AVSEEK_SIZE) >= 0) {
|
||||
int fd = (int)ctx->opaque;
|
||||
closeParcelFileDescriptor(fd);
|
||||
}
|
||||
ctx->opaque = NULL;
|
||||
}
|
||||
|
||||
int android_avformat_open_input(AVFormatContext **ps, const char *filename,
|
||||
ff_const59 AVInputFormat *fmt, AVDictionary **options) {
|
||||
if (!(*ps) && !(*ps = avformat_alloc_context()))
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
(*ps)->pb = create_fd_avio_context(filename, AVIO_FLAG_READ);
|
||||
|
||||
return avformat_open_input(ps, filename, fmt, options);
|
||||
}
|
||||
|
||||
int android_avio_open2(AVIOContext **s, const char *filename, int flags,
|
||||
const AVIOInterruptCB *int_cb, AVDictionary **options) {
|
||||
AVIOContext *fd_context = create_fd_avio_context(filename, flags);
|
||||
|
||||
if (fd_context) {
|
||||
*s = fd_context;
|
||||
return 0;
|
||||
}
|
||||
return avio_open2(s, filename, flags, int_cb, options);
|
||||
}
|
||||
|
||||
int android_avio_open(AVIOContext **s, const char *url, int flags) {
|
||||
return android_avio_open2(s, url, flags, NULL, NULL);
|
||||
}
|
||||
|
||||
int android_avio_closep(AVIOContext **s) {
|
||||
close_fd_avio_context(*s);
|
||||
return avio_closep(s);
|
||||
}
|
||||
|
||||
void android_avformat_close_input(AVFormatContext **ps) {
|
||||
if (*ps && (*ps)->pb) {
|
||||
close_fd_avio_context((*ps)->pb);
|
||||
}
|
||||
avformat_close_input(ps);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MOBILE_FFMPEG_SAF_WRAPPER_H
|
||||
#define MOBILE_FFMPEG_SAF_WRAPPER_H
|
||||
|
||||
/*
|
||||
* These wrappers are intended to be used instead of the ffmpeg apis.
|
||||
* You don't even need to change the source to call them.
|
||||
* Instead, we redefine the public api names so that the wrapper be used.
|
||||
*/
|
||||
|
||||
int android_avio_closep(AVIOContext **s);
|
||||
#define avio_closep android_avio_closep
|
||||
|
||||
void android_avformat_close_input(AVFormatContext **s);
|
||||
#define avformat_close_input android_avformat_close_input
|
||||
|
||||
int android_avio_open(AVIOContext **s, const char *url, int flags);
|
||||
#define avio_open android_avio_open
|
||||
|
||||
int android_avio_open2(AVIOContext **s, const char *url, int flags,
|
||||
const AVIOInterruptCB *int_cb, AVDictionary **options);
|
||||
#define avio_open2 android_avio_open2
|
||||
|
||||
int android_avformat_open_input(AVFormatContext **ps, const char *filename,
|
||||
ff_const59 AVInputFormat *fmt, AVDictionary **options);
|
||||
#define avformat_open_input android_avformat_open_input
|
||||
|
||||
#endif //MOBILE_FFMPEG_SAF_WRAPPER_H
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Taner Sener
|
||||
* Copyright (c) 2018-2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.arthenica.mobileffmpeg.FFprobe;
|
||||
import com.arthenica.mobileffmpeg.GetMediaInformationCallback;
|
||||
import com.arthenica.mobileffmpeg.MediaInformation;
|
||||
|
||||
/**
|
||||
* <p>Utility class to get media information asynchronously.
|
||||
*
|
||||
* @author Taner Sener
|
||||
*/
|
||||
public class AsyncGetMediaInformationTask extends AsyncTask<String, MediaInformation, MediaInformation> {
|
||||
private final String path;
|
||||
private final GetMediaInformationCallback getMediaInformationCallback;
|
||||
|
||||
public AsyncGetMediaInformationTask(final String path, final GetMediaInformationCallback getMediaInformationCallback) {
|
||||
this.path = path;
|
||||
this.getMediaInformationCallback = getMediaInformationCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaInformation doInBackground(final String... arguments) {
|
||||
return FFprobe.getMediaInformation(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final MediaInformation mediaInformation) {
|
||||
if (getMediaInformationCallback != null) {
|
||||
getMediaInformationCallback.apply(mediaInformation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,11 @@ import java.util.List;
|
||||
import static android.content.Context.CAMERA_SERVICE;
|
||||
import static com.arthenica.mobileffmpeg.Config.TAG;
|
||||
|
||||
/**
|
||||
* Utility class for camera devices.
|
||||
*
|
||||
* @author Taner Sener
|
||||
*/
|
||||
class CameraSupport {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Taner Sener
|
||||
* Copyright (c) 2018-2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
@@ -20,13 +20,19 @@
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -78,25 +84,48 @@ public class Config {
|
||||
|
||||
private static int lastCreatedPipeIndex;
|
||||
|
||||
private static final List<FFmpegExecution> executions;
|
||||
private static SparseArray<ParcelFileDescriptor> pfdmap = new SparseArray<>();
|
||||
|
||||
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") || externalLibrariesEnabled.contains("rubberband")) {
|
||||
// libc++_shared.so included only when tesseract or x265 is enabled
|
||||
System.loadLibrary("c++_shared");
|
||||
}
|
||||
System.loadLibrary("cpufeatures");
|
||||
System.loadLibrary("avutil");
|
||||
System.loadLibrary("swscale");
|
||||
System.loadLibrary("swresample");
|
||||
System.loadLibrary("avcodec");
|
||||
System.loadLibrary("avformat");
|
||||
System.loadLibrary("avfilter");
|
||||
System.loadLibrary("avdevice");
|
||||
|
||||
if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
|
||||
try {
|
||||
System.loadLibrary("avutil_neon");
|
||||
System.loadLibrary("swscale_neon");
|
||||
System.loadLibrary("swresample_neon");
|
||||
System.loadLibrary("avcodec_neon");
|
||||
System.loadLibrary("avformat_neon");
|
||||
System.loadLibrary("avfilter_neon");
|
||||
System.loadLibrary("avdevice_neon");
|
||||
nativeFFmpegLoaded = true;
|
||||
} catch (final UnsatisfiedLinkError e) {
|
||||
Log.i(Config.TAG, "NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.", e);
|
||||
nativeFFmpegTriedAndFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeFFmpegLoaded) {
|
||||
System.loadLibrary("avutil");
|
||||
System.loadLibrary("swscale");
|
||||
System.loadLibrary("swresample");
|
||||
System.loadLibrary("avcodec");
|
||||
System.loadLibrary("avformat");
|
||||
System.loadLibrary("avfilter");
|
||||
System.loadLibrary("avdevice");
|
||||
}
|
||||
}
|
||||
|
||||
/* ALL MOBILE-FFMPEG LIBRARIES LOADED AT STARTUP */
|
||||
@@ -104,30 +133,23 @@ public class Config {
|
||||
FFmpeg.class.getName();
|
||||
FFprobe.class.getName();
|
||||
|
||||
/*
|
||||
* NEON supported arm-v7a library has a different name
|
||||
*/
|
||||
boolean nativeLibraryLoaded = false;
|
||||
if (AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
|
||||
if (AbiDetect.isNativeLTSBuild()) {
|
||||
boolean nativeMobileFFmpegLoaded = false;
|
||||
if (!nativeFFmpegTriedAndFailed && AbiDetect.ARM_V7A.equals(AbiDetect.getNativeAbi())) {
|
||||
try {
|
||||
|
||||
/*
|
||||
* IF CPU SUPPORTS ARM-V7A-NEON THE TRY TO LOAD IT FIRST. IF NOT LOAD DEFAULT ARM-V7A
|
||||
* THE TRY TO LOAD ARM-V7A-NEON FIRST. IF NOT LOAD DEFAULT ARM-V7A
|
||||
*/
|
||||
|
||||
try {
|
||||
System.loadLibrary("mobileffmpeg_armv7a_neon");
|
||||
nativeLibraryLoaded = true;
|
||||
AbiDetect.setArmV7aNeonLoaded(true);
|
||||
} catch (final UnsatisfiedLinkError e) {
|
||||
Log.i(Config.TAG, "NEON supported armeabi-v7a library not found. Loading default armeabi-v7a library.", e);
|
||||
}
|
||||
} else {
|
||||
System.loadLibrary("mobileffmpeg_armv7a_neon");
|
||||
nativeMobileFFmpegLoaded = true;
|
||||
AbiDetect.setArmV7aNeonLoaded(true);
|
||||
} catch (final UnsatisfiedLinkError e) {
|
||||
Log.i(Config.TAG, "NEON supported armeabi-v7a mobileffmpeg library not found. Loading default armeabi-v7a library.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeLibraryLoaded) {
|
||||
if (!nativeMobileFFmpegLoaded) {
|
||||
System.loadLibrary("mobileffmpeg");
|
||||
}
|
||||
|
||||
@@ -141,6 +163,8 @@ public class Config {
|
||||
enableRedirection();
|
||||
|
||||
lastCreatedPipeIndex = 0;
|
||||
|
||||
executions = Collections.synchronizedList(new ArrayList<FFmpegExecution>());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,10 +238,11 @@ public class Config {
|
||||
/**
|
||||
* <p>Log redirection method called by JNI/native part.
|
||||
*
|
||||
* @param levelValue log level as defined in {@link Level}
|
||||
* @param logMessage redirected log message
|
||||
* @param executionId id of the execution that generated this log, 0 by default
|
||||
* @param levelValue log level as defined in {@link Level}
|
||||
* @param logMessage redirected log message
|
||||
*/
|
||||
private static void log(final int levelValue, final byte[] logMessage) {
|
||||
private static void log(final long executionId, final int levelValue, final byte[] logMessage) {
|
||||
final Level level = Level.from(levelValue);
|
||||
final String text = new String(logMessage);
|
||||
|
||||
@@ -229,7 +254,7 @@ public class Config {
|
||||
|
||||
if (logCallbackFunction != null) {
|
||||
try {
|
||||
logCallbackFunction.apply(new LogMessage(level, text));
|
||||
logCallbackFunction.apply(new LogMessage(executionId, level, text));
|
||||
} catch (final Exception e) {
|
||||
Log.e(Config.TAG, "Exception thrown inside LogCallback block", e);
|
||||
}
|
||||
@@ -274,6 +299,7 @@ public class Config {
|
||||
/**
|
||||
* <p>Statistics redirection method called by JNI/native part.
|
||||
*
|
||||
* @param executionId id of the execution that generated this statistics, 0 by default
|
||||
* @param videoFrameNumber last processed frame number for videos
|
||||
* @param videoFps frames processed per second for videos
|
||||
* @param videoQuality quality of the video stream
|
||||
@@ -282,10 +308,10 @@ public class Config {
|
||||
* @param bitrate output bit rate in kbits/s
|
||||
* @param speed processing speed = processed duration / operation duration
|
||||
*/
|
||||
private static void statistics(final int videoFrameNumber, final float videoFps,
|
||||
final float videoQuality, final long size, final int time,
|
||||
final double bitrate, final double speed) {
|
||||
final Statistics newStatistics = new Statistics(videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
|
||||
private static void statistics(final long executionId, final int videoFrameNumber,
|
||||
final float videoFps, final float videoQuality, final long size,
|
||||
final int time, final double bitrate, final double speed) {
|
||||
final Statistics newStatistics = new Statistics(executionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
|
||||
lastReceivedStatistics.update(newStatistics);
|
||||
|
||||
if (statisticsCallbackFunction != null) {
|
||||
@@ -497,7 +523,7 @@ public class Config {
|
||||
* @return MobileFFmpeg version
|
||||
*/
|
||||
public static String getVersion() {
|
||||
if (AbiDetect.isNativeLTSBuild()) {
|
||||
if (isLTSBuild()) {
|
||||
return String.format("%s-lts", getNativeVersion());
|
||||
} else {
|
||||
return getNativeVersion();
|
||||
@@ -586,6 +612,48 @@ public class Config {
|
||||
} while (buffer.length() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets an environment variable.
|
||||
*
|
||||
* @param variableName environment variable name
|
||||
* @param variableValue environment variable value
|
||||
* @return zero on success, non-zero on error
|
||||
*/
|
||||
public static int setEnvironmentVariable(final String variableName, final String variableValue) {
|
||||
return setNativeEnvironmentVariable(variableName, variableValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a new ignored signal. Ignored signals are not handled by the library.
|
||||
*
|
||||
* @param signal signal number to ignore
|
||||
*/
|
||||
public static void ignoreSignal(final Signal signal) {
|
||||
ignoreNativeSignal(signal.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFmpeg with arguments provided.
|
||||
*
|
||||
* @param executionId id of the execution
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
*/
|
||||
static int ffmpegExecute(final long executionId, final String[] arguments) {
|
||||
final FFmpegExecution currentFFmpegExecution = new FFmpegExecution(executionId, arguments);
|
||||
executions.add(currentFFmpegExecution);
|
||||
|
||||
try {
|
||||
final int lastReturnCode = nativeFFmpegExecute(executionId, arguments);
|
||||
|
||||
Config.setLastReturnCode(lastReturnCode);
|
||||
|
||||
return lastReturnCode;
|
||||
} finally {
|
||||
executions.remove(currentFFmpegExecution);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates return code value for the last executed command.
|
||||
*
|
||||
@@ -595,6 +663,15 @@ public class Config {
|
||||
lastReturnCode = newLastReturnCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Lists ongoing FFmpeg executions.
|
||||
*
|
||||
* @return list of ongoing FFmpeg executions
|
||||
*/
|
||||
static List<FFmpegExecution> listFFmpegExecutions() {
|
||||
return new ArrayList<>(executions);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Enables native redirection. Necessary for log and statistics callback functions.
|
||||
*/
|
||||
@@ -624,28 +701,31 @@ public class Config {
|
||||
*
|
||||
* @return FFmpeg version
|
||||
*/
|
||||
native static String getNativeFFmpegVersion();
|
||||
private native static String getNativeFFmpegVersion();
|
||||
|
||||
/**
|
||||
* <p>Returns MobileFFmpeg library version natively.
|
||||
*
|
||||
* @return MobileFFmpeg version
|
||||
*/
|
||||
native static String getNativeVersion();
|
||||
private native static String getNativeVersion();
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFmpeg natively with arguments provided.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @param executionId id of the execution
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
*/
|
||||
native static int nativeFFmpegExecute(final String[] arguments);
|
||||
private native static int nativeFFmpegExecute(final long executionId, final String[] arguments);
|
||||
|
||||
/**
|
||||
* <p>Cancels an ongoing FFmpeg operation natively. This function does not wait for termination
|
||||
* to complete and returns immediately.
|
||||
*
|
||||
* @param executionId id of the execution
|
||||
*/
|
||||
native static void nativeFFmpegCancel();
|
||||
native static void nativeFFmpegCancel(final long executionId);
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFprobe natively with arguments provided.
|
||||
@@ -663,14 +743,14 @@ public class Config {
|
||||
* @param ffmpegPipePath full path of ffmpeg pipe
|
||||
* @return zero on successful creation, non-zero on error
|
||||
*/
|
||||
native static int registerNewNativeFFmpegPipe(final String ffmpegPipePath);
|
||||
private native static int registerNewNativeFFmpegPipe(final String ffmpegPipePath);
|
||||
|
||||
/**
|
||||
* <p>Returns MobileFFmpeg library build date natively.
|
||||
*
|
||||
* @return MobileFFmpeg library build date
|
||||
*/
|
||||
native static String getNativeBuildDate();
|
||||
private native static String getNativeBuildDate();
|
||||
|
||||
/**
|
||||
* <p>Sets an environment variable natively.
|
||||
@@ -679,13 +759,74 @@ 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);
|
||||
|
||||
/**
|
||||
* <p>Convert Structured Access Framework Uri (<code></code>"content:…"</code>) for MobileFfmpeg.
|
||||
*
|
||||
* @return String can be passed to FFmpeg or FFprobe
|
||||
*/
|
||||
private static String getSafParameter(Context context, Uri uri, String openMode) {
|
||||
|
||||
String displayName = "unknown";
|
||||
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
displayName = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, "failed to get column", ex);
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
try {
|
||||
ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, openMode);
|
||||
fd = parcelFileDescriptor.getFd();
|
||||
pfdmap.put(fd, parcelFileDescriptor);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "obtaining " + openMode + " ParcelFileDescriptor for " + uri, e);
|
||||
}
|
||||
|
||||
// workaround for https://issuetracker.google.com/issues/162440528: ANDROID_CREATE_DOCUMENT generating file names like "transcode.mp3 (2)"
|
||||
if (displayName.lastIndexOf('.') > 0 && displayName.lastIndexOf(' ') > displayName.lastIndexOf('.')) {
|
||||
String extension = displayName.substring(displayName.lastIndexOf('.'), displayName.lastIndexOf(' '));
|
||||
displayName += extension;
|
||||
}
|
||||
// spaces can break argument list parsing, see https://github.com/alexcohn/mobile-ffmpeg/pull/1#issuecomment-688643836
|
||||
final char NBSP = (char)0xa0;
|
||||
return "saf:" + fd + "/" + displayName.replace(' ', NBSP);
|
||||
}
|
||||
|
||||
public static String getSafParameterForRead(Context context, Uri uri) {
|
||||
return getSafParameter(context, uri, "r");
|
||||
}
|
||||
|
||||
public static String getSafParameterForWrite(Context context, Uri uri) {
|
||||
return getSafParameter(context, uri, "w");
|
||||
}
|
||||
|
||||
private static void closeParcelFileDescriptor(int fd) {
|
||||
try {
|
||||
ParcelFileDescriptor pfd = pfdmap.get(fd);
|
||||
if (pfd != null) {
|
||||
pfd.close();
|
||||
pfdmap.delete(fd);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "closeParcelFileDescriptor " + fd, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
/**
|
||||
* <p>Represents a callback function to receive an asynchronous execution result.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ExecuteCallback {
|
||||
|
||||
/**
|
||||
* <p>Called when an asynchronous FFmpeg execution is completed.
|
||||
*
|
||||
* @param executionId id of the execution that completed
|
||||
* @param returnCode return code of the execution completed, 0 on successful completion, 255
|
||||
* on user cancel, other non-zero codes on error
|
||||
*/
|
||||
void apply(long executionId, int returnCode);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Taner Sener
|
||||
* Copyright (c) 2018-2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
@@ -19,22 +19,34 @@
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* <p>Main class for FFmpeg operations. Provides {@link #execute(String...)} method to execute
|
||||
* FFmpeg commands.
|
||||
* <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and
|
||||
* asynchronous {@link #executeAsync(String, ExecuteCallback)} methods to execute FFmpeg commands.
|
||||
* <pre>
|
||||
* int rc = FFmpeg.execute("-i file1.mp4 -c:v libxvid file1.avi");
|
||||
* Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
|
||||
* </pre>
|
||||
* <pre>
|
||||
* long executionId = FFmpeg.executeAsync("-i file1.mp4 -c:v libxvid file1.avi", executeCallback);
|
||||
* Log.i(Config.TAG, String.format("Asynchronous execution %d started.", executionId));
|
||||
* </pre>
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v1.0
|
||||
*/
|
||||
public class FFmpeg {
|
||||
|
||||
static final long DEFAULT_EXECUTION_ID = 0;
|
||||
|
||||
private static final AtomicLong executionIdCounter = new AtomicLong(3000);
|
||||
|
||||
static {
|
||||
AbiDetect.class.getName();
|
||||
Config.class.getName();
|
||||
@@ -50,14 +62,43 @@ public class FFmpeg {
|
||||
* <p>Synchronously executes FFmpeg with arguments provided.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
|
||||
*/
|
||||
public static int execute(final String[] arguments) {
|
||||
final int lastReturnCode = Config.nativeFFmpegExecute(arguments);
|
||||
return Config.ffmpegExecute(DEFAULT_EXECUTION_ID, arguments);
|
||||
}
|
||||
|
||||
Config.setLastReturnCode(lastReturnCode);
|
||||
/**
|
||||
* <p>Asynchronously executes FFmpeg with arguments provided.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @param executeCallback callback that will be notified when execution is completed
|
||||
* @return returns a unique id that represents this execution
|
||||
*/
|
||||
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback) {
|
||||
final long newExecutionId = executionIdCounter.incrementAndGet();
|
||||
|
||||
return lastReturnCode;
|
||||
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
|
||||
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
return newExecutionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Asynchronously executes FFmpeg with arguments provided.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @param executeCallback callback that will be notified when execution is completed
|
||||
* @param executor executor that will be used to run this asynchronous operation
|
||||
* @return returns a unique id that represents this execution
|
||||
*/
|
||||
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback, final Executor executor) {
|
||||
final long newExecutionId = executionIdCounter.incrementAndGet();
|
||||
|
||||
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
|
||||
asyncFFmpegExecuteTask.executeOnExecutor(executor);
|
||||
|
||||
return newExecutionId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +107,7 @@ public class FFmpeg {
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param delimiter delimiter used to split arguments
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
|
||||
* @since 3.0
|
||||
* @deprecated argument splitting mechanism used in this method is pretty simple and prone to
|
||||
* errors. Consider using a more advanced method like {@link #execute(String)} or
|
||||
@@ -82,18 +123,76 @@ public class FFmpeg {
|
||||
* your command.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
|
||||
*/
|
||||
public static int execute(final String command) {
|
||||
return execute(parseArguments(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Cancels an ongoing operation. This function does not wait for termination to complete and
|
||||
* returns immediately.
|
||||
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
|
||||
* into arguments. You can use single and double quote characters to specify arguments inside
|
||||
* your command.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param executeCallback callback that will be notified when execution is completed
|
||||
* @return returns a unique id that represents this execution
|
||||
*/
|
||||
public static long executeAsync(final String command, final ExecuteCallback executeCallback) {
|
||||
final long newExecutionId = executionIdCounter.incrementAndGet();
|
||||
|
||||
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
|
||||
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
return newExecutionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
|
||||
* into arguments. You can use single and double quote characters to specify arguments inside
|
||||
* your command.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param executeCallback callback that will be notified when execution is completed
|
||||
* @param executor executor that will be used to run this asynchronous operation
|
||||
* @return returns a unique id that represents this execution
|
||||
*/
|
||||
public static long executeAsync(final String command, final ExecuteCallback executeCallback, final Executor executor) {
|
||||
final long newExecutionId = executionIdCounter.incrementAndGet();
|
||||
|
||||
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
|
||||
asyncFFmpegExecuteTask.executeOnExecutor(executor);
|
||||
|
||||
return newExecutionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Cancels an ongoing operation.
|
||||
*
|
||||
* <p>This function does not wait for termination to complete and returns immediately.
|
||||
*/
|
||||
public static void cancel() {
|
||||
Config.nativeFFmpegCancel();
|
||||
Config.nativeFFmpegCancel(DEFAULT_EXECUTION_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Cancels an ongoing operation.
|
||||
*
|
||||
* <p>This function does not wait for termination to complete and returns immediately.
|
||||
*
|
||||
* @param executionId id of the execution
|
||||
*/
|
||||
public static void cancel(final long executionId) {
|
||||
Config.nativeFFmpegCancel(executionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Lists ongoing executions.
|
||||
*
|
||||
* @return list of ongoing executions
|
||||
*/
|
||||
public static List<FFmpegExecution> listExecutions() {
|
||||
return Config.listFFmpegExecutions();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,4 +252,26 @@ public class FFmpeg {
|
||||
return argumentList.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Combines arguments into a string.
|
||||
*
|
||||
* @param arguments arguments
|
||||
* @return string containing all arguments
|
||||
*/
|
||||
static String argumentsToString(final String[] arguments) {
|
||||
if (arguments == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
if (i > 0) {
|
||||
stringBuilder.append(" ");
|
||||
}
|
||||
stringBuilder.append(arguments[i]);
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>Represents an ongoing FFmpeg execution.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v4.4
|
||||
*/
|
||||
public class FFmpegExecution {
|
||||
private final Date startTime;
|
||||
private final long executionId;
|
||||
private final String command;
|
||||
|
||||
public FFmpegExecution(final long executionId, final String[] arguments) {
|
||||
this.startTime = new Date();
|
||||
this.executionId = executionId;
|
||||
this.command = FFmpeg.argumentsToString(arguments);
|
||||
}
|
||||
|
||||
public Date getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public long getExecutionId() {
|
||||
return executionId;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public class FFprobe {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns media information for given file.
|
||||
* <p>Returns media information for the given file.
|
||||
*
|
||||
* <p>This method does not support executing multiple concurrent operations. If you execute
|
||||
* multiple operations (execute or getMediaInformation) at the same time, the response of this
|
||||
@@ -83,14 +83,22 @@ public class FFprobe {
|
||||
* @since 3.0
|
||||
*/
|
||||
public static MediaInformation getMediaInformation(final String path) {
|
||||
final int rc = execute(new String[]{"-v", "info", "-hide_banner", "-i", path});
|
||||
return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path});
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
return MediaInformationParser.from(Config.getLastCommandOutput());
|
||||
} else {
|
||||
Log.i(Config.TAG, Config.getLastCommandOutput());
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* <p>Returns media information for the given command.
|
||||
*
|
||||
* <p>This method does not support executing multiple concurrent operations. If you execute
|
||||
* multiple operations (execute or getMediaInformation) at the same time, the response of this
|
||||
* method is not predictable.
|
||||
*
|
||||
* @param command command to execute
|
||||
* @return media information
|
||||
* @since 4.3.3
|
||||
*/
|
||||
public static MediaInformation getMediaInformationFromCommand(final String command) {
|
||||
return getMediaInformationFromCommandArguments(FFmpeg.parseArguments(command));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,4 +119,15 @@ public class FFprobe {
|
||||
return getMediaInformation(path);
|
||||
}
|
||||
|
||||
private static MediaInformation getMediaInformationFromCommandArguments(final String[] arguments) {
|
||||
final int rc = execute(arguments);
|
||||
|
||||
if (rc == 0) {
|
||||
return MediaInformationParser.from(Config.getLastCommandOutput());
|
||||
} else {
|
||||
Log.w(Config.TAG, Config.getLastCommandOutput());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
/**
|
||||
* <p>Represents a callback function to receive asynchronous getMediaInformation result.
|
||||
*
|
||||
* @author Taner Sener
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GetMediaInformationCallback {
|
||||
|
||||
void apply(MediaInformation mediaInformation);
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
/**
|
||||
* <p>Represents a callback function to redirect logs.
|
||||
* <p>Represents a callback function to receive logs from running executions
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
|
||||
@@ -20,21 +20,27 @@
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
/**
|
||||
* <p>Represents a redirected log message.
|
||||
* <p>Logs for running executions.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
public class LogMessage {
|
||||
|
||||
private final long executionId;
|
||||
private final Level level;
|
||||
private final String text;
|
||||
|
||||
public LogMessage(final Level level, final String text) {
|
||||
public LogMessage(final long executionId, final Level level, final String text) {
|
||||
this.executionId = executionId;
|
||||
this.level = level;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public long getExecutionId() {
|
||||
return executionId;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
@@ -43,4 +49,21 @@ public class LogMessage {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append("LogMessage{");
|
||||
stringBuilder.append("executionId=");
|
||||
stringBuilder.append(executionId);
|
||||
stringBuilder.append(", level=");
|
||||
stringBuilder.append(level);
|
||||
stringBuilder.append(", text=");
|
||||
stringBuilder.append("\'");
|
||||
stringBuilder.append(text);
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append('}');
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Taner Sener
|
||||
* Copyright (c) 2018, 2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
@@ -19,11 +19,9 @@
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Media information class.
|
||||
@@ -32,49 +30,38 @@ import java.util.Set;
|
||||
*/
|
||||
public class MediaInformation {
|
||||
|
||||
/**
|
||||
* Format
|
||||
*/
|
||||
private String format;
|
||||
private static final String KEY_MEDIA_PROPERTIES = "format";
|
||||
private static final String KEY_FILENAME = "filename";
|
||||
private static final String KEY_FORMAT = "format_name";
|
||||
private static final String KEY_FORMAT_LONG = "format_long_name";
|
||||
private static final String KEY_START_TIME = "start_time";
|
||||
private static final String KEY_DURATION = "duration";
|
||||
private static final String KEY_SIZE = "size";
|
||||
private static final String KEY_BIT_RATE = "bit_rate";
|
||||
private static final String KEY_TAGS = "tags";
|
||||
|
||||
/**
|
||||
* Path
|
||||
* Stores all properties.
|
||||
*/
|
||||
private String path;
|
||||
private final JSONObject jsonObject;
|
||||
|
||||
/**
|
||||
* Duration, in milliseconds
|
||||
* Stores streams.
|
||||
*/
|
||||
private Long duration;
|
||||
private final List<StreamInformation> streams;
|
||||
|
||||
public MediaInformation(final JSONObject jsonObject, final List<StreamInformation> streams) {
|
||||
this.jsonObject = jsonObject;
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start time, in milliseconds
|
||||
* Returns file name.
|
||||
*
|
||||
* @return media file name
|
||||
*/
|
||||
private Long startTime;
|
||||
|
||||
/**
|
||||
* Bitrate, kb/s
|
||||
*/
|
||||
private Long bitrate;
|
||||
|
||||
/**
|
||||
* Metadata map
|
||||
*/
|
||||
private Map<String, String> metadata;
|
||||
|
||||
/**
|
||||
* List of streams
|
||||
*/
|
||||
private List<StreamInformation> streams;
|
||||
|
||||
/**
|
||||
* Raw unparsed media information
|
||||
*/
|
||||
private String rawInformation;
|
||||
|
||||
public MediaInformation() {
|
||||
this.metadata = new HashMap<>();
|
||||
this.streams = new ArrayList<>();
|
||||
public String getFilename() {
|
||||
return getStringProperty(KEY_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,34 +70,16 @@ public class MediaInformation {
|
||||
* @return media format
|
||||
*/
|
||||
public String getFormat() {
|
||||
return format;
|
||||
return getStringProperty(KEY_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets media format.
|
||||
* Returns long format.
|
||||
*
|
||||
* @param format media format
|
||||
* @return media long format
|
||||
*/
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path.
|
||||
*
|
||||
* @return media path
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets path.
|
||||
*
|
||||
* @param path media path
|
||||
*/
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
public String getLongFormat() {
|
||||
return getStringProperty(KEY_FORMAT_LONG);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,17 +87,8 @@ public class MediaInformation {
|
||||
*
|
||||
* @return media duration in milliseconds
|
||||
*/
|
||||
public Long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets duration.
|
||||
*
|
||||
* @param duration media duration in milliseconds
|
||||
*/
|
||||
public void setDuration(Long duration) {
|
||||
this.duration = duration;
|
||||
public String getDuration() {
|
||||
return getStringProperty(KEY_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,17 +96,17 @@ public class MediaInformation {
|
||||
*
|
||||
* @return media start time in milliseconds
|
||||
*/
|
||||
public Long getStartTime() {
|
||||
return startTime;
|
||||
public String getStartTime() {
|
||||
return getStringProperty(KEY_START_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets start time.
|
||||
* Returns size.
|
||||
*
|
||||
* @param startTime media start time in milliseconds
|
||||
* @return media size in bytes
|
||||
*/
|
||||
public void setStartTime(Long startTime) {
|
||||
this.startTime = startTime;
|
||||
public String getSize() {
|
||||
return getStringProperty(KEY_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,63 +114,17 @@ public class MediaInformation {
|
||||
*
|
||||
* @return media bitrate in kb/s
|
||||
*/
|
||||
public Long getBitrate() {
|
||||
return bitrate;
|
||||
public String getBitrate() {
|
||||
return getStringProperty(KEY_BIT_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bitrate.
|
||||
* Returns all tags.
|
||||
*
|
||||
* @param bitrate media bitrate in kb/s
|
||||
* @return tags dictionary
|
||||
*/
|
||||
public void setBitrate(Long bitrate) {
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unparsed media information.
|
||||
*
|
||||
* @return unparsed media information data
|
||||
*/
|
||||
public String getRawInformation() {
|
||||
return rawInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets unparsed media information.
|
||||
*
|
||||
* @param rawInformation unparsed media information data
|
||||
*/
|
||||
public void setRawInformation(String rawInformation) {
|
||||
this.rawInformation = rawInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds metadata.
|
||||
*
|
||||
* @param key metadata key
|
||||
* @param value metadata value
|
||||
*/
|
||||
public void addMetadata(String key, String value) {
|
||||
this.metadata.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all metadata entries.
|
||||
*
|
||||
* @return set of metadata entries
|
||||
*/
|
||||
public Set<Map.Entry<String, String>> getMetadataEntries() {
|
||||
return this.metadata.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new stream.
|
||||
*
|
||||
* @param stream new stream information
|
||||
*/
|
||||
public void addStream(StreamInformation stream) {
|
||||
this.streams.add(stream);
|
||||
public JSONObject getTags() {
|
||||
return getProperties(KEY_TAGS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,4 +136,75 @@ public class MediaInformation {
|
||||
return streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return media property as string or null if the key is not found
|
||||
*/
|
||||
public String getStringProperty(final String key) {
|
||||
JSONObject mediaProperties = getMediaProperties();
|
||||
if (mediaProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mediaProperties.has(key)) {
|
||||
return mediaProperties.optString(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return media property as Long or null if the key is not found
|
||||
*/
|
||||
public Long getNumberProperty(String key) {
|
||||
JSONObject mediaProperties = getMediaProperties();
|
||||
if (mediaProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mediaProperties.has(key)) {
|
||||
return mediaProperties.optLong(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media properties associated with the key.
|
||||
*
|
||||
* @param key properties key
|
||||
* @return media properties as a JSONObject or null if the key is not found
|
||||
*/
|
||||
public JSONObject getProperties(String key) {
|
||||
JSONObject mediaProperties = getMediaProperties();
|
||||
if (mediaProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mediaProperties.optJSONObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all media properties.
|
||||
*
|
||||
* @return all media properties as a JSONObject or null if no media properties are defined
|
||||
*/
|
||||
public JSONObject getMediaProperties() {
|
||||
return jsonObject.optJSONObject(KEY_MEDIA_PROPERTIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all properties defined.
|
||||
*
|
||||
* @return all properties as a JSONObject or null if no properties are defined
|
||||
*/
|
||||
public JSONObject getAllProperties() {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Taner Sener
|
||||
* Copyright (c) 2018, 2020 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
@@ -21,505 +21,55 @@ package com.arthenica.mobileffmpeg;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.arthenica.mobileffmpeg.util.Pair;
|
||||
import com.arthenica.mobileffmpeg.util.Trio;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Helper class for {@link MediaInformation}.
|
||||
* Helper class for parsing {@link MediaInformation}.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class MediaInformationParser {
|
||||
|
||||
public static SimpleDateFormat DURATION_FORMAT;
|
||||
|
||||
public static Date REFERENCE_DURATION;
|
||||
|
||||
static {
|
||||
/**
|
||||
* Extracts MediaInformation from the given ffprobe json output.
|
||||
*
|
||||
* @param ffprobeJsonOutput ffprobe json output
|
||||
* @return created {@link MediaInformation} instance of null if a parsing error occurs
|
||||
*/
|
||||
public static MediaInformation from(final String ffprobeJsonOutput) {
|
||||
try {
|
||||
DURATION_FORMAT = new SimpleDateFormat("kk:mm:ss", Locale.getDefault());
|
||||
REFERENCE_DURATION = DURATION_FORMAT.parse("00:00:00");
|
||||
} catch (final ParseException e) {
|
||||
Log.i(Config.TAG, "Preparing duration reference failed.", e);
|
||||
DURATION_FORMAT = null;
|
||||
REFERENCE_DURATION = null;
|
||||
return fromWithError(ffprobeJsonOutput);
|
||||
} catch (JSONException e) {
|
||||
Log.e(Config.TAG, "MediaInformation parsing failed.", e);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses media information command output and builds a {@link MediaInformation} instance.
|
||||
* Extracts MediaInformation from the given ffprobe json output.
|
||||
*
|
||||
* @param rawCommandOutput media information command output
|
||||
* @return parsed instance of null if a parsing error occurs
|
||||
* @param ffprobeJsonOutput ffprobe json output
|
||||
* @return created {@link MediaInformation} instance
|
||||
* @throws JSONException if a parsing error occurs
|
||||
*/
|
||||
public static MediaInformation from(final String rawCommandOutput) {
|
||||
final MediaInformation mediaInformation = new MediaInformation();
|
||||
public static MediaInformation fromWithError(final String ffprobeJsonOutput) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject(ffprobeJsonOutput);
|
||||
JSONArray streamArray = jsonObject.optJSONArray("streams");
|
||||
|
||||
if (rawCommandOutput != null) {
|
||||
final String[] split = rawCommandOutput.split("\n");
|
||||
boolean metadata = false;
|
||||
boolean sidedata = false;
|
||||
StreamInformation lastCreatedStream = null;
|
||||
final StringBuilder rawInformation = new StringBuilder();
|
||||
|
||||
for (final String outputLine : split) {
|
||||
if (outputLine.startsWith("[")) {
|
||||
metadata = false;
|
||||
sidedata = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
final String trimmedLine = outputLine.trim();
|
||||
|
||||
if (trimmedLine.startsWith("Input")) {
|
||||
metadata = false;
|
||||
sidedata = false;
|
||||
lastCreatedStream = null;
|
||||
Pair<String, String> pair = parseInputBlock(trimmedLine);
|
||||
mediaInformation.setFormat(pair.getFirst());
|
||||
mediaInformation.setPath(pair.getSecond());
|
||||
} else if (trimmedLine.startsWith("Duration")) {
|
||||
metadata = false;
|
||||
sidedata = false;
|
||||
lastCreatedStream = null;
|
||||
Trio<Long, Long, Long> trio = parseDurationBlock(trimmedLine);
|
||||
mediaInformation.setDuration(trio.getFirst());
|
||||
mediaInformation.setStartTime(trio.getSecond());
|
||||
mediaInformation.setBitrate(trio.getThird());
|
||||
} else if (trimmedLine.toLowerCase(Locale.ENGLISH).startsWith("metadata")) {
|
||||
sidedata = false;
|
||||
metadata = true;
|
||||
} else if (trimmedLine.toLowerCase(Locale.ENGLISH).startsWith("side data")) {
|
||||
metadata = false;
|
||||
sidedata = true;
|
||||
} else if (trimmedLine.startsWith("Stream mapping") || trimmedLine.startsWith("Press [q] to stop") || trimmedLine.startsWith("Output")) {
|
||||
break;
|
||||
} else if (trimmedLine.startsWith("Stream")) {
|
||||
metadata = false;
|
||||
sidedata = false;
|
||||
lastCreatedStream = MediaInformationParser.parseStreamBlock(trimmedLine);
|
||||
mediaInformation.addStream(lastCreatedStream);
|
||||
} else if (metadata) {
|
||||
Pair<String, String> pair = parseMetadataBlock(trimmedLine);
|
||||
|
||||
if (pair.getFirst() != null && pair.getSecond() != null) {
|
||||
if (lastCreatedStream != null) {
|
||||
lastCreatedStream.addMetadata(pair.getFirst(), pair.getSecond());
|
||||
} else {
|
||||
mediaInformation.addMetadata(pair.getFirst(), pair.getSecond());
|
||||
}
|
||||
}
|
||||
} else if (sidedata) {
|
||||
Pair<String, String> pair = parseMetadataBlock(trimmedLine);
|
||||
|
||||
if (pair.getFirst() != null && pair.getSecond() != null) {
|
||||
if (lastCreatedStream != null) {
|
||||
lastCreatedStream.addSidedata(pair.getFirst(), pair.getSecond());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rawInformation.append(outputLine);
|
||||
rawInformation.append("\n");
|
||||
}
|
||||
|
||||
mediaInformation.setRawInformation(rawInformation.toString());
|
||||
}
|
||||
|
||||
return mediaInformation;
|
||||
}
|
||||
|
||||
static Pair<String, String> parseInputBlock(final String input) {
|
||||
String format = substring(input, ",", ", from", Collections.<String>emptyList());
|
||||
String path = substring(input, "\'", "\'", Collections.<String>emptyList());
|
||||
|
||||
return new Pair<>(format, path);
|
||||
}
|
||||
|
||||
static Trio<Long, Long, Long> parseDurationBlock(final String line) {
|
||||
Long duration = parseDuration(substring(line, "Duration:", ",", Collections.singletonList("uration:")));
|
||||
Long start = parseStartTime(substring(line, "start:", ",", Collections.singletonList("tart:")));
|
||||
Long bitrate = toLongObject(substring(line, "bitrate:", Arrays.asList("itrate:", "kb/s")));
|
||||
|
||||
return new Trio<>(duration, start, bitrate);
|
||||
}
|
||||
|
||||
static Pair<String, String> parseMetadataBlock(final String metadata) {
|
||||
String key = null;
|
||||
String value = null;
|
||||
|
||||
if (metadata != null) {
|
||||
int index = metadata.indexOf(':');
|
||||
if (index > -1) {
|
||||
key = metadata.substring(0, index).trim();
|
||||
value = metadata.substring(index + 1).trim();
|
||||
ArrayList<StreamInformation> arrayList = new ArrayList<>();
|
||||
for (int i = 0; streamArray != null && i < streamArray.length(); i++) {
|
||||
JSONObject streamObject = streamArray.optJSONObject(i);
|
||||
if (streamObject != null) {
|
||||
arrayList.add(new StreamInformation(streamObject));
|
||||
}
|
||||
}
|
||||
|
||||
return new Pair<>(key, value);
|
||||
}
|
||||
|
||||
static StreamInformation parseStreamBlock(final String input) {
|
||||
final StreamInformation streamInformation = new StreamInformation();
|
||||
|
||||
if (input != null) {
|
||||
streamInformation.setIndex(parseStreamIndex(input));
|
||||
|
||||
int typeBlockStartIndex = index(input, ":", 0, 2);
|
||||
if (typeBlockStartIndex > -1 && (typeBlockStartIndex < input.length())) {
|
||||
String[] parts = input.substring(typeBlockStartIndex + 1).split(",");
|
||||
String typePart = safeGet(parts, 0);
|
||||
|
||||
final String type = parseStreamType(typePart);
|
||||
streamInformation.setType(type);
|
||||
streamInformation.setCodec(parseStreamCodec(typePart));
|
||||
streamInformation.setFullCodec(parseStreamFullCodec(typePart));
|
||||
|
||||
String part2 = safeGet(parts, 1);
|
||||
String part3 = safeGet(parts, 2);
|
||||
String part4 = safeGet(parts, 3);
|
||||
String part5 = safeGet(parts, 4);
|
||||
if ("video".equals(type)) {
|
||||
int lastUsedPart = 1;
|
||||
|
||||
if (part2 != null) {
|
||||
int pStart = count(part2, "(");
|
||||
int pEnd = count(part2, ")");
|
||||
|
||||
while (pStart != pEnd) {
|
||||
lastUsedPart++;
|
||||
String newPart = safeGet(parts, lastUsedPart);
|
||||
if (newPart == null) {
|
||||
break;
|
||||
}
|
||||
part2 = String.format("%s,%s", part2, newPart);
|
||||
pStart = count(part2, "(");
|
||||
pEnd = count(part2, ")");
|
||||
}
|
||||
|
||||
streamInformation.setFullFormat(part2.toLowerCase(Locale.getDefault()).trim());
|
||||
streamInformation.setFormat(part2.replaceAll("\\(.*\\)", "").toLowerCase(Locale.getDefault()).trim());
|
||||
}
|
||||
|
||||
lastUsedPart++;
|
||||
String videoDimensionPart = safeGet(parts, lastUsedPart);
|
||||
if (videoDimensionPart != null) {
|
||||
String videoLayout = videoDimensionPart.toLowerCase(Locale.getDefault()).trim();
|
||||
Pair<Long, Long> dimensions = parseVideoDimensions(videoLayout);
|
||||
streamInformation.setWidth(dimensions.getFirst());
|
||||
streamInformation.setHeight(dimensions.getSecond());
|
||||
streamInformation.setSampleAspectRatio(parseVideoStreamSampleAspectRatio(videoLayout));
|
||||
streamInformation.setDisplayAspectRatio(parseVideoStreamDisplayAspectRatio(videoLayout));
|
||||
}
|
||||
|
||||
for (int i = lastUsedPart + 1; i < parts.length; i++) {
|
||||
String part = parts[i].replaceAll("\\(.*\\)", "").toLowerCase(Locale.getDefault());
|
||||
|
||||
if (part.contains("kb/s")) {
|
||||
streamInformation.setBitrate(toLongObject(part.replaceAll("kb/s", "").trim()));
|
||||
} else if (part.contains("fps")) {
|
||||
streamInformation.setAverageFrameRate(part.replaceAll("fps", "").trim());
|
||||
} else if (part.contains("tbr")) {
|
||||
streamInformation.setRealFrameRate(part.replaceAll("tbr", "").trim());
|
||||
} else if (part.contains("tbn")) {
|
||||
streamInformation.setTimeBase(part.replaceAll("tbn", "").trim());
|
||||
} else if (part.contains("tbc")) {
|
||||
streamInformation.setCodecTimeBase(part.replaceAll("tbc", "").trim());
|
||||
}
|
||||
}
|
||||
} else if ("audio".equals(type)) {
|
||||
if (part2 != null) {
|
||||
streamInformation.setSampleRate(parseAudioStreamSampleRate(part2));
|
||||
}
|
||||
if (part3 != null) {
|
||||
streamInformation.setChannelLayout(part3.toLowerCase(Locale.getDefault()).trim());
|
||||
}
|
||||
if (part4 != null) {
|
||||
streamInformation.setSampleFormat(part4.toLowerCase(Locale.getDefault()).trim());
|
||||
}
|
||||
if (part5 != null) {
|
||||
streamInformation.setBitrate(toLongObject(part5.toLowerCase(Locale.getDefault()).replaceAll("\\(.*\\)", "").replaceAll("kb/s", "").trim()));
|
||||
}
|
||||
} else if ("data".equals(type)) {
|
||||
if (part2 != null) {
|
||||
streamInformation.setBitrate(toLongObject(part2.toLowerCase(Locale.getDefault()).replaceAll("\\(.*\\)", "").replaceAll("kb/s", "").trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return streamInformation;
|
||||
}
|
||||
|
||||
static Pair<Long, Long> parseVideoDimensions(final String input) {
|
||||
Long width = null;
|
||||
Long height = null;
|
||||
|
||||
if (input != null) {
|
||||
final String[] dimensions = input.toLowerCase(Locale.getDefault()).replaceAll("\\[.*\\]", "").trim().split("x");
|
||||
width = toLongObject(safeGet(dimensions, 0));
|
||||
height = toLongObject(safeGet(dimensions, 1));
|
||||
}
|
||||
|
||||
return new Pair<>(width, height);
|
||||
}
|
||||
|
||||
static String parseVideoStreamSampleAspectRatio(final String input) {
|
||||
if (input != null) {
|
||||
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (parts[i].toLowerCase(Locale.getDefault()).equals("sar")) {
|
||||
return safeGet(parts, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseVideoStreamDisplayAspectRatio(final String input) {
|
||||
if (input != null) {
|
||||
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (parts[i].toLowerCase(Locale.getDefault()).equals("dar")) {
|
||||
return safeGet(parts, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Long parseAudioStreamSampleRate(final String input) {
|
||||
if (input != null) {
|
||||
boolean khz = false;
|
||||
boolean mhz = false;
|
||||
|
||||
String lowerCase = input.toLowerCase(Locale.getDefault());
|
||||
if (lowerCase.contains("khz")) {
|
||||
khz = true;
|
||||
}
|
||||
if (lowerCase.contains("mhz")) {
|
||||
mhz = true;
|
||||
}
|
||||
|
||||
String sampleRate = lowerCase
|
||||
.replaceAll("khz", "")
|
||||
.replaceAll("mhz", "")
|
||||
.replaceAll("hz", "")
|
||||
.trim();
|
||||
|
||||
if (khz) {
|
||||
return 1000 * toLong(sampleRate);
|
||||
} else if (mhz) {
|
||||
return 1000000 * toLong(sampleRate);
|
||||
} else {
|
||||
return toLong(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseStreamType(final String input) {
|
||||
if (input != null) {
|
||||
if (input.toLowerCase(Locale.getDefault()).contains("audio:")) {
|
||||
return "audio";
|
||||
} else if (input.toLowerCase(Locale.getDefault()).contains("video:")) {
|
||||
return "video";
|
||||
} else if (input.toLowerCase(Locale.getDefault()).contains("data:")) {
|
||||
return "data";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseStreamCodec(final String input) {
|
||||
if (input != null) {
|
||||
return input.toLowerCase(Locale.getDefault())
|
||||
.replaceAll("\\(.*\\)", "")
|
||||
.replaceAll("video:", "")
|
||||
.replaceAll("audio:", "")
|
||||
.replaceAll("data:", "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseStreamFullCodec(final String input) {
|
||||
if (input != null) {
|
||||
return input.toLowerCase(Locale.getDefault())
|
||||
.replaceAll("video:", "")
|
||||
.replaceAll("audio:", "")
|
||||
.replaceAll("data:", "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Long parseStreamIndex(final String input) {
|
||||
String substring = substring(input, "Stream #0:", ":", Collections.singletonList("tream #0"));
|
||||
if (substring != null) {
|
||||
|
||||
// DISCARD PARANTHESIS
|
||||
return toLongObject(substring
|
||||
.replace(":", "")
|
||||
.replaceAll("\\(.*\\)", ""));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Long parseDuration(final String duration) {
|
||||
if (duration == null || duration.equals("N/A")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final Date calculated = DURATION_FORMAT.parse(duration);
|
||||
|
||||
long secondsPartInMilliseconds = calculated.getTime() - REFERENCE_DURATION.getTime();
|
||||
|
||||
int index = duration.indexOf('.');
|
||||
if (index > -1) {
|
||||
Long centiSeconds = toLong(duration.substring(index + 1));
|
||||
secondsPartInMilliseconds += 10 * centiSeconds;
|
||||
}
|
||||
|
||||
return secondsPartInMilliseconds;
|
||||
|
||||
} catch (final ParseException e) {
|
||||
Log.d(Config.TAG, String.format("Parsing duration: %s failed.", duration), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Long parseStartTime(final String startTime) {
|
||||
if (startTime == null || startTime.equals("N/A")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
BigDecimal bigDecimal = new BigDecimal(startTime);
|
||||
bigDecimal = bigDecimal.setScale(3, BigDecimal.ROUND_CEILING).multiply(new BigDecimal(1000));
|
||||
return bigDecimal.longValue();
|
||||
} catch (NumberFormatException e) {
|
||||
Log.d(Config.TAG, String.format("Parsing startTime: %s failed.", startTime), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String substring(final String string, final String start, final String end, final List<String> ignoredTokens) {
|
||||
String extractedSubstring = null;
|
||||
|
||||
if (string != null) {
|
||||
int formatStart = string.indexOf(start);
|
||||
if (formatStart > -1) {
|
||||
|
||||
int formatEnd = string.indexOf(end, formatStart + start.length());
|
||||
if (formatEnd > -1) {
|
||||
extractedSubstring = string.substring(formatStart + start.length(), formatEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((ignoredTokens != null) && (extractedSubstring != null)) {
|
||||
for (String token : ignoredTokens) {
|
||||
extractedSubstring = extractedSubstring.replaceAll(token, "");
|
||||
}
|
||||
}
|
||||
|
||||
return (extractedSubstring == null) ? null : extractedSubstring.trim();
|
||||
}
|
||||
|
||||
public static String substring(final String string, final String start, final List<String> ignoredTokens) {
|
||||
String extractedSubstring = null;
|
||||
|
||||
if (string != null) {
|
||||
int formatStart = string.indexOf(start);
|
||||
if (formatStart > -1) {
|
||||
extractedSubstring = string.substring(formatStart + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ((ignoredTokens != null) && (extractedSubstring != null)) {
|
||||
for (String token : ignoredTokens) {
|
||||
extractedSubstring = extractedSubstring.replaceAll(token, "");
|
||||
}
|
||||
}
|
||||
|
||||
return (extractedSubstring == null) ? null : extractedSubstring.trim();
|
||||
}
|
||||
|
||||
public static int index(final String string, String substring, int startIndex, int n) {
|
||||
int count = 1;
|
||||
|
||||
while (count <= n) {
|
||||
startIndex = string.indexOf(substring, startIndex + substring.length());
|
||||
count++;
|
||||
}
|
||||
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
public static int count(final String string, String substring) {
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
|
||||
do {
|
||||
index = string.indexOf(substring, index);
|
||||
if (index >= 0) {
|
||||
count++;
|
||||
index = index + substring.length();
|
||||
}
|
||||
} while (index >= 0);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static <K> K safeGet(final K[] array, final int index) {
|
||||
if (array == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final int size = array.length;
|
||||
if (size > index) {
|
||||
return array[index];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (final ArrayIndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static long toLong(final String value) {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static Long toLongObject(final String value) {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
return new MediaInformation(jsonObject, arrayList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-48
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -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);
|
||||
|
||||
}
|
||||
-34
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
+2
-1
@@ -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
-29
@@ -1,14 +1,30 @@
|
||||
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
|
||||
else
|
||||
FFMPEG_INCLUDES := $(MY_LOCAL_PATH)/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
|
||||
endif
|
||||
|
||||
MY_ARM_MODE := arm
|
||||
MY_ARM_NEON := false
|
||||
MY_PATH := ../app/src/main/cpp
|
||||
LOCAL_PATH := $(MY_LOCAL_PATH)/../app/src/main/cpp
|
||||
|
||||
# DEFINE ARCH FLAGS
|
||||
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
|
||||
MY_ARCH_FLAGS := ARM_V7A
|
||||
MY_ARM_NEON := true
|
||||
MY_ARM_NEON := false
|
||||
endif
|
||||
ifeq ($(TARGET_ARCH_ABI), arm64-v8a)
|
||||
MY_ARCH_FLAGS := ARM64_V8A
|
||||
@@ -21,40 +37,69 @@ 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
|
||||
$(call import-module, cpu-features)
|
||||
|
||||
ifeq ($(TARGET_PLATFORM),android-16)
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/mobileffprobe.c $(MY_PATH)/android_lts_support.c $(MY_PATH)/mobileffmpeg_exception.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffprobe.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
|
||||
MY_SRC_FILES := mobileffmpeg.c mobileffprobe.c android_lts_support.c mobileffmpeg_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
|
||||
else ifeq ($(TARGET_PLATFORM),android-17)
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/mobileffprobe.c $(MY_PATH)/android_lts_support.c $(MY_PATH)/mobileffmpeg_exception.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffprobe.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
|
||||
MY_SRC_FILES := mobileffmpeg.c mobileffprobe.c android_lts_support.c mobileffmpeg_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
|
||||
else
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/mobileffprobe.c $(MY_PATH)/mobileffmpeg_exception.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffprobe.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
|
||||
MY_SRC_FILES := mobileffmpeg.c mobileffprobe.c mobileffmpeg_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
|
||||
endif
|
||||
LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
|
||||
LOCAL_LDLIBS := -llog -lz -landroid
|
||||
LOCAL_SHARED_LIBRARIES := c++_shared libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
|
||||
LOCAL_ARM_NEON := ${MY_ARM_NEON}
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
MY_SRC_FILES += saf_wrapper.c
|
||||
|
||||
$(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
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
ifeq ($(MY_ARMV7_NEON), true)
|
||||
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/neon/cpu-features/lib
|
||||
else
|
||||
LOCAL_PATH := $(call my-dir)/../../../prebuilt/android-$(TARGET_ARCH)/cpu-features/lib
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_ARM_MODE := $(MY_ARM_MODE)
|
||||
LOCAL_MODULE := cpu-features
|
||||
LOCAL_SRC_FILES := libndk_compat.a
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include/ndk_compat
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
include ':app', ':test-app'
|
||||
include ':mobile-ffmpeg'
|
||||
project(':mobile-ffmpeg').projectDir = new File('..')
|
||||
rootProject.name='Mobile FFmpeg (android)'
|
||||
@@ -9,13 +9,13 @@ android {
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 30
|
||||
defaultConfig {
|
||||
applicationId "com.arthenica.mobileffmpeg.test"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 29
|
||||
versionCode 240432
|
||||
versionName "4.3.2"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 160440
|
||||
versionName "4.4.LTS"
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
@@ -51,8 +51,8 @@ android.applicationVariants.all { variant ->
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// implementation project(':app')
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:4.3.2'
|
||||
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'
|
||||
|
||||
+20
-15
@@ -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("Creating audio sample 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":
|
||||
|
||||
+5
-3
@@ -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) {
|
||||
|
||||
+273
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+25
-19
@@ -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 {
|
||||
@@ -63,7 +64,7 @@ public class HttpsTabFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
getInfo();
|
||||
runGetMediaInformation();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,16 +99,16 @@ public class HttpsTabFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
public void getInfo() {
|
||||
public void runGetMediaInformation() {
|
||||
clearLog();
|
||||
|
||||
String testUrl = urlText.getText().toString();
|
||||
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,12 @@ 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;
|
||||
@@ -61,7 +63,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
Exceptions.registerRootPackage("com.arthenica");
|
||||
}
|
||||
|
||||
protected static final Queue<Callable> actionQueue = new ConcurrentLinkedQueue<>();
|
||||
protected static final Queue<Callable<Object>> actionQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
protected static final Handler handler = new Handler();
|
||||
|
||||
@@ -69,7 +71,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Callable callable;
|
||||
Callable<Object> callable;
|
||||
|
||||
do {
|
||||
callable = actionQueue.poll();
|
||||
@@ -77,7 +79,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
try {
|
||||
callable.call();
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(TAG, String.format("Running UI action received error.%s", Exceptions.getStackTraceString(e)));
|
||||
android.util.Log.e(TAG, String.format("Running UI action received error.%s.", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
} while (callable != null);
|
||||
@@ -128,11 +130,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
registerAppFont();
|
||||
Log.d(TAG, "Application fonts registered.");
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, String.format("Font registration failed.%s", Exceptions.getStackTraceString(e)));
|
||||
Log.e(TAG, String.format("Font registration failed.%s.", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
|
||||
Log.d(TAG, "Listing supported camera ids.");
|
||||
listSupportedCameraIds();
|
||||
Config.ignoreSignal(Signal.SIGXCPU);
|
||||
Config.setLogLevel(Level.AV_LOG_DEBUG);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,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);
|
||||
}
|
||||
|
||||
@@ -157,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);
|
||||
}
|
||||
|
||||
@@ -171,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 = 9;
|
||||
|
||||
private final Context context;
|
||||
|
||||
@@ -59,6 +59,12 @@ public class PagerAdapter extends FragmentPagerAdapter {
|
||||
case 6: {
|
||||
return PipeTabFragment.newInstance();
|
||||
}
|
||||
case 7: {
|
||||
return ConcurrentExecutionTabFragment.newInstance();
|
||||
}
|
||||
case 8: {
|
||||
return SafTabFragment.newInstance();
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
@@ -94,6 +100,12 @@ public class PagerAdapter extends FragmentPagerAdapter {
|
||||
case 6: {
|
||||
return context.getString(R.string.pipe_tab);
|
||||
}
|
||||
case 7: {
|
||||
return context.getString(R.string.concurrent_tab);
|
||||
}
|
||||
case 8: {
|
||||
return context.getString(R.string.saf_tab);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
|
||||
+12
-12
@@ -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,6 @@ 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;
|
||||
@@ -108,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() {
|
||||
@@ -155,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() {
|
||||
@@ -174,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;
|
||||
@@ -189,7 +189,7 @@ public class PipeTabFragment extends Fragment {
|
||||
startAsyncCatImageProcess(image3File.getAbsolutePath(), pipe3);
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, String.format("Create video failed %s", Exceptions.getStackTraceString(e)));
|
||||
Log.e(TAG, String.format("Create video failed %s.", Exceptions.getStackTraceString(e)));
|
||||
Popup.show(requireContext(), "Create video failed");
|
||||
}
|
||||
}
|
||||
@@ -226,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() {
|
||||
@@ -251,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,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() {
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.mobileffmpeg.test;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.TextView;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.ExecuteCallback;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
import com.arthenica.mobileffmpeg.FFprobe;
|
||||
import com.arthenica.mobileffmpeg.LogCallback;
|
||||
import com.arthenica.mobileffmpeg.LogMessage;
|
||||
import com.arthenica.mobileffmpeg.Statistics;
|
||||
import com.arthenica.mobileffmpeg.StatisticsCallback;
|
||||
import com.arthenica.mobileffmpeg.util.DialogUtil;
|
||||
import com.arthenica.mobileffmpeg.util.ResourcesUtil;
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS;
|
||||
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
|
||||
|
||||
public class SafTabFragment extends Fragment {
|
||||
|
||||
private TextView outputText;
|
||||
private Uri inUri;
|
||||
private Uri outUri;
|
||||
private static final int REQUEST_SAF_FFPROBE = 11;
|
||||
private static final int REQUEST_SAF_FFMPEG = 12;
|
||||
|
||||
private boolean backFromIntent = false;
|
||||
|
||||
private VideoView videoView;
|
||||
private AlertDialog progressDialog;
|
||||
private Statistics statistics;
|
||||
|
||||
private Button runFFmpegButton;
|
||||
private Button runFFprobeButton;
|
||||
|
||||
public SafTabFragment() {
|
||||
super(R.layout.fragment_saf_tab);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
runFFmpegButton = view.findViewById(R.id.runFFmpegButton);
|
||||
runFFmpegButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.setType("video/*")
|
||||
.putExtra(Intent.EXTRA_TITLE, "video.mp4")
|
||||
.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
startActivityForResult(intent, REQUEST_SAF_FFMPEG);
|
||||
}
|
||||
});
|
||||
|
||||
runFFprobeButton = view.findViewById(R.id.runFFprobeButton);
|
||||
runFFprobeButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
|
||||
.setType("*/*")
|
||||
.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*", "audio/*"})
|
||||
.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
startActivityForResult(intent, REQUEST_SAF_FFPROBE);
|
||||
}
|
||||
});
|
||||
|
||||
outputText = view.findViewById(R.id.outputText);
|
||||
outputText.setMovementMethod(new ScrollingMovementMethod());
|
||||
|
||||
videoView = view.findViewById(R.id.videoPlayerFrame);
|
||||
progressDialog = DialogUtil.createProgressDialog(requireContext(), "Encoding video");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setActive();
|
||||
}
|
||||
|
||||
static SafTabFragment newInstance() {
|
||||
return new SafTabFragment();
|
||||
}
|
||||
|
||||
private void enableLogCallback() {
|
||||
Config.enableLogCallback(new LogCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final LogMessage message) {
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
appendLog(message.getText());
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void runFFprobe() {
|
||||
videoView.setVisibility(View.GONE);
|
||||
outputText.setVisibility(View.VISIBLE);
|
||||
|
||||
clearLog();
|
||||
|
||||
final String ffprobeCommand = "-hide_banner -print_format json -show_format -show_streams " + Config.getSafParameterForRead(getContext(), inUri);
|
||||
|
||||
Log.d(TAG, "Testing FFprobe COMMAND synchronously.");
|
||||
|
||||
Log.d(TAG, String.format("FFprobe process started with arguments\n\'%s\'", ffprobeCommand));
|
||||
|
||||
int result = FFprobe.execute(ffprobeCommand);
|
||||
|
||||
Log.d(TAG, String.format("FFprobe process exited with rc %d", result));
|
||||
|
||||
if (result != 0) {
|
||||
Popup.show(requireContext(), "Command failed. Please check output for the details.");
|
||||
}
|
||||
inUri = null;
|
||||
}
|
||||
|
||||
private void setActive() {
|
||||
if (backFromIntent) {
|
||||
backFromIntent = false;
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
Popup.show(requireContext(), "SAF is only available for Android 4.4 and above.");
|
||||
runFFprobeButton.setEnabled(false);
|
||||
runFFmpegButton.setEnabled(false);
|
||||
outputText.setEnabled(false);
|
||||
Log.i(TAG, "SAF Tab Dectivated");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "SAF Tab Activated");
|
||||
enableLogCallback();
|
||||
enableStatisticsCallback();
|
||||
Popup.show(requireContext(), getString(R.string.saf_test_tooltip_text));
|
||||
}
|
||||
|
||||
private void appendLog(final String logMessage) {
|
||||
outputText.append(logMessage);
|
||||
}
|
||||
|
||||
private void clearLog() {
|
||||
outputText.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
backFromIntent = true;
|
||||
if (requestCode == REQUEST_SAF_FFPROBE && resultCode == RESULT_OK && data != null) {
|
||||
inUri = data.getData();
|
||||
MainActivity.handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runFFprobe();
|
||||
}
|
||||
});
|
||||
} else if (requestCode == REQUEST_SAF_FFMPEG && resultCode == MainActivity.RESULT_OK && data != null) {
|
||||
outUri = data.getData();
|
||||
MainActivity.handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
encodeVideo();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
private String getCodec(String videoPath) {
|
||||
String extension = "mp4";
|
||||
int pos = videoPath.lastIndexOf('.');
|
||||
if (pos >= 0)
|
||||
extension = videoPath.substring(pos+1);
|
||||
|
||||
switch (extension) {
|
||||
case "webm":
|
||||
return "vp8";
|
||||
case "mkv":
|
||||
return "aom";
|
||||
case "ogv":
|
||||
return "theora";
|
||||
case "mov":
|
||||
return "hap";
|
||||
case "mp4":
|
||||
default:
|
||||
return "mpeg4";
|
||||
}
|
||||
}
|
||||
|
||||
private String getCustomOptions(String videoCodec) {
|
||||
switch (videoCodec) {
|
||||
case "x265":
|
||||
return "-crf 28 -preset fast ";
|
||||
case "vp8":
|
||||
return "-b:v 1M -crf 10 ";
|
||||
case "vp9":
|
||||
return "-b:v 2M ";
|
||||
case "aom":
|
||||
return "-crf 30 -strict experimental ";
|
||||
case "theora":
|
||||
return "-qscale:v 7 ";
|
||||
case "hap":
|
||||
return "-format hap_q ";
|
||||
default:
|
||||
|
||||
// kvazaar, mpeg4, x264, xvid
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void enableStatisticsCallback() {
|
||||
Config.enableStatisticsCallback(new StatisticsCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final Statistics newStatistics) {
|
||||
MainActivity.addUIAction(new Callable<Object>() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
statistics = newStatistics;
|
||||
updateProgressDialog();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
throw new AndroidRuntimeException("I am test exception thrown by test application");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void encodeVideo() {
|
||||
final File image1File = new File(requireContext().getCacheDir(), "colosseum.jpg");
|
||||
final File image2File = new File(requireContext().getCacheDir(), "pyramid.jpg");
|
||||
final File image3File = new File(requireContext().getCacheDir(), "tajmahal.jpg");
|
||||
final String videoPath = Config.getSafParameterForWrite(requireContext(), outUri);
|
||||
|
||||
try {
|
||||
// IF VIDEO IS PLAYING STOP PLAYBACK
|
||||
videoView.stopPlayback();
|
||||
|
||||
videoView.setVisibility(View.GONE);
|
||||
outputText.setVisibility(View.VISIBLE);
|
||||
|
||||
String selectedCodec = getCodec(videoPath);
|
||||
Log.d(TAG, String.format("Testing VIDEO encoding with '%s' codec", selectedCodec));
|
||||
|
||||
showProgressDialog();
|
||||
|
||||
ResourcesUtil.resourceToFile(getResources(), R.drawable.colosseum, image1File);
|
||||
ResourcesUtil.resourceToFile(getResources(), R.drawable.pyramid, image2File);
|
||||
ResourcesUtil.resourceToFile(getResources(), R.drawable.tajmahal, image3File);
|
||||
|
||||
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoPath, selectedCodec, getCustomOptions(selectedCodec));
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
|
||||
|
||||
long executionId = FFmpeg.executeAsync(ffmpegCommand, new ExecuteCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final long executionId, final int returnCode) {
|
||||
Log.d(TAG, String.format("FFmpeg process exited with rc %d.", returnCode));
|
||||
|
||||
Log.d(TAG, "FFmpeg process output:");
|
||||
|
||||
Config.printLastCommandOutput(Log.INFO);
|
||||
|
||||
hideProgressDialog();
|
||||
|
||||
MainActivity.addUIAction(new Callable<Object>() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
Log.d(TAG, "Encode completed successfully; playing video.");
|
||||
playVideo(outUri, new MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mediaPlayer) {
|
||||
videoView.setVisibility(View.GONE);
|
||||
outputText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Popup.show(requireContext(), "Encode failed. Please check log for the details.");
|
||||
Log.d(TAG, String.format("Encode failed with rc=%d.", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Log.d(TAG, String.format("Async FFmpeg process started with executionId %d.", executionId));
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, String.format("Encode video failed %s.", Exceptions.getStackTraceString(e)));
|
||||
Popup.show(requireContext(), "Encode video failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void playVideo(Uri videoUri, MediaPlayer.OnCompletionListener onCompletionListener) {
|
||||
videoView.setVisibility(View.VISIBLE);
|
||||
outputText.setVisibility(View.GONE);
|
||||
|
||||
MediaController mediaController = new MediaController(requireContext());
|
||||
mediaController.setAnchorView(videoView);
|
||||
videoView.setVideoURI(videoUri);
|
||||
videoView.setMediaController(mediaController);
|
||||
videoView.requestFocus();
|
||||
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
videoView.setBackgroundColor(0x00000000);
|
||||
}
|
||||
});
|
||||
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
videoView.stopPlayback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
videoView.setOnCompletionListener(onCompletionListener);
|
||||
videoView.start();
|
||||
}
|
||||
|
||||
private void showProgressDialog() {
|
||||
|
||||
// CLEAN STATISTICS
|
||||
statistics = null;
|
||||
Config.resetStatistics();
|
||||
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
private void updateProgressDialog() {
|
||||
if (statistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int timeInMilliseconds = this.statistics.getTime();
|
||||
if (timeInMilliseconds > 0) {
|
||||
int totalVideoDuration = 9000;
|
||||
|
||||
String completePercentage = new BigDecimal(timeInMilliseconds).multiply(new BigDecimal(100)).divide(new BigDecimal(totalVideoDuration), 0, BigDecimal.ROUND_HALF_UP).toString();
|
||||
|
||||
TextView textView = progressDialog.findViewById(R.id.progressDialogText);
|
||||
if (textView != null) {
|
||||
textView.setText(String.format("Encoding video: %% %s.", completePercentage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void hideProgressDialog() {
|
||||
progressDialog.dismiss();
|
||||
|
||||
MainActivity.addUIAction(new Callable<Object>() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
progressDialog = DialogUtil.createProgressDialog(requireContext(), "Encoding video");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+32
-32
@@ -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,6 @@ 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;
|
||||
@@ -66,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);
|
||||
@@ -158,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.");
|
||||
|
||||
@@ -182,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() {
|
||||
@@ -206,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("Async FFmpeg process started with executionId %d.", executionId));
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, String.format("Burn subtitles failed %s", Exceptions.getStackTraceString(e)));
|
||||
Log.e(TAG, String.format("Burn subtitles failed %s.", Exceptions.getStackTraceString(e)));
|
||||
Popup.show(requireContext(), "Burn subtitles failed");
|
||||
}
|
||||
}
|
||||
@@ -275,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() {
|
||||
@@ -288,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();
|
||||
@@ -307,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";
|
||||
|
||||
}
|
||||
+20
-20
@@ -37,7 +37,7 @@ 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;
|
||||
@@ -132,17 +132,17 @@ public class VidStabTabFragment extends Fragment {
|
||||
|
||||
final String ffmpegCommand = Video.generateShakingVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath());
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", ffmpegCommand));
|
||||
|
||||
MainActivity.executeAsync(new SingleExecuteCallback() {
|
||||
MainActivity.executeAsync(new ExecuteCallback() {
|
||||
|
||||
@Override
|
||||
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() {
|
||||
MainActivity.addUIAction(new Callable<Object>() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
@@ -154,28 +154,28 @@ public class VidStabTabFragment extends Fragment {
|
||||
|
||||
showStabilizeProgressDialog();
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", analyzeVideoCommand));
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'.", analyzeVideoCommand));
|
||||
|
||||
MainActivity.executeAsync(new SingleExecuteCallback() {
|
||||
MainActivity.executeAsync(new ExecuteCallback() {
|
||||
|
||||
@Override
|
||||
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));
|
||||
|
||||
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());
|
||||
|
||||
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) {
|
||||
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() {
|
||||
@@ -185,7 +185,7 @@ public class VidStabTabFragment extends Fragment {
|
||||
playStabilizedVideo();
|
||||
} else {
|
||||
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
|
||||
Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
|
||||
Log.d(TAG, String.format("Stabilize video failed with rc=%d.", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -197,14 +197,14 @@ public class VidStabTabFragment extends Fragment {
|
||||
} else {
|
||||
hideStabilizeProgressDialog();
|
||||
Popup.show(requireContext(), "Stabilize video failed. Please check log for the details.");
|
||||
Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
|
||||
Log.d(TAG, String.format("Stabilize video failed with rc=%d.", returnCode));
|
||||
}
|
||||
}
|
||||
}, analyzeVideoCommand);
|
||||
|
||||
} else {
|
||||
Popup.show(requireContext(), "Create video failed. Please check log for the details.");
|
||||
Log.d(TAG, String.format("Create failed with rc=%d", returnCode));
|
||||
Log.d(TAG, String.format("Create failed with rc=%d.", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -214,7 +214,7 @@ public class VidStabTabFragment extends Fragment {
|
||||
}, ffmpegCommand);
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, String.format("Stabilize video failed %s", Exceptions.getStackTraceString(e)));
|
||||
Log.e(TAG, String.format("Stabilize video failed %s.", Exceptions.getStackTraceString(e)));
|
||||
Popup.show(requireContext(), "Stabilize video failed");
|
||||
}
|
||||
}
|
||||
@@ -282,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() {
|
||||
|
||||
+21
-18
@@ -38,6 +38,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.ExecuteCallback;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
import com.arthenica.mobileffmpeg.LogCallback;
|
||||
import com.arthenica.mobileffmpeg.LogMessage;
|
||||
@@ -45,7 +46,6 @@ 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;
|
||||
@@ -122,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() {
|
||||
@@ -174,13 +174,13 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
|
||||
|
||||
final String ffmpegCommand = Video.generateEncodeVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath(), getSelectedVideoCodec(), getCustomOptions());
|
||||
|
||||
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:");
|
||||
|
||||
@@ -188,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() {
|
||||
@@ -197,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, String.format("Encode video failed %s", Exceptions.getStackTraceString(e)));
|
||||
Log.e(TAG, String.format("Encode video failed %s.", Exceptions.getStackTraceString(e)));
|
||||
Popup.show(requireContext(), "Encode video failed");
|
||||
}
|
||||
}
|
||||
@@ -223,13 +225,13 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
|
||||
|
||||
final String ffmpegCommand = String.format("-hide_banner -i %s %s", imageFile.getAbsolutePath(), outputFile.getAbsolutePath());
|
||||
|
||||
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, String.format("Encode webp failed %s", Exceptions.getStackTraceString(e)));
|
||||
Log.e(TAG, String.format("Encode webp failed %s.", Exceptions.getStackTraceString(e)));
|
||||
Popup.show(requireContext(), "Encode webp failed");
|
||||
}
|
||||
}
|
||||
@@ -259,8 +261,6 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
|
||||
}
|
||||
|
||||
public String getSelectedVideoCodec() {
|
||||
|
||||
// NOTE THAT MPEG4 CODEC IS ASSIGNED HERE
|
||||
String videoCodec = selectedCodec;
|
||||
|
||||
// VIDEO CODEC SPINNER HAS BASIC NAMES, FFMPEG NEEDS LONGER AND EXACT CODEC NAMES.
|
||||
@@ -269,6 +269,9 @@ public class VideoTabFragment extends Fragment implements AdapterView.OnItemSele
|
||||
case "x264":
|
||||
videoCodec = "libx264";
|
||||
break;
|
||||
case "openh264":
|
||||
videoCodec = "libopenh264";
|
||||
break;
|
||||
case "x265":
|
||||
videoCodec = "libx265";
|
||||
break;
|
||||
@@ -351,7 +354,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() {
|
||||
@@ -376,7 +379,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,7 +387,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() {
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
tools:context=".SafTabFragment">
|
||||
|
||||
<Button
|
||||
android:id="@+id/runFFmpegButton"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/command_run_ffmpeg_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:targetApi="jelly_bean" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/runFFprobeButton"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/command_run_ffprobe_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:targetApi="jelly_bean" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/outputText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="6dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/rounded_output_frame"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="bottom"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
android:scrollbars="vertical"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp"
|
||||
android:typeface="sans"
|
||||
tools:targetApi="jelly_bean" />
|
||||
|
||||
<VideoView
|
||||
android:visibility="gone"
|
||||
android:id="@+id/videoPlayerFrame"
|
||||
android:layout_width="wrap_content"
|
||||
android:background="@drawable/rounded_video_frame"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_margin="20dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -45,7 +45,7 @@
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_encode_button_text"
|
||||
android:text="@string/encode_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<item>mp3 (libshine)</item>
|
||||
<item>vorbis</item>
|
||||
<item>opus</item>
|
||||
<item>amr</item>
|
||||
<item>amr-nb</item>
|
||||
<item>amr-wb</item>
|
||||
<item>ilbc</item>
|
||||
<item>soxr</item>
|
||||
<item>speex</item>
|
||||
|
||||
@@ -7,15 +7,23 @@
|
||||
<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="saf_tab">SAF</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,11 @@
|
||||
<resources>
|
||||
<string name="command_test_tooltip_text">Enter an FFmpeg command without \'ffmpeg\' at the beginning and click one of the RUN buttons</string>
|
||||
<string name="video_test_tooltip_text">Select a video codec and press ENCODE button</string>
|
||||
<string name="https_test_tooltip_text">Enter the https url of a media file and click the button</string>
|
||||
<string name="audio_test_tooltip_text">Select an audio codec and press ENCODE button</string>
|
||||
<string name="subtitle_test_tooltip_text">Click the button to burn subtitles. Created video will play inside the frame below</string>
|
||||
<string name="vidstab_test_tooltip_text">Click the button to stabilize video. Original video will play above and stabilized video will play below</string>
|
||||
<string name="pipe_test_tooltip_text">Click the button to create a video using pipe redirection. Created video will play inside the frame below</string>
|
||||
<string name="concurrent_execution_test_tooltip_text">Use ENCODE and CANCEL buttons to start/stop multiple execution</string>
|
||||
<string name="saf_test_tooltip_text">Use system file picker to test scoped storage extension</string>
|
||||
</resources>
|
||||
@@ -3,6 +3,7 @@
|
||||
<string-array name="video_codec">
|
||||
<item>mpeg4</item>
|
||||
<item>x264</item>
|
||||
<item>openh264</item>
|
||||
<item>x265</item>
|
||||
<item>xvid</item>
|
||||
<item>vp8</item>
|
||||
|
||||
@@ -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
|
||||
|
||||
+53
-298
@@ -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
|
||||
@@ -51,19 +37,21 @@ get_library_name() {
|
||||
30) echo "sdl" ;;
|
||||
31) echo "tesseract" ;;
|
||||
32) echo "openh264" ;;
|
||||
33) echo "giflib" ;;
|
||||
34) echo "jpeg" ;;
|
||||
35) echo "libogg" ;;
|
||||
36) echo "libpng" ;;
|
||||
37) echo "libuuid" ;;
|
||||
38) echo "nettle" ;;
|
||||
39) echo "tiff" ;;
|
||||
40) echo "expat" ;;
|
||||
41) echo "libsndfile" ;;
|
||||
42) echo "leptonica" ;;
|
||||
43) echo "libsamplerate" ;;
|
||||
44) echo "android-zlib" ;;
|
||||
45) echo "android-media-codec" ;;
|
||||
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
|
||||
}
|
||||
|
||||
@@ -153,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"
|
||||
@@ -839,159 +823,60 @@ 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/296494a4011f58f32adc54304a2654627558c59a/x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2"
|
||||
GPL_LIB_FILE="x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2"
|
||||
GPL_LIB_ORIG_DIR="x264-296494a4011f58f32adc54304a2654627558c59a"
|
||||
GPL_LIB_DEST_DIR="x264"
|
||||
arm64-v8a)
|
||||
echo "arm64-v8a"
|
||||
;;
|
||||
x265)
|
||||
GPL_LIB_URL="https://bitbucket.org/multicoreware/x265/downloads/x265_3.3.tar.gz"
|
||||
GPL_LIB_FILE="x265_3.3.tar.gz"
|
||||
GPL_LIB_ORIG_DIR="x265_3.3"
|
||||
GPL_LIB_DEST_DIR="x265"
|
||||
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"
|
||||
;;
|
||||
rubberband)
|
||||
GPL_LIB_URL="https://breakfastquay.com/files/releases/rubberband-1.8.2.tar.bz2"
|
||||
GPL_LIB_FILE="rubberband-1.8.2.tar.bz2"
|
||||
GPL_LIB_ORIG_DIR="rubberband-1.8.2"
|
||||
GPL_LIB_DEST_DIR="rubberband"
|
||||
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() {
|
||||
@@ -1033,30 +918,6 @@ set_toolchain_clang_paths() {
|
||||
prepare_inline_sed
|
||||
}
|
||||
|
||||
build_cpufeatures() {
|
||||
|
||||
# CLEAN OLD BUILD
|
||||
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.a 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -f ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.so 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
echo -e "\nINFO: Building cpu-features for ${ARCH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
set_toolchain_clang_paths "cpu-features"
|
||||
|
||||
BUILD_HOST=$(get_build_host)
|
||||
export CFLAGS=$(get_cflags "cpu-features")
|
||||
export CXXFLAGS=$(get_cxxflags "cpu-features")
|
||||
export LDFLAGS=$(get_ldflags "cpu-features")
|
||||
|
||||
# THEN BUILD FOR THIS ABI
|
||||
$(get_clang_target_host)-clang -c ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.c -o ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
|
||||
${BUILD_HOST}-ar rcs ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.a ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o 1>>${BASEDIR}/build.log 2>&1
|
||||
$(get_clang_target_host)-clang -shared ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/cpu-features.o -o ${ANDROID_NDK_ROOT}/sources/android/cpufeatures/libcpufeatures.so 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
create_cpufeatures_package_config
|
||||
}
|
||||
|
||||
build_android_lts_support() {
|
||||
|
||||
# CLEAN OLD BUILD
|
||||
@@ -1078,109 +939,3 @@ build_android_lts_support() {
|
||||
$(get_clang_target_host)-clang ${CFLAGS} -Wno-unused-command-line-argument -c ${BASEDIR}/android/app/src/main/cpp/android_lts_support.c -o ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o ${LDFLAGS} 1>>${BASEDIR}/build.log 2>&1
|
||||
${BUILD_HOST}-ar rcs ${BASEDIR}/android/app/src/main/cpp/libandroidltssupport.a ${BASEDIR}/android/app/src/main/cpp/android_lts_support.o 1>>${BASEDIR}/build.log 2>&1
|
||||
}
|
||||
|
||||
autoreconf_library() {
|
||||
echo -e "\nDEBUG: Running full autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY FULL RECONF
|
||||
(autoreconf --force --install)
|
||||
|
||||
local EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Full autoreconf failed. Running full autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY FULL RECONF WITH m4
|
||||
(autoreconf --force --install -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Full autoreconf with include failed. Running autoreconf without force for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY RECONF WITHOUT FORCE
|
||||
(autoreconf --install)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Autoreconf without force failed. Running autoreconf without force with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY RECONF WITHOUT FORCE WITH m4
|
||||
(autoreconf --install -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Autoreconf without force with include failed. Running default autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY DEFAULT RECONF
|
||||
(autoreconf)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Default autoreconf failed. Running default autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY DEFAULT RECONF WITH m4
|
||||
(autoreconf -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
library_is_installed() {
|
||||
local INSTALL_PATH=$1
|
||||
local LIB_NAME=$2
|
||||
|
||||
echo -e "DEBUG: Checking if ${LIB_NAME} is already built and installed at ${INSTALL_PATH}/${LIB_NAME}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME} ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME} directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/lib ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/lib directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/include ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/include directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
local HEADER_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/include | wc -l)
|
||||
local LIB_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/lib | wc -l)
|
||||
|
||||
if [[ ${HEADER_COUNT} -eq 0 ]]; then
|
||||
echo -e "DEBUG: No headers found under ${INSTALL_PATH}/${LIB_NAME}/include\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ${LIB_COUNT} -eq 0 ]]; then
|
||||
echo -e "DEBUG: No libraries found under ${INSTALL_PATH}/${LIB_NAME}/lib\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "INFO: ${LIB_NAME} library is already built and installed\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
echo 0
|
||||
}
|
||||
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
|
||||
echo -e "(*) ANDROID_NDK_ROOT not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${ARCH} ]]; then
|
||||
echo -e "(*) ARCH not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${API} ]]; then
|
||||
echo -e "(*) API not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ENABLE COMMON FUNCTIONS
|
||||
. ${BASEDIR}/build/android-common.sh
|
||||
|
||||
LIB_NAME="cpu-features"
|
||||
set_toolchain_clang_paths ${LIB_NAME}
|
||||
|
||||
# DOWNLOAD LIBRARY
|
||||
DOWNLOAD_RESULT=$(download_library_source ${LIB_NAME})
|
||||
if [[ ${DOWNLOAD_RESULT} -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
$(android_ndk_cmake) -DBUILD_PIC=ON || exit 1
|
||||
make -C $(android_build_dir) install || exit 1
|
||||
|
||||
create_cpufeatures_package_config
|
||||
+33
-17
@@ -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..46}
|
||||
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)"
|
||||
@@ -236,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)"
|
||||
@@ -285,14 +287,14 @@ do
|
||||
;;
|
||||
android-media-codec)
|
||||
CONFIGURE_POSTFIX+=" --enable-mediacodec"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
|
||||
# THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT
|
||||
# NOTE THAT IDS MUST BE +1 OF THE INDEX VALUE
|
||||
if [[ ${library} -eq 31 ]]; then
|
||||
CONFIGURE_POSTFIX+=" --disable-sdl2"
|
||||
elif [[ ${library} -eq 45 ]]; then
|
||||
elif [[ ${library} -eq 46 ]]; then
|
||||
CONFIGURE_POSTFIX+=" --disable-zlib"
|
||||
fi
|
||||
fi
|
||||
@@ -322,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
|
||||
@@ -341,6 +349,13 @@ export LDFLAGS="${LDFLAGS}"
|
||||
# USE HIGHER LIMITS FOR FFMPEG LINKING
|
||||
ulimit -n 2048 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
########################### CUSTOMIZATIONS #######################
|
||||
|
||||
# 1. Use thread local log level
|
||||
${SED_INLINE} 's/static int av_log_level/__thread int av_log_level/g' ${BASEDIR}/src/${LIB_NAME}/libavutil/log.c 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
###################################################################
|
||||
|
||||
./configure \
|
||||
--cross-prefix="${BUILD_HOST}-" \
|
||||
--sysroot="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot" \
|
||||
@@ -351,8 +366,9 @@ ulimit -n 2048 1>>${BASEDIR}/build.log 2>&1
|
||||
--cpu="${TARGET_CPU}" \
|
||||
--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 \
|
||||
|
||||
@@ -63,6 +63,6 @@ export LIBPNG_LIBS="-L${BASEDIR}/prebuilt/android-$(get_target_build)/libpng/lib
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# CREATE PACKAGE CONFIG MANUALLY
|
||||
create_freetype_package_config "23.1.17"
|
||||
create_freetype_package_config "23.2.17"
|
||||
|
||||
make install || exit 1
|
||||
|
||||
@@ -36,14 +36,10 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
|
||||
|
||||
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
./autogen.sh || exit 1
|
||||
|
||||
make distclean 2>/dev/null 1>/dev/null
|
||||
|
||||
# RECONFIGURE IF REQUESTED
|
||||
if [[ ${RECONF_kvazaar} -eq 1 ]]; then
|
||||
autoreconf_library ${LIB_NAME}
|
||||
fi
|
||||
# ALWAYS RECONFIGURE
|
||||
autoreconf_library ${LIB_NAME}
|
||||
|
||||
# LINKING WITH ANDROID LTS SUPPORT LIBRARY IS NECESSARY FOR API < 18
|
||||
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${API} < 18 ]]; then
|
||||
|
||||
@@ -38,6 +38,9 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
make distclean 2>/dev/null 1>/dev/null
|
||||
|
||||
# DISABLE building of examples manually
|
||||
${SED_INLINE} 's/examples tests//g' ${BASEDIR}/src/${LIB_NAME}/Makefile*
|
||||
|
||||
# RECONFIGURE IF REQUESTED
|
||||
if [[ ${RECONF_libsamplerate} -eq 1 ]]; then
|
||||
autoreconf_library ${LIB_NAME}
|
||||
@@ -53,9 +56,6 @@ fi
|
||||
--disable-fast-install \
|
||||
--host=${BUILD_HOST} || exit 1
|
||||
|
||||
# DISABLE building of examples manually
|
||||
${SED_INLINE} 's/examples tests//g' ${BASEDIR}/src/${LIB_NAME}/Makefile
|
||||
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# MANUALLY COPY PKG-CONFIG FILES
|
||||
|
||||
@@ -60,6 +60,6 @@ PKG_CONFIG= ./configure \
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# CREATE PACKAGE CONFIG MANUALLY
|
||||
create_libvorbis_package_config "1.3.6"
|
||||
create_libvorbis_package_config "1.3.7"
|
||||
|
||||
make install || exit 1
|
||||
|
||||
@@ -30,7 +30,7 @@ set_toolchain_clang_paths ${LIB_NAME}
|
||||
# PREPARING FLAGS
|
||||
export CFLAGS="$(get_cflags ${LIB_NAME}) -I${ANDROID_NDK_ROOT}/sources/android/cpufeatures"
|
||||
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
|
||||
export LDFLAGS="$(get_ldflags ${LIB_NAME}) -L${ANDROID_NDK_ROOT}/sources/android/cpufeatures -lcpufeatures"
|
||||
export LDFLAGS="$(get_ldflags ${LIB_NAME})"
|
||||
|
||||
# RECOVER configure.sh
|
||||
rm -f ${BASEDIR}/src/${LIB_NAME}/build/make/configure.sh
|
||||
|
||||
@@ -55,7 +55,6 @@ fi
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# MANUALLY COPY PKG-CONFIG FILES
|
||||
cp amrwb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
|
||||
cp amrnb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
|
||||
|
||||
make install || exit 1
|
||||
|
||||
@@ -35,17 +35,16 @@ LDFLAGS=$(get_ldflags ${LIB_NAME})
|
||||
|
||||
case ${ARCH} in
|
||||
arm-v7a-neon)
|
||||
ASM_ARCH=arm
|
||||
# Enabling NEON causes undefined symbol error for WelsCopy8x8_neon
|
||||
# CFLAGS+=" -DHAVE_NEON"
|
||||
ASM_ARCH=arm
|
||||
CFLAGS+=" -DHAVE_NEON -DANDROID_NDK"
|
||||
;;
|
||||
arm64-v8a)
|
||||
ASM_ARCH=arm64
|
||||
CFLAGS+=" -DHAVE_NEON_AARCH64"
|
||||
CFLAGS+=" -DHAVE_NEON_AARCH64 -DANDROID_NDK"
|
||||
;;
|
||||
x86*)
|
||||
ASM_ARCH=x86
|
||||
CFLAGS+=" -DHAVE_AVX2"
|
||||
CFLAGS+=" -DHAVE_AVX2 -DANDROID_NDK"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -57,6 +56,10 @@ make clean 2>/dev/null 1>/dev/null
|
||||
git checkout ${BASEDIR}/src/${LIB_NAME}/build 1>>${BASEDIR}/build.log 2>&1
|
||||
git checkout ${BASEDIR}/src/${LIB_NAME}/codec 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# comment out the piece that compiles cpu-features into libopenh264.a
|
||||
${SED_INLINE} 's/^COMMON_OBJS +=/# COMMON_OBJS +=/' ${BASEDIR}/src/${LIB_NAME}/build/platform-android.mk
|
||||
${SED_INLINE} 's/^COMMON_CFLAGS +=/# COMMON_CFLAGS +=/' ${BASEDIR}/src/${LIB_NAME}/build/platform-android.mk
|
||||
|
||||
make -j$(get_cpu_count) \
|
||||
ARCH="$(get_toolchain_arch)" \
|
||||
CC="$CC" \
|
||||
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -z ${ANDROID_NDK_ROOT} ]]; then
|
||||
echo -e "(*) ANDROID_NDK_ROOT not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${ARCH} ]]; then
|
||||
echo -e "(*) ARCH not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${API} ]]; then
|
||||
echo -e "(*) API not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${BASEDIR} ]]; then
|
||||
echo -e "(*) BASEDIR not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ENABLE COMMON FUNCTIONS
|
||||
. ${BASEDIR}/build/android-common.sh
|
||||
|
||||
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
|
||||
LIB_NAME="vo-amrwbenc"
|
||||
set_toolchain_clang_paths ${LIB_NAME}
|
||||
|
||||
# PREPARING FLAGS
|
||||
BUILD_HOST=$(get_build_host)
|
||||
export CFLAGS=$(get_cflags ${LIB_NAME})
|
||||
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
|
||||
export LDFLAGS=$(get_ldflags ${LIB_NAME})
|
||||
|
||||
ARCH_OPTIONS=""
|
||||
case ${ARCH} in
|
||||
arm-v7a-neon)
|
||||
ARCH_OPTIONS="--enable-armv7neon"
|
||||
;;
|
||||
esac
|
||||
|
||||
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
make distclean 2>/dev/null 1>/dev/null
|
||||
|
||||
# RECONFIGURE IF REQUESTED
|
||||
if [[ ${RECONF_vo_amrwbenc} -eq 1 ]]; then
|
||||
autoreconf_library ${LIB_NAME}
|
||||
fi
|
||||
|
||||
./configure \
|
||||
--prefix=${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME} \
|
||||
--with-pic \
|
||||
--with-sysroot=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot \
|
||||
${ARCH_OPTIONS} \
|
||||
--enable-static \
|
||||
--disable-shared \
|
||||
--disable-fast-install \
|
||||
--disable-maintainer-mode \
|
||||
--host=${BUILD_HOST} || exit 1
|
||||
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# MANUALLY COPY PKG-CONFIG FILES
|
||||
cp ./*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
|
||||
|
||||
make install || exit 1
|
||||
@@ -94,6 +94,6 @@ cmake -Wno-dev \
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# CREATE PACKAGE CONFIG MANUALLY
|
||||
create_x265_package_config "3.3"
|
||||
create_x265_package_config "3.4"
|
||||
|
||||
make install || exit 1
|
||||
|
||||
Executable
+452
@@ -0,0 +1,452 @@
|
||||
#!/bin/bash
|
||||
|
||||
export MOBILE_FFMPEG_TMPDIR="${BASEDIR}/.tmp"
|
||||
|
||||
autoreconf_library() {
|
||||
echo -e "\nDEBUG: Running full autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY FULL RECONF
|
||||
(autoreconf --force --install)
|
||||
|
||||
local EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Full autoreconf failed. Running full autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY FULL RECONF WITH m4
|
||||
(autoreconf --force --install -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Full autoreconf with include failed. Running autoreconf without force for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY RECONF WITHOUT FORCE
|
||||
(autoreconf --install)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Autoreconf without force failed. Running autoreconf without force with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY RECONF WITHOUT FORCE WITH m4
|
||||
(autoreconf --install -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Autoreconf without force with include failed. Running default autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY DEFAULT RECONF
|
||||
(autoreconf)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Default autoreconf failed. Running default autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY DEFAULT RECONF WITH m4
|
||||
(autoreconf -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 1. <repo url>
|
||||
# 2. <local folder path>
|
||||
# 3. <commit id>
|
||||
#
|
||||
clone_git_repository_with_commit_id() {
|
||||
local RC
|
||||
|
||||
(mkdir -p $2 1>>${BASEDIR}/build.log 2>&1)
|
||||
|
||||
RC=$?
|
||||
|
||||
if [ ${RC} -ne 0 ]; then
|
||||
echo -e "\nDEBUG: Failed to create local directory $2\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${RC}
|
||||
return
|
||||
fi
|
||||
|
||||
(git clone $1 $2 --depth 1 1>>${BASEDIR}/build.log 2>&1)
|
||||
|
||||
RC=$?
|
||||
|
||||
if [ ${RC} -ne 0 ]; then
|
||||
echo -e "\nDEBUG: Failed to clone $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${RC}
|
||||
return
|
||||
fi
|
||||
|
||||
cd $2 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
RC=$?
|
||||
|
||||
if [ ${RC} -ne 0 ]; then
|
||||
echo -e "\nDEBUG: Failed to cd into $2\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${RC}
|
||||
return
|
||||
fi
|
||||
|
||||
(git fetch --depth 1 origin $3 1>>${BASEDIR}/build.log 2>&1)
|
||||
|
||||
RC=$?
|
||||
|
||||
if [ ${RC} -ne 0 ]; then
|
||||
echo -e "\nDEBUG: Failed to fetch commit id $3 from $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf $2 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${RC}
|
||||
return
|
||||
fi
|
||||
|
||||
(git checkout $3 1>>${BASEDIR}/build.log 2>&1)
|
||||
|
||||
RC=$?
|
||||
|
||||
if [ ${RC} -ne 0 ]; then
|
||||
echo -e "\nDEBUG: Failed to checkout commit id $3 from $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${RC}
|
||||
return
|
||||
fi
|
||||
|
||||
echo ${RC}
|
||||
}
|
||||
|
||||
#
|
||||
# 1. <repo url>
|
||||
# 2. <tag name>
|
||||
# 3. <local folder path>
|
||||
#
|
||||
clone_git_repository_with_tag() {
|
||||
local RC
|
||||
|
||||
(mkdir -p $3 1>>${BASEDIR}/build.log 2>&1)
|
||||
|
||||
RC=$?
|
||||
|
||||
if [ ${RC} -ne 0 ]; then
|
||||
echo -e "\nDEBUG: Failed to create local directory $3\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf $3 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${RC}
|
||||
return
|
||||
fi
|
||||
|
||||
(git clone --depth 1 --branch $2 $1 $3 1>>${BASEDIR}/build.log 2>&1)
|
||||
|
||||
RC=$?
|
||||
|
||||
if [ ${RC} -ne 0 ]; then
|
||||
echo -e "\nDEBUG: Failed to clone $1 -> $2\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf $3 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${RC}
|
||||
return
|
||||
fi
|
||||
|
||||
echo ${RC}
|
||||
}
|
||||
|
||||
#
|
||||
# 1. <url>
|
||||
# 2. <local file name>
|
||||
# 3. <on error action>
|
||||
#
|
||||
download() {
|
||||
if [ ! -d "${MOBILE_FFMPEG_TMPDIR}" ]; then
|
||||
mkdir -p "${MOBILE_FFMPEG_TMPDIR}"
|
||||
fi
|
||||
|
||||
(curl --fail --location $1 -o ${MOBILE_FFMPEG_TMPDIR}/$2 1>>${BASEDIR}/build.log 2>&1)
|
||||
|
||||
local RC=$?
|
||||
|
||||
if [ ${RC} -eq 0 ]; then
|
||||
echo -e "\nDEBUG: Downloaded $1 to ${MOBILE_FFMPEG_TMPDIR}/$2\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
else
|
||||
rm -f ${MOBILE_FFMPEG_TMPDIR}/$2 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
echo -e -n "\nINFO: Failed to download $1 to ${MOBILE_FFMPEG_TMPDIR}/$2, rc=${RC}. " 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ "$3" == "exit" ]; then
|
||||
echo -e "DEBUG: Build will now exit.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
exit 1
|
||||
else
|
||||
echo -e "DEBUG: Build will continue.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ${RC}
|
||||
}
|
||||
|
||||
download_library_source() {
|
||||
local LIB_REPO_URL=""
|
||||
local LIB_NAME="$1"
|
||||
local LIB_LOCAL_PATH=${BASEDIR}/src/${LIB_NAME}
|
||||
local SOURCE_ID=""
|
||||
local LIBRARY_RC=""
|
||||
local DOWNLOAD_RC=""
|
||||
local SOURCE_TYPE=""
|
||||
|
||||
echo -e "\nDEBUG: Downloading library source: $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
case $1 in
|
||||
cpu-features)
|
||||
LIB_REPO_URL="https://github.com/tanersener/cpu_features"
|
||||
SOURCE_ID="v0.4.1.1" # TAG
|
||||
SOURCE_TYPE="TAG"
|
||||
;;
|
||||
ffmpeg)
|
||||
LIB_REPO_URL="https://github.com/tanersener/FFmpeg"
|
||||
SOURCE_ID="d222da435e63a2665b85c0305ad2cf8a07b1af6d" # COMMIT -> v4.4-dev-416
|
||||
SOURCE_TYPE="COMMIT"
|
||||
;;
|
||||
esac
|
||||
|
||||
LIBRARY_RC=$(library_is_downloaded "${LIB_NAME}")
|
||||
|
||||
if [ ${LIBRARY_RC} -eq 0 ]; then
|
||||
echo -e "INFO: $1 already downloaded. Source folder found at ${LIB_LOCAL_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 0
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ${SOURCE_TYPE} == "TAG" ]; then
|
||||
DOWNLOAD_RC=$(clone_git_repository_with_tag "${LIB_REPO_URL}" "${SOURCE_ID}" "${LIB_LOCAL_PATH}")
|
||||
else
|
||||
DOWNLOAD_RC=$(clone_git_repository_with_commit_id "${LIB_REPO_URL}" "${LIB_LOCAL_PATH}" "${SOURCE_ID}")
|
||||
fi
|
||||
|
||||
if [ ${DOWNLOAD_RC} -ne 0 ]; then
|
||||
echo -e "INFO: Downloading library $1 failed. Can not get library from ${LIB_REPO_URL}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${DOWNLOAD_RC}
|
||||
else
|
||||
echo -e "DEBUG: $1 library downloaded\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
download_gpl_library_source() {
|
||||
local GPL_LIB_URL=""
|
||||
local GPL_LIB_FILE=""
|
||||
local GPL_LIB_ORIG_DIR=""
|
||||
local GPL_LIB_DEST_DIR="$1"
|
||||
local GPL_LIB_SOURCE_PATH="${BASEDIR}/src/${GPL_LIB_DEST_DIR}"
|
||||
local LIBRARY_RC=""
|
||||
local DOWNLOAD_RC=""
|
||||
|
||||
echo -e "\nDEBUG: Downloading GPL library source: $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
case $1 in
|
||||
libvidstab)
|
||||
GPL_LIB_URL="https://github.com/georgmartius/vid.stab/archive/v1.1.0.tar.gz"
|
||||
GPL_LIB_FILE="v1.1.0.tar.gz"
|
||||
GPL_LIB_ORIG_DIR="vid.stab-1.1.0"
|
||||
;;
|
||||
x264)
|
||||
GPL_LIB_URL="https://code.videolan.org/videolan/x264/-/archive/cde9a93319bea766a92e306d69059c76de970190/x264-cde9a93319bea766a92e306d69059c76de970190.tar.bz2"
|
||||
GPL_LIB_FILE="x264-cde9a93319bea766a92e306d69059c76de970190.tar.bz2"
|
||||
GPL_LIB_ORIG_DIR="x264-cde9a93319bea766a92e306d69059c76de970190"
|
||||
;;
|
||||
x265)
|
||||
GPL_LIB_URL="https://github.com/videolan/x265/archive/3.4.tar.gz"
|
||||
GPL_LIB_FILE="x265_3.4.tar.gz"
|
||||
GPL_LIB_ORIG_DIR="x265-3.4"
|
||||
;;
|
||||
xvidcore)
|
||||
GPL_LIB_URL="https://downloads.xvid.com/downloads/xvidcore-1.3.7.tar.gz"
|
||||
GPL_LIB_FILE="xvidcore-1.3.7.tar.gz"
|
||||
GPL_LIB_ORIG_DIR="xvidcore"
|
||||
;;
|
||||
rubberband)
|
||||
GPL_LIB_URL="https://breakfastquay.com/files/releases/rubberband-1.8.2.tar.bz2"
|
||||
GPL_LIB_FILE="rubberband-1.8.2.tar.bz2"
|
||||
GPL_LIB_ORIG_DIR="rubberband-1.8.2"
|
||||
;;
|
||||
esac
|
||||
|
||||
LIBRARY_RC=$(library_is_downloaded "${GPL_LIB_DEST_DIR}")
|
||||
|
||||
if [ ${LIBRARY_RC} -eq 0 ]; then
|
||||
echo -e "INFO: $1 already downloaded. Source folder found at ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 0
|
||||
return
|
||||
fi
|
||||
|
||||
local GPL_LIB_PACKAGE_PATH="${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_FILE}"
|
||||
|
||||
echo -e "DEBUG: $1 source not found. Checking if library package ${GPL_LIB_FILE} is downloaded at ${GPL_LIB_PACKAGE_PATH} \n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ ! -f "${GPL_LIB_PACKAGE_PATH}" ]; then
|
||||
echo -e "DEBUG: $1 library package not found. Downloading from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
DOWNLOAD_RC=$(download "${GPL_LIB_URL}" "${GPL_LIB_FILE}")
|
||||
|
||||
if [ ${DOWNLOAD_RC} -ne 0 ]; then
|
||||
echo -e "INFO: Downloading GPL library $1 failed. Can not get library package from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${DOWNLOAD_RC}
|
||||
return
|
||||
else
|
||||
echo -e "DEBUG: $1 library package downloaded\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
else
|
||||
echo -e "DEBUG: $1 library package already downloaded\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
|
||||
local EXTRACT_COMMAND=""
|
||||
|
||||
if [[ ${GPL_LIB_FILE} == *bz2 ]]; then
|
||||
EXTRACT_COMMAND="tar jxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
|
||||
else
|
||||
EXTRACT_COMMAND="tar zxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
|
||||
fi
|
||||
|
||||
echo -e "DEBUG: Extracting library package ${GPL_LIB_FILE} inside ${MOBILE_FFMPEG_TMPDIR}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
${EXTRACT_COMMAND} 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
local EXTRACT_RC=$?
|
||||
|
||||
if [ ${EXTRACT_RC} -ne 0 ]; then
|
||||
echo -e "\nINFO: Downloading GPL library $1 failed. Extract for library package ${GPL_LIB_FILE} completed with rc=${EXTRACT_RC}. Deleting failed files.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -f ${GPL_LIB_PACKAGE_PATH} 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${EXTRACT_RC}
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "DEBUG: Extract completed. Copying library source to ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
COPY_COMMAND="cp -r ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} ${GPL_LIB_SOURCE_PATH}"
|
||||
|
||||
${COPY_COMMAND} 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
local COPY_RC=$?
|
||||
|
||||
if [ ${COPY_RC} -eq 0 ]; then
|
||||
echo -e "DEBUG: Downloading GPL library source $1 completed successfully\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
else
|
||||
echo -e "\nINFO: Downloading GPL library $1 failed. Copying library source to ${GPL_LIB_SOURCE_PATH} completed with rc=${COPY_RC}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf ${GPL_LIB_SOURCE_PATH} 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${COPY_RC}
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
get_cpu_count() {
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
echo $(sysctl -n hw.physicalcpu)
|
||||
else
|
||||
echo $(nproc)
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# 1. <lib name>
|
||||
#
|
||||
library_is_downloaded() {
|
||||
local LOCAL_PATH
|
||||
local LIB_NAME=$1
|
||||
local FILE_COUNT
|
||||
local REDOWNLOAD_VARIABLE
|
||||
REDOWNLOAD_VARIABLE=$(echo "REDOWNLOAD_$1" | sed "s/\-/\_/g")
|
||||
|
||||
LOCAL_PATH=${BASEDIR}/src/${LIB_NAME}
|
||||
|
||||
echo -e "DEBUG: Checking if ${LIB_NAME} is already downloaded at ${LOCAL_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ ! -d ${LOCAL_PATH} ]; then
|
||||
echo -e "DEBUG: ${LOCAL_PATH} directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
FILE_COUNT=$(ls -l ${LOCAL_PATH} | wc -l)
|
||||
|
||||
if [[ ${FILE_COUNT} -eq 0 ]]; then
|
||||
echo -e "DEBUG: No files found under ${LOCAL_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ${REDOWNLOAD_VARIABLE} -eq 1 ]]; then
|
||||
echo -e "INFO: ${LIB_NAME} library already downloaded but re-download requested\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf ${LOCAL_PATH} 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
else
|
||||
echo -e "INFO: ${LIB_NAME} library already downloaded\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
library_is_installed() {
|
||||
local INSTALL_PATH=$1
|
||||
local LIB_NAME=$2
|
||||
local HEADER_COUNT
|
||||
local LIB_COUNT
|
||||
|
||||
echo -e "DEBUG: Checking if ${LIB_NAME} is already built and installed at ${INSTALL_PATH}/${LIB_NAME}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME} ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME} directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/lib ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/lib directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/include ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/include directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
HEADER_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/include | wc -l)
|
||||
LIB_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/lib | wc -l)
|
||||
|
||||
if [[ ${HEADER_COUNT} -eq 0 ]]; then
|
||||
echo -e "DEBUG: No headers found under ${INSTALL_PATH}/${LIB_NAME}/include\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ${LIB_COUNT} -eq 0 ]]; then
|
||||
echo -e "DEBUG: No libraries found under ${INSTALL_PATH}/${LIB_NAME}/lib\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "INFO: ${LIB_NAME} library is already built and installed\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
echo 0
|
||||
}
|
||||
|
||||
prepare_inline_sed() {
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
export SED_INLINE="sed -i .tmp"
|
||||
else
|
||||
export SED_INLINE="sed -i"
|
||||
fi
|
||||
}
|
||||
|
||||
to_capital_case() {
|
||||
echo "$(echo ${1:0:1} | tr '[a-z]' '[A-Z]')${1:1}"
|
||||
}
|
||||
+27
-290
@@ -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
|
||||
@@ -50,19 +36,19 @@ get_library_name() {
|
||||
29) echo "sdl" ;;
|
||||
30) echo "tesseract" ;;
|
||||
31) echo "openh264" ;;
|
||||
32) echo "giflib" ;;
|
||||
33) echo "jpeg" ;;
|
||||
34) echo "libogg" ;;
|
||||
35) echo "libpng" ;;
|
||||
36) echo "nettle" ;;
|
||||
37) echo "tiff" ;;
|
||||
38) echo "expat" ;;
|
||||
39) echo "libsndfile" ;;
|
||||
40) echo "leptonica" ;;
|
||||
41) echo "libsamplerate" ;;
|
||||
42) echo "ios-zlib" ;;
|
||||
43) echo "ios-audiotoolbox" ;;
|
||||
44) echo "ios-coreimage" ;;
|
||||
32) echo "vo-amrwbenc" ;;
|
||||
33) echo "giflib" ;;
|
||||
34) echo "jpeg" ;;
|
||||
35) echo "libogg" ;;
|
||||
36) echo "libpng" ;;
|
||||
37) echo "nettle" ;;
|
||||
38) echo "tiff" ;;
|
||||
39) echo "expat" ;;
|
||||
40) echo "libsndfile" ;;
|
||||
41) echo "leptonica" ;;
|
||||
42) echo "libsamplerate" ;;
|
||||
43) echo "ios-zlib" ;;
|
||||
44) echo "ios-audiotoolbox" ;;
|
||||
45) echo "ios-bzip2" ;;
|
||||
46) echo "ios-videotoolbox" ;;
|
||||
47) echo "ios-avfoundation" ;;
|
||||
@@ -84,21 +70,17 @@ get_package_config_file_name() {
|
||||
26) echo "aom" ;;
|
||||
27) echo "libchromaprint" ;;
|
||||
29) echo "sdl2" ;;
|
||||
33) echo "libjpeg" ;;
|
||||
34) echo "ogg" ;;
|
||||
37) echo "libtiff-4" ;;
|
||||
39) echo "sndfile" ;;
|
||||
40) echo "lept" ;;
|
||||
41) echo "samplerate" ;;
|
||||
49) echo "uuid" ;;
|
||||
34) echo "libjpeg" ;;
|
||||
35) echo "ogg" ;;
|
||||
38) echo "libtiff-4" ;;
|
||||
40) echo "sndfile" ;;
|
||||
41) echo "lept" ;;
|
||||
42) echo "samplerate" ;;
|
||||
50) echo "uuid" ;;
|
||||
*) echo $(get_library_name $1)
|
||||
esac
|
||||
}
|
||||
|
||||
to_capital_case() {
|
||||
echo "$(echo ${1:0:1} | tr '[a-z]' '[A-Z]')${1:1}"
|
||||
}
|
||||
|
||||
get_static_archive_name() {
|
||||
case $1 in
|
||||
5) echo "libmp3lame.a" ;;
|
||||
@@ -111,12 +93,12 @@ get_static_archive_name() {
|
||||
28) echo "libtwolame.a" ;;
|
||||
29) echo "libSDL2.a" ;;
|
||||
30) echo "libtesseract.a" ;;
|
||||
32) echo "libgif.a" ;;
|
||||
34) echo "libogg.a" ;;
|
||||
35) echo "libpng.a" ;;
|
||||
39) echo "libsndfile.a" ;;
|
||||
40) echo "liblept.a" ;;
|
||||
41) echo "libsamplerate.a" ;;
|
||||
33) echo "libgif.a" ;;
|
||||
35) echo "libogg.a" ;;
|
||||
36) echo "libpng.a" ;;
|
||||
40) echo "libsndfile.a" ;;
|
||||
41) echo "liblept.a" ;;
|
||||
42) echo "libsamplerate.a" ;;
|
||||
*) echo lib$(get_library_name $1).a
|
||||
esac
|
||||
}
|
||||
@@ -874,145 +856,6 @@ 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"
|
||||
;;
|
||||
x264)
|
||||
GPL_LIB_URL="https://code.videolan.org/videolan/x264/-/archive/296494a4011f58f32adc54304a2654627558c59a/x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2"
|
||||
GPL_LIB_FILE="x264-296494a4011f58f32adc54304a2654627558c59a.tar.bz2"
|
||||
GPL_LIB_ORIG_DIR="x264-296494a4011f58f32adc54304a2654627558c59a"
|
||||
GPL_LIB_DEST_DIR="x264"
|
||||
;;
|
||||
x265)
|
||||
GPL_LIB_URL="https://bitbucket.org/multicoreware/x265/downloads/x265_3.3.tar.gz"
|
||||
GPL_LIB_FILE="x265_3.3.tar.gz"
|
||||
GPL_LIB_ORIG_DIR="x265_3.3"
|
||||
GPL_LIB_DEST_DIR="x265"
|
||||
;;
|
||||
xvidcore)
|
||||
GPL_LIB_URL="https://downloads.xvid.com/downloads/xvidcore-1.3.7.tar.gz"
|
||||
GPL_LIB_FILE="xvidcore-1.3.7.tar.gz"
|
||||
GPL_LIB_ORIG_DIR="xvidcore"
|
||||
GPL_LIB_DEST_DIR="xvidcore"
|
||||
;;
|
||||
rubberband)
|
||||
GPL_LIB_URL="https://breakfastquay.com/files/releases/rubberband-1.8.2.tar.bz2"
|
||||
GPL_LIB_FILE="rubberband-1.8.2.tar.bz2"
|
||||
GPL_LIB_ORIG_DIR="rubberband-1.8.2"
|
||||
GPL_LIB_DEST_DIR="rubberband"
|
||||
;;
|
||||
esac
|
||||
|
||||
local GPL_LIB_SOURCE_PATH="${BASEDIR}/src/${GPL_LIB_DEST_DIR}"
|
||||
|
||||
if [ -d "${GPL_LIB_SOURCE_PATH}" ]; then
|
||||
echo -e "INFO: $1 already downloaded. Source folder found at ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 0
|
||||
return
|
||||
fi
|
||||
|
||||
local GPL_LIB_PACKAGE_PATH="${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_FILE}"
|
||||
|
||||
echo -e "DEBUG: $1 source not found. Checking if library package ${GPL_LIB_FILE} is downloaded at ${GPL_LIB_PACKAGE_PATH} \n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ ! -f "${GPL_LIB_PACKAGE_PATH}" ]; then
|
||||
echo -e "DEBUG: $1 library package not found. Downloading from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
local DOWNLOAD_RC=$(download "${GPL_LIB_URL}" "${GPL_LIB_FILE}")
|
||||
|
||||
if [ ${DOWNLOAD_RC} -ne 0 ]; then
|
||||
echo -e "INFO: Downloading GPL library $1 failed. Can not get library package from ${GPL_LIB_URL}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${DOWNLOAD_RC}
|
||||
return
|
||||
else
|
||||
echo -e "DEBUG: $1 library package downloaded\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
else
|
||||
echo -e "DEBUG: $1 library package already downloaded\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
|
||||
local EXTRACT_COMMAND=""
|
||||
|
||||
if [[ ${GPL_LIB_FILE} == *bz2 ]]; then
|
||||
EXTRACT_COMMAND="tar jxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
|
||||
else
|
||||
EXTRACT_COMMAND="tar zxf ${GPL_LIB_PACKAGE_PATH} --directory ${MOBILE_FFMPEG_TMPDIR}"
|
||||
fi
|
||||
|
||||
echo -e "DEBUG: Extracting library package ${GPL_LIB_FILE} inside ${MOBILE_FFMPEG_TMPDIR}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
${EXTRACT_COMMAND} 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
local EXTRACT_RC=$?
|
||||
|
||||
if [ ${EXTRACT_RC} -ne 0 ]; then
|
||||
echo -e "\nINFO: Downloading GPL library $1 failed. Extract for library package ${GPL_LIB_FILE} completed with rc=${EXTRACT_RC}. Deleting failed files.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -f ${GPL_LIB_PACKAGE_PATH} 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${EXTRACT_RC}
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "DEBUG: Extract completed. Copying library source to ${GPL_LIB_SOURCE_PATH}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
COPY_COMMAND="cp -r ${MOBILE_FFMPEG_TMPDIR}/${GPL_LIB_ORIG_DIR} ${GPL_LIB_SOURCE_PATH}"
|
||||
|
||||
${COPY_COMMAND} 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
local COPY_RC=$?
|
||||
|
||||
if [ ${COPY_RC} -eq 0 ]; then
|
||||
echo -e "DEBUG: Downloading GPL library source $1 completed successfully\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
else
|
||||
echo -e "\nINFO: Downloading GPL library $1 failed. Copying library source to ${GPL_LIB_SOURCE_PATH} completed with rc=${COPY_RC}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
rm -rf ${GPL_LIB_SOURCE_PATH} 1>>${BASEDIR}/build.log 2>&1
|
||||
echo ${COPY_RC}
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
set_toolchain_clang_paths() {
|
||||
if [ ! -f "${MOBILE_FFMPEG_TMPDIR}/gas-preprocessor.pl" ]; then
|
||||
DOWNLOAD_RESULT=$(download "https://github.com/libav/gas-preprocessor/raw/master/gas-preprocessor.pl" "gas-preprocessor.pl" "exit")
|
||||
@@ -1093,109 +936,3 @@ set_toolchain_clang_paths() {
|
||||
|
||||
prepare_inline_sed
|
||||
}
|
||||
|
||||
autoreconf_library() {
|
||||
echo -e "\nDEBUG: Running full autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY FULL RECONF
|
||||
(autoreconf --force --install)
|
||||
|
||||
local EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Full autoreconf failed. Running full autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY FULL RECONF WITH m4
|
||||
(autoreconf --force --install -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Full autoreconf with include failed. Running autoreconf without force for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY RECONF WITHOUT FORCE
|
||||
(autoreconf --install)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Autoreconf without force failed. Running autoreconf without force with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY RECONF WITHOUT FORCE WITH m4
|
||||
(autoreconf --install -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Autoreconf without force with include failed. Running default autoreconf for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY DEFAULT RECONF
|
||||
(autoreconf)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\nDEBUG: Default autoreconf failed. Running default autoreconf with include for $1\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# TRY DEFAULT RECONF WITH m4
|
||||
(autoreconf -I m4)
|
||||
|
||||
EXTRACT_RC=$?
|
||||
if [ ${EXTRACT_RC} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
library_is_installed() {
|
||||
local INSTALL_PATH=$1
|
||||
local LIB_NAME=$2
|
||||
|
||||
echo -e "DEBUG: Checking if ${LIB_NAME} is already built and installed at ${INSTALL_PATH}/${LIB_NAME}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME} ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME} directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/lib ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/lib directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -d ${INSTALL_PATH}/${LIB_NAME}/include ]; then
|
||||
echo -e "DEBUG: ${INSTALL_PATH}/${LIB_NAME}/include directory not found\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
local HEADER_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/include | wc -l)
|
||||
local LIB_COUNT=$(ls -l ${INSTALL_PATH}/${LIB_NAME}/lib | wc -l)
|
||||
|
||||
if [[ ${HEADER_COUNT} -eq 0 ]]; then
|
||||
echo -e "DEBUG: No headers found under ${INSTALL_PATH}/${LIB_NAME}/include\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ${LIB_COUNT} -eq 0 ]]; then
|
||||
echo -e "DEBUG: No libraries found under ${INSTALL_PATH}/${LIB_NAME}/lib\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "INFO: ${LIB_NAME} library is already built and installed\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
echo 0
|
||||
}
|
||||
+27
-32
@@ -94,16 +94,8 @@ case ${ARCH} in
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ${APPLE_TVOS_BUILD} -eq 1 ]]; then
|
||||
CONFIGURE_POSTFIX="--disable-avfoundation"
|
||||
LIBRARY_COUNT=49
|
||||
else
|
||||
CONFIGURE_POSTFIX=""
|
||||
LIBRARY_COUNT=50
|
||||
fi
|
||||
|
||||
library=1
|
||||
while [[ ${library} -le ${LIBRARY_COUNT} ]]
|
||||
while [[ ${library} -le 49 ]]
|
||||
do
|
||||
if [[ ${!library} -eq 1 ]]; then
|
||||
ENABLED_LIBRARY=$(get_library_name $((library - 1)))
|
||||
@@ -198,11 +190,8 @@ do
|
||||
;;
|
||||
opencore-amr)
|
||||
FFMPEG_CFLAGS+=" $(pkg-config --cflags opencore-amrnb)"
|
||||
FFMPEG_CFLAGS+=" $(pkg-config --cflags opencore-amrwb)"
|
||||
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static opencore-amrnb)"
|
||||
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static opencore-amrwb)"
|
||||
CONFIGURE_POSTFIX+=" --enable-libopencore-amrnb"
|
||||
CONFIGURE_POSTFIX+=" --enable-libopencore-amrwb"
|
||||
;;
|
||||
openh264)
|
||||
FFMPEG_CFLAGS+=" $(pkg-config --cflags openh264)"
|
||||
@@ -257,6 +246,11 @@ do
|
||||
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static twolame)"
|
||||
CONFIGURE_POSTFIX+=" --enable-libtwolame"
|
||||
;;
|
||||
vo-amrwbenc)
|
||||
FFMPEG_CFLAGS+=" $(pkg-config --cflags vo-amrwbenc)"
|
||||
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static vo-amrwbenc)"
|
||||
CONFIGURE_POSTFIX+=" --enable-libvo-amrwbenc"
|
||||
;;
|
||||
wavpack)
|
||||
FFMPEG_CFLAGS+=" $(pkg-config --cflags wavpack)"
|
||||
FFMPEG_LDFLAGS+=" $(pkg-config --libs --static wavpack)"
|
||||
@@ -312,9 +306,6 @@ do
|
||||
*-bzip2)
|
||||
CONFIGURE_POSTFIX+=" --enable-bzlib"
|
||||
;;
|
||||
*-coreimage)
|
||||
CONFIGURE_POSTFIX+=" --enable-coreimage"
|
||||
;;
|
||||
*-videotoolbox)
|
||||
CONFIGURE_POSTFIX+=" --enable-videotoolbox"
|
||||
;;
|
||||
@@ -330,14 +321,13 @@ do
|
||||
else
|
||||
|
||||
# THE FOLLOWING LIBRARIES SHOULD BE EXPLICITLY DISABLED TO PREVENT AUTODETECT
|
||||
# NOTE THAT IDS MUST BE +1 OF THE INDEX VALUE
|
||||
if [[ ${library} -eq 30 ]]; then
|
||||
CONFIGURE_POSTFIX+=" --disable-sdl2"
|
||||
elif [[ ${library} -eq 43 ]]; then
|
||||
CONFIGURE_POSTFIX+=" --disable-zlib"
|
||||
elif [[ ${library} -eq 44 ]]; then
|
||||
CONFIGURE_POSTFIX+=" --disable-audiotoolbox"
|
||||
CONFIGURE_POSTFIX+=" --disable-zlib"
|
||||
elif [[ ${library} -eq 45 ]]; then
|
||||
CONFIGURE_POSTFIX+=" --disable-coreimage"
|
||||
CONFIGURE_POSTFIX+=" --disable-audiotoolbox"
|
||||
elif [[ ${library} -eq 46 ]]; then
|
||||
CONFIGURE_POSTFIX+=" --disable-bzlib"
|
||||
elif [[ ${library} -eq 47 ]]; then
|
||||
@@ -366,7 +356,7 @@ fi
|
||||
if [[ -z ${MOBILE_FFMPEG_DEBUG} ]]; then
|
||||
DEBUG_OPTIONS="--disable-debug";
|
||||
else
|
||||
DEBUG_OPTIONS="--enable-debug";
|
||||
DEBUG_OPTIONS="--enable-debug --disable-stripping";
|
||||
fi
|
||||
|
||||
# CFLAGS PARTS
|
||||
@@ -396,27 +386,37 @@ export CFLAGS="${ARCH_CFLAGS} ${APP_CFLAGS} ${COMMON_CFLAGS} ${OPTIMIZATION_CFLA
|
||||
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
|
||||
export LDFLAGS="${ARCH_LDFLAGS}${FFMPEG_LDFLAGS} ${LINKED_LIBRARIES} ${COMMON_LDFLAGS} ${BITCODE_FLAGS} ${OPTIMIZATION_FLAGS}"
|
||||
|
||||
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
echo -n -e "\n${LIB_NAME}: "
|
||||
|
||||
# DOWNLOAD LIBRARY
|
||||
DOWNLOAD_RESULT=$(download_library_source ${LIB_NAME})
|
||||
if [[ ${DOWNLOAD_RESULT} -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then
|
||||
echo -e "INFO: Cleaning workspace for ${LIB_NAME}" 1>>${BASEDIR}/build.log 2>&1
|
||||
make distclean 2>/dev/null 1>/dev/null
|
||||
fi
|
||||
|
||||
# Workaround to prevent adding of -mdynamic-no-pic flag
|
||||
########################### CUSTOMIZATIONS #######################
|
||||
|
||||
# 1. Workaround to prevent adding of -mdynamic-no-pic flag
|
||||
${SED_INLINE} 's/check_cflags -mdynamic-no-pic && add_asflags -mdynamic-no-pic;/check_cflags -mdynamic-no-pic;/g' ./configure 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# Workaround for videotoolbox on mac catalyst
|
||||
# 2. Workaround for videotoolbox on mac catalyst
|
||||
if [ ${ARCH} == "x86-64-mac-catalyst" ]; then
|
||||
${SED_INLINE} 's/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/ \/\/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/g' ${BASEDIR}/src/${LIB_NAME}/libavcodec/videotoolbox.c
|
||||
else
|
||||
${SED_INLINE} 's/ \/\/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/ CFDictionarySetValue(buffer_attributes\, kCVPixelBufferOpenGLESCompatibilityKey/g' ${BASEDIR}/src/${LIB_NAME}/libavcodec/videotoolbox.c
|
||||
fi
|
||||
|
||||
# Workaround for not supported iOS/tvOS features
|
||||
git checkout 2e595085ef653f365af49e6a32f012cc6d9ee03c -- ${BASEDIR}/src/${LIB_NAME}/libavdevice/avfoundation.m 1>>${BASEDIR}/build.log 2>&1
|
||||
# 3. 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 \
|
||||
--sysroot=${SDK_PATH} \
|
||||
@@ -443,6 +443,7 @@ git checkout 2e595085ef653f365af49e6a32f012cc6d9ee03c -- ${BASEDIR}/src/${LIB_NA
|
||||
--disable-v4l2-m2m \
|
||||
--disable-outdev=v4l2 \
|
||||
--disable-outdev=fbdev \
|
||||
--disable-outdev=audiotoolbox \
|
||||
--disable-indev=v4l2 \
|
||||
--disable-indev=fbdev \
|
||||
--disable-openssl \
|
||||
@@ -474,12 +475,6 @@ git checkout 2e595085ef653f365af49e6a32f012cc6d9ee03c -- ${BASEDIR}/src/${LIB_NA
|
||||
--disable-vdpau \
|
||||
${CONFIGURE_POSTFIX} 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
# Workaround for issue #328
|
||||
echo "" >>ffbuild/config.mak
|
||||
echo "all:" >>ffbuild/config.mak
|
||||
echo "libswscale/aarch64/hscale.o: ${BASEDIR}/tools/make/ffmpeg/libswscale/aarch64/hscale.S" >>ffbuild/config.mak
|
||||
echo ' $(COMPILE_S)' >>ffbuild/config.mak
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "failed"
|
||||
exit 1
|
||||
|
||||
@@ -67,6 +67,6 @@ export LIBPNG_LIBS="-L${BASEDIR}/prebuilt/$(get_target_build_directory)/libpng/l
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# CREATE PACKAGE CONFIG MANUALLY
|
||||
create_freetype_package_config "23.1.17"
|
||||
create_freetype_package_config "23.2.17"
|
||||
|
||||
make install || exit 1
|
||||
|
||||
@@ -52,14 +52,10 @@ export PKG_CONFIG_LIBDIR="${INSTALL_PKG_CONFIG_DIR}"
|
||||
|
||||
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
./autogen.sh || exit 1
|
||||
|
||||
make distclean 2>/dev/null 1>/dev/null
|
||||
|
||||
# RECONFIGURE IF REQUESTED
|
||||
if [[ ${RECONF_kvazaar} -eq 1 ]]; then
|
||||
autoreconf_library ${LIB_NAME}
|
||||
fi
|
||||
# ALWAYS RECONFIGURE
|
||||
autoreconf_library ${LIB_NAME}
|
||||
|
||||
./configure \
|
||||
--prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \
|
||||
|
||||
@@ -42,6 +42,9 @@ cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
make distclean 2>/dev/null 1>/dev/null
|
||||
|
||||
# DISABLE building of examples manually
|
||||
${SED_INLINE} 's/examples tests//g' ${BASEDIR}/src/${LIB_NAME}/Makefile*
|
||||
|
||||
# RECONFIGURE IF REQUESTED
|
||||
if [[ ${RECONF_libsamplerate} -eq 1 ]]; then
|
||||
autoreconf_library ${LIB_NAME}
|
||||
@@ -57,9 +60,6 @@ fi
|
||||
--disable-fast-install \
|
||||
--host=${BUILD_HOST} || exit 1
|
||||
|
||||
# DISABLE building of examples manually
|
||||
${SED_INLINE} 's/examples tests//g' ${BASEDIR}/src/${LIB_NAME}/Makefile
|
||||
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# MANUALLY COPY PKG-CONFIG FILES
|
||||
|
||||
@@ -68,6 +68,6 @@ PKG_CONFIG= ./configure \
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# CREATE PACKAGE CONFIG MANUALLY
|
||||
create_libvorbis_package_config "1.3.6"
|
||||
create_libvorbis_package_config "1.3.7"
|
||||
|
||||
make install || exit 1
|
||||
|
||||
@@ -61,7 +61,7 @@ if [[ ${RECONF_mobile_ffmpeg} -eq 1 ]]; then
|
||||
fi
|
||||
|
||||
VIDEOTOOLBOX_SUPPORT_FLAG=""
|
||||
if [[ ${46} -eq 1 ]]; then
|
||||
if [[ ${47} -eq 1 ]]; then
|
||||
VIDEOTOOLBOX_SUPPORT_FLAG="--enable-videotoolbox"
|
||||
fi
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ if [[ ${RECONF_nettle} -eq 1 ]]; then
|
||||
autoreconf_library ${LIB_NAME}
|
||||
fi
|
||||
|
||||
${SED_INLINE} 's/exit(0)/return 0/g' ${BASEDIR}/src/${LIB_NAME}/configure
|
||||
${SED_INLINE} 's/exit (0)/return 0/g' ${BASEDIR}/src/${LIB_NAME}/configure
|
||||
|
||||
./configure \
|
||||
--prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \
|
||||
--enable-pic \
|
||||
|
||||
@@ -69,7 +69,6 @@ fi
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# MANUALLY COPY PKG-CONFIG FILES
|
||||
cp amrwb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
|
||||
cp amrnb/*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
|
||||
|
||||
make install || exit 1
|
||||
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -z ${ARCH} ]]; then
|
||||
echo -e "(*) ARCH not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${TARGET_SDK} ]]; then
|
||||
echo -e "(*) TARGET_SDK not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${SDK_PATH} ]]; then
|
||||
echo -e "(*) SDK_PATH not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${BASEDIR} ]]; then
|
||||
echo -e "(*) BASEDIR not defined\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ENABLE COMMON FUNCTIONS
|
||||
if [[ ${APPLE_TVOS_BUILD} -eq 1 ]]; then
|
||||
. ${BASEDIR}/build/tvos-common.sh
|
||||
else
|
||||
. ${BASEDIR}/build/ios-common.sh
|
||||
fi
|
||||
|
||||
# PREPARE PATHS & DEFINE ${INSTALL_PKG_CONFIG_DIR}
|
||||
LIB_NAME="vo-amrwbenc"
|
||||
set_toolchain_clang_paths ${LIB_NAME}
|
||||
|
||||
# PREPARING FLAGS
|
||||
BUILD_HOST=$(get_build_host)
|
||||
export CFLAGS=$(get_cflags ${LIB_NAME})
|
||||
export CXXFLAGS=$(get_cxxflags ${LIB_NAME})
|
||||
export LDFLAGS=$(get_ldflags ${LIB_NAME})
|
||||
|
||||
# OVERRIDE CXX
|
||||
case ${ARCH} in
|
||||
x86-64 | x86-64-mac-catalyst)
|
||||
export CXX="xcrun --sdk $(get_sdk_name) clang++ -arch x86_64"
|
||||
;;
|
||||
*)
|
||||
export CXX="xcrun --sdk $(get_sdk_name) clang++ -arch ${ARCH}"
|
||||
;;
|
||||
esac
|
||||
|
||||
cd ${BASEDIR}/src/${LIB_NAME} || exit 1
|
||||
|
||||
make distclean 2>/dev/null 1>/dev/null
|
||||
|
||||
# RECONFIGURE IF REQUESTED
|
||||
if [[ ${RECONF_vo_amrwbenc} -eq 1 ]]; then
|
||||
autoreconf_library ${LIB_NAME}
|
||||
fi
|
||||
|
||||
./configure \
|
||||
--prefix=${BASEDIR}/prebuilt/$(get_target_build_directory)/${LIB_NAME} \
|
||||
--with-pic \
|
||||
--with-sysroot=${SDK_PATH} \
|
||||
--enable-static \
|
||||
--disable-shared \
|
||||
--disable-fast-install \
|
||||
--disable-maintainer-mode \
|
||||
--host=${BUILD_HOST} || exit 1
|
||||
|
||||
make -j$(get_cpu_count) || exit 1
|
||||
|
||||
# MANUALLY COPY PKG-CONFIG FILES
|
||||
cp ./*.pc ${INSTALL_PKG_CONFIG_DIR} || exit 1
|
||||
|
||||
make install || exit 1
|
||||
@@ -73,18 +73,6 @@ cd cmake-build || exit 1
|
||||
${SED_INLINE} 's/win64/macho64 -DPREFIX/g' ${BASEDIR}/src/x265/source/cmake/CMakeASM_NASMInformation.cmake
|
||||
${SED_INLINE} 's/win/macho/g' ${BASEDIR}/src/x265/source/cmake/CMakeASM_NASMInformation.cmake
|
||||
|
||||
# fix pointer array assignments
|
||||
${SED_INLINE} '/addAvg/s/ p.pu/ *p.pu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/convert_p2s/s/ p.pu/ *p.pu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/pixelavg_pp/s/ p.pu/ *p.pu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/addAvg/s/ p.chroma/ *p.chroma/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/add_ps/s/ p.chroma/ *p.chroma/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/add_ps/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/blockfill_s/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/ssd_s/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/calcresidual/s/ p.cu/ *p.cu/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
${SED_INLINE} '/scale1D_128to64_neon/s/ p.scale/ *p.scale/g' ${BASEDIR}/src/x265/source/common/arm/asm-primitives.cpp
|
||||
|
||||
# fixing constant shift
|
||||
${SED_INLINE} 's/lsr 16/lsr #16/g' ${BASEDIR}/src/x265/source/common/arm/blockcopy8.S
|
||||
|
||||
|
||||
@@ -122,19 +122,16 @@ fi
|
||||
# FILTERING WHICH EXTERNAL LIBRARIES WILL BE BUILT
|
||||
# NOTE THAT BUILT-IN LIBRARIES ARE FORWARDED TO FFMPEG SCRIPT WITHOUT ANY PROCESSING
|
||||
enabled_library_list=()
|
||||
for library in {1..44}
|
||||
for library in {1..45} 48
|
||||
do
|
||||
if [[ ${!library} -eq 1 ]]; then
|
||||
ENABLED_LIBRARY=$(get_library_name $((library - 1)))
|
||||
enabled_library_list+=(${ENABLED_LIBRARY})
|
||||
|
||||
echo -e "INFO: Enabled library ${ENABLED_LIBRARY}" 1>>${BASEDIR}/build.log 2>&1
|
||||
echo -e "INFO: Enabled library ${ENABLED_LIBRARY}\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
done
|
||||
|
||||
# BUILD CPU-FEATURES FIRST
|
||||
build_cpufeatures
|
||||
|
||||
# BUILD LTS SUPPORT LIBRARY FOR API < 18
|
||||
if [[ ! -z ${MOBILE_FFMPEG_LTS_BUILD} ]] && [[ ${API} < 18 ]]; then
|
||||
build_android_lts_support
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user