Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f438e94c58 | |||
| 48b0933d16 | |||
| 5a7bd1ab52 | |||
| 6cc35a6dc9 | |||
| 2ee604de1d | |||
| 6af39b5234 | |||
| e0e415f808 | |||
| 4bc585fb50 | |||
| 9995378117 | |||
| 094c96a0fe | |||
| d3ee5ab33f | |||
| 60b3483a70 | |||
| 60ecdc66c8 | |||
| 9812d6d294 | |||
| 1b6e71d157 |
@@ -1,109 +1,186 @@
|
||||
# MobileFFmpeg
|
||||
Source code and scripts to build FFmpeg for Android and IOS platforms
|
||||
# MobileFFmpeg [](https://gitter.im/mobile-ffmpeg/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)   
|
||||
|
||||
FFmpeg for Android and IOS
|
||||
|
||||
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/dev-v2.x/docs/assets/mobile-ffmpeg-logo-v4.png" width="240">
|
||||
|
||||
### 1. Features
|
||||
- Builds both Android and IOS
|
||||
- Supports
|
||||
- Use prebuilt binaries available under `Github`/`JCenter`/`CocoaPods` or build your own version with external libraries you need
|
||||
- Supports
|
||||
- Both Android and IOS
|
||||
- FFmpeg `v3.4.x` and `v4.0.x` releases
|
||||
- 23 external libraries.
|
||||
- 24 external libraries
|
||||
|
||||
`fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`,
|
||||
`libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `opus`, `shine`, `snappy`,
|
||||
`soxr`, `speex`, `wavpack`
|
||||
`chromaprint`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `opus`, `shine`, `snappy`, `soxr`, `speex`, `wavpack`
|
||||
|
||||
- 2 external libraries with GPL license
|
||||
- 4 external libraries with GPL license
|
||||
|
||||
`x264`, `xvidcore`
|
||||
`vid.stab`, `x264`, `x265`, `xvidcore`
|
||||
|
||||
- Exposes FFmpeg capabilities both directly from FFmpeg libraries and through MobileFFmpeg wrapper library
|
||||
- Creates shared libraries (.so for Android, .dylib for IOS)
|
||||
- Includes cross-compile instructions for 35 open-source libraries
|
||||
- Includes cross-compile instructions for 38 open-source libraries
|
||||
|
||||
`expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`,
|
||||
`libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libtheora`, `libuuid`, `libvorbis`,
|
||||
`libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `opus`, `shine`, `snappy`, `soxr`, `speex`, `tiff`,
|
||||
`wavpack`, `x264`, `xvidcore`
|
||||
|
||||
- Prebuilt binaries under `JCenter` and `CocoaPods`
|
||||
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `opus`, `shine`, `snappy`, `soxr`, `speex`, `tiff`, `vid.stab`, `wavpack`, `x264`, `x265`, `xvidcore`
|
||||
|
||||
- Licensed under LGPL 3.0, can be customized to support GPL v3.0
|
||||
#### 1.1 Android
|
||||
- Supports `arm-v7a`, `arm-v7a-neon`, `arm64-v8a`, `x86` and `x86_64` architectures
|
||||
- Creates Android archive with .aar extension
|
||||
#### 1.2 IOS
|
||||
- Supports `armv7`, `armv7s`, `arm64`, `i386` and `x86_64` architectures
|
||||
- Builds with `-fembed-bitcode` flag
|
||||
- `ARC` enabled library
|
||||
- Built with `-fembed-bitcode` flag
|
||||
- Creates IOS dynamic universal (fat) library
|
||||
- Creates IOS dynamic framework for IOS 8 or later
|
||||
|
||||
### 2. Using
|
||||
Prebuilt libraries are available under [Github](https://github.com/tanersener/mobile-ffmpeg/releases), [JCenter](https://bintray.com/bintray/jcenter) and [CocoaPods](https://cocoapods.org)
|
||||
|
||||
There are six different prebuilt packages. Below you can see which external libraries are enabled in each of them.
|
||||
There are eight different prebuilt packages. Below you can see which external libraries are enabled in each of them.
|
||||
|
||||
| | min | min-gpl | https | https-gpl | full | full-gpl |
|
||||
| :----: | :----: | :----: | :----: | :----: | :----: | :----: |
|
||||
| external <br/> libraries <br/> enabled | - | x264* <br/> xvidcore* | gnutls | gnutls <br/> x264* <br/> xvidcore* | fontconfig <br/> freetype <br/> fribidi <br/> gmp <br/> gnutls <br/> kvazaar <br/> lame <br/> libaom** <br/> libass <br/> libiconv <br/> libilbc* <br/> libtheora <br/> libvorbis <br/> libvpx <br/> libwebp <br/> libxml2 <br/> opencore-amr <br/> opus* <br/> shine <br/> snappy* <br/> soxr** <br/> speex <br/> wavpack | fontconfig <br/> freetype <br/> fribidi <br/> gmp <br/> gnutls <br/> kvazaar <br/> lame <br/> libaom** <br/> libass <br/> libiconv <br/> libilbc* <br/> libtheora <br/> libvorbis <br/> libvpx <br/> libwebp <br/> libxml2 <br/> opencore-amr <br/> opus* <br/> shine <br/> snappy* <br/> soxr** <br/> speex <br/> wavpack <br/> x264* <br/> xvidcore* |
|
||||
| min | min-gpl | https | https-gpl | audio | video | full | full-gpl |
|
||||
| :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: |
|
||||
| - | vid.stab<sup>3</sup> <br/> x264<sup>1</sup> <br/> x265<sup>3</sup> <br/> xvidcore<sup>1</sup> | gnutls | gnutls <br/> vid.stab<sup>3</sup> <br/> x264<sup>1</sup> <br/> x265<sup>3</sup> <br/> xvidcore<sup>1</sup> | chromaprint<sup>3</sup> <br/> lame <br/> libilbc<sup>1</sup> <br/> libvorbis <br/> opencore-amr <br/> opus<sup>1</sup> <br/> shine <br/> soxr<sup>2</sup> <br/> speex <br/> wavpack | fontconfig <br/> freetype <br/> fribidi <br/> kvazaar <br/> libaom<sup>2</sup> <br/> libass <br/> libiconv <br/> libtheora <br/> libvpx <br/> snappy<sup>1</sup> | chromaprint<sup>3</sup> <br/> fontconfig <br/> freetype <br/> fribidi <br/> gmp <br/> gnutls <br/> kvazaar <br/> lame <br/> libaom<sup>2</sup> <br/> libass <br/> libiconv <br/> libilbc<sup>1</sup> <br/> libtheora <br/> libvorbis <br/> libvpx <br/> libwebp <br/> libxml2 <br/> opencore-amr <br/> opus<sup>1</sup> <br/> shine <br/> snappy<sup>1</sup> <br/> soxr<sup>2</sup> <br/> speex <br/> wavpack | chromaprint<sup>3</sup> <br/> fontconfig <br/> freetype <br/> fribidi <br/> gmp <br/> gnutls <br/> kvazaar <br/> lame <br/> libaom<sup>2</sup> <br/> libass <br/> libiconv <br/> libilbc<sup>1</sup> <br/> libtheora <br/> libvorbis <br/> libvpx <br/> libwebp <br/> libxml2 <br/> opencore-amr <br/> opus<sup>1</sup> <br/> shine <br/> snappy<sup>1</sup> <br/> soxr<sup>2</sup> <br/> speex <br/> vid.stab<sup>3</sup> <br/> wavpack <br/> x264<sup>1</sup> <br/> x265<sup>3</sup> <br/> xvidcore<sup>1</sup> |
|
||||
|
||||
\* - Supported since `v1.1`
|
||||
<sup>1</sup> - Supported since `v1.1`
|
||||
|
||||
\*\* - Supported since `v2.0`
|
||||
<sup>2</sup> - Supported since `v2.0`
|
||||
|
||||
<sup>3</sup> - Supported since `v2.1`
|
||||
|
||||
#### 2.1 Android
|
||||
1. Add MobileFFmpeg dependency from `jcenter()`
|
||||
```
|
||||
dependencies {`
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:2.0'
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:2.1.1'
|
||||
}
|
||||
```
|
||||
|
||||
2. Use the following source code to execute commands.
|
||||
2. Execute commands.
|
||||
```
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
|
||||
int rc = FFmpeg.execute("-i", "file1.mp4", "-c:v", "libxvid", "file1.avi");
|
||||
Log.i(Log.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
|
||||
int rc = FFmpeg.execute("-i file1.mp4 -c:v mpeg4 file2.mp4");
|
||||
|
||||
if (rc == RETURN_CODE_SUCCESS) {
|
||||
Log.i(Config.TAG, "Command execution completed successfully.");
|
||||
} else if (rc == RETURN_CODE_CANCEL) {
|
||||
Log.i(Config.TAG, "Command execution cancelled by user.");
|
||||
} else {
|
||||
Log.i(Config.TAG, String.format("Command execution failed with rc=%d.", rc));
|
||||
}
|
||||
```
|
||||
|
||||
3. Stop an ongoing operation.
|
||||
```
|
||||
FFmpeg.cancel();
|
||||
```
|
||||
|
||||
4. Enable log callback.
|
||||
```
|
||||
Config.enableLogCallback(new LogCallback() {
|
||||
public void apply(LogMessage message) {
|
||||
Log.d(Config.TAG, message.getText());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
5. Enable statistics callback.
|
||||
```
|
||||
Config.enableStatisticsCallback(new StatisticsCallback() {
|
||||
public void apply(Statistics newStatistics) {
|
||||
Log.d(Config.TAG, String.format("frame: %d, time: %d", newStatistics.getVideoFrameNumber(), newStatistics.getTime()));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
6. Set log level.
|
||||
```
|
||||
Config.setLogLevel(Level.AV_LOG_FATAL);
|
||||
```
|
||||
|
||||
7. Register custom fonts directory.
|
||||
```
|
||||
Config.setFontDirectory(this, "<folder with fonts>", Collections.EMPTY_MAP);
|
||||
```
|
||||
|
||||
#### 2.2 IOS
|
||||
1. Add MobileFFmpeg pod to your `Podfile`
|
||||
```
|
||||
pod 'mobile-ffmpeg-full', '~> 2.0'
|
||||
pod 'mobile-ffmpeg-full', '~> 2.1.1'
|
||||
```
|
||||
|
||||
2. Create and execute commands using the following `Objective-C` example.
|
||||
2. Create and execute commands.
|
||||
```
|
||||
#import <mobileffmpeg/mobileffmpeg.h>
|
||||
#import <mobileffmpeg/MobileFFmpeg.h>
|
||||
|
||||
NSString* command = @"-i file1.mp4 -c:v libxvid file1.avi";
|
||||
NSArray* commandArray = [command componentsSeparatedByString:@" "];
|
||||
char **arguments = (char **)malloc(sizeof(char*) * ([commandArray count]));
|
||||
for (int i=0; i < [commandArray count]; i++) {
|
||||
NSString *argument = [commandArray objectAtIndex:i];
|
||||
arguments[i] = (char *) [argument UTF8String];
|
||||
int rc = [MobileFFmpeg execute: @"-i file1.mp4 -c:v mpeg4 file2.mp4"];
|
||||
|
||||
if (rc == RETURN_CODE_SUCCESS) {
|
||||
NSLog(@"Command execution completed successfully.\n");
|
||||
} else if (rc == RETURN_CODE_CANCEL) {
|
||||
NSLog(@"Command execution cancelled by user.\n");
|
||||
} else {
|
||||
NSLog(@"Command execution failed with rc=%d.\n", rc);
|
||||
}
|
||||
|
||||
int result = mobileffmpeg_execute((int) [commandArray count], arguments);
|
||||
|
||||
NSLog(@"Process exited with rc %d\n", result);
|
||||
|
||||
free(arguments);
|
||||
```
|
||||
|
||||
3. Stop an ongoing operation.
|
||||
```
|
||||
[MobileFFmpeg cancel];
|
||||
```
|
||||
|
||||
4. Enable log callback.
|
||||
```
|
||||
- (void)logCallback: (int)level :(NSString*)message {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSLog(@"%@", message);
|
||||
});
|
||||
}
|
||||
...
|
||||
[MobileFFmpegConfig setLogDelegate:self];
|
||||
```
|
||||
|
||||
5. Enable statistics callback.
|
||||
```
|
||||
- (void)statisticsCallback:(Statistics *)newStatistics {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSLog(@"frame: %d, time: %d\n", newStatistics.getVideoFrameNumber, newStatistics.getTime);
|
||||
});
|
||||
}
|
||||
...
|
||||
[MobileFFmpegConfig setStatisticsDelegate:self];
|
||||
```
|
||||
|
||||
6. Set log level.
|
||||
```
|
||||
[MobileFFmpegConfig setLogLevel:AV_LOG_FATAL];
|
||||
```
|
||||
|
||||
7. Register custom fonts directory.
|
||||
```
|
||||
[MobileFFmpegConfig setFontDirectory:@"<folder with fonts>" with:nil];
|
||||
```
|
||||
|
||||
#### 2.3 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 and an IOS test application under the
|
||||
`ios/test-app` folder. Both applications are identical and supports command execution and video encoding.
|
||||
There is an Android test application under the `android/test-app` folder and an IOS test application under the `ios/test-app` folder. Both applications are identical and supports command execution, video encoding, accessing https, encoding audio, burning subtitles and video stabilization.
|
||||
|
||||
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/master/docs/assets/ios_test_app.gif" width="240">
|
||||
|
||||
### 3. Versions
|
||||
|
||||
- `MobileFFmpeg v1.x` is the current stable, includes `FFmpeg v3.4.2`
|
||||
- `MobileFFmpeg v1.x` is the previous stable, includes `FFmpeg v3.4.4`
|
||||
|
||||
- `MobileFFmpeg v2.x` is the next stable, includes `FFmpeg v4.0.1`
|
||||
- `MobileFFmpeg v2.x` is the current stable, includes `FFmpeg v4.0.2`
|
||||
|
||||
### 4. Building
|
||||
#### 4.1 Prerequisites
|
||||
1. Use your package manager (apt, yum, dnf, brew, etc.) to install the following packages.
|
||||
|
||||
```
|
||||
autoconf automake libtool pkg-config curl cmake gcc gperf texinfo yasm nasm
|
||||
autoconf automake libtool pkg-config curl cmake gcc gperf texinfo yasm nasm bison
|
||||
```
|
||||
Some of these packages are not mandatory for the default build.
|
||||
Please visit [Android Prerequisites](https://github.com/tanersener/mobile-ffmpeg/wiki/Android-Prerequisites) and
|
||||
@@ -118,7 +195,6 @@ Please visit [Android Prerequisites](https://github.com/tanersener/mobile-ffmpeg
|
||||
- **IOS SDK 7.0.x** or later
|
||||
- **Xcode 8.x** or later
|
||||
- **Command Line Tools**
|
||||
- **lipo** utility
|
||||
|
||||
#### 4.2 Build Scripts
|
||||
Use `android.sh` and `ios.sh` to build MobileFFmpeg for each platform.
|
||||
@@ -132,13 +208,19 @@ Both `android.sh` and `ios.sh` can be customized to override default settings,
|
||||
export ANDROID_NDK_ROOT=<Android NDK Path>
|
||||
./android.sh
|
||||
```
|
||||
|
||||
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/master/docs/assets/android_custom.gif" width="600">
|
||||
|
||||
##### 4.2.2 IOS
|
||||
```
|
||||
./ios.sh
|
||||
```
|
||||
|
||||
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/master/docs/assets/ios_custom.gif" width="600">
|
||||
|
||||
#### 4.3 GPL Support
|
||||
Since`v1.1`, it is possible to enable to GPL licensed libraries `x264` and `xvidcore` from the top level build scripts.
|
||||
It is possible to enable GPL licensed libraries `x264`, `xvidcore` since `v1.1` and `vid.stab`, `x265` since `v2.1`
|
||||
from the top level build scripts.
|
||||
Their source code is not included in the repository and downloaded when enabled.
|
||||
|
||||
#### 4.4 External Libraries
|
||||
@@ -157,9 +239,9 @@ prebuilt binaries with `-gpl` postfix are used then MobileFFmpeg is subject to t
|
||||
|
||||
Source code of FFmpeg and external libraries is included in compliance with their individual licenses.
|
||||
|
||||
`strip-frameworks.sh` script included and distributed is published under the Apache License version 2.0.
|
||||
`strip-frameworks.sh` script included and distributed is published under the [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
Digital assets used in test applications are published in the public domain.
|
||||
In test applications, fonts embedded 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.
|
||||
|
||||
@@ -170,5 +252,8 @@ If you have any recommendations or ideas to improve it, please feel free to subm
|
||||
### 8. See Also
|
||||
|
||||
- [libav gas-preprocessor](https://github.com/libav/gas-preprocessor/raw/master/gas-preprocessor.pl)
|
||||
- [FFmpeg API Documentation](https://ffmpeg.org/doxygen/3.4/index.html)
|
||||
- [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)
|
||||
|
||||
|
||||
@@ -28,27 +28,30 @@ LIBRARY_WAVPACK=16
|
||||
LIBRARY_KVAZAAR=17
|
||||
LIBRARY_X264=18
|
||||
LIBRARY_XVIDCORE=19
|
||||
LIBRARY_LIBILBC=20
|
||||
LIBRARY_OPUS=21
|
||||
LIBRARY_SNAPPY=22
|
||||
LIBRARY_SOXR=23
|
||||
LIBRARY_LIBAOM=24
|
||||
LIBRARY_GIFLIB=25
|
||||
LIBRARY_JPEG=26
|
||||
LIBRARY_LIBOGG=27
|
||||
LIBRARY_LIBPNG=28
|
||||
LIBRARY_LIBUUID=29
|
||||
LIBRARY_NETTLE=30
|
||||
LIBRARY_TIFF=31
|
||||
LIBRARY_EXPAT=32
|
||||
LIBRARY_ZLIB=33
|
||||
LIBRARY_MEDIA_CODEC=34
|
||||
LIBRARY_X265=20
|
||||
LIBRARY_LIBVIDSTAB=21
|
||||
LIBRARY_LIBILBC=22
|
||||
LIBRARY_OPUS=23
|
||||
LIBRARY_SNAPPY=24
|
||||
LIBRARY_SOXR=25
|
||||
LIBRARY_LIBAOM=26
|
||||
LIBRARY_CHROMAPRINT=27
|
||||
LIBRARY_GIFLIB=28
|
||||
LIBRARY_JPEG=29
|
||||
LIBRARY_LIBOGG=30
|
||||
LIBRARY_LIBPNG=31
|
||||
LIBRARY_LIBUUID=32
|
||||
LIBRARY_NETTLE=33
|
||||
LIBRARY_TIFF=34
|
||||
LIBRARY_EXPAT=35
|
||||
LIBRARY_ZLIB=36
|
||||
LIBRARY_MEDIA_CODEC=37
|
||||
|
||||
# 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)
|
||||
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)
|
||||
|
||||
export BASEDIR=$(pwd)
|
||||
|
||||
@@ -78,7 +81,8 @@ When compilation ends an Android Archive (AAR) file is created with enabled plat
|
||||
echo -e "Options:"
|
||||
|
||||
echo -e " -h, --help\t\t\tdisplay this help and exit"
|
||||
echo -e " -V, --version\t\t\tdisplay version information and exit\n"
|
||||
echo -e " -V, --version\t\t\tdisplay version information and exit"
|
||||
echo -e " -d, --debug\t\t\tbuild with debug information\n"
|
||||
|
||||
echo -e "Licensing options:"
|
||||
|
||||
@@ -97,6 +101,7 @@ When compilation ends an Android Archive (AAR) file is created with enabled plat
|
||||
echo -e " --full\t\t\tenables all external libraries"
|
||||
echo -e " --enable-android-media-codec\tbuild with built-in Android MediaCodec [no]"
|
||||
echo -e " --enable-android-zlib\t\tbuild with built-in zlib [no]"
|
||||
echo -e " --enable-chromaprint\t\tbuild with chromaprint [no]"
|
||||
echo -e " --enable-fontconfig\t\tbuild with fontconfig [no]"
|
||||
echo -e " --enable-freetype\t\tbuild with freetype [no]"
|
||||
echo -e " --enable-fribidi\t\tbuild with fribidi [no]"
|
||||
@@ -123,7 +128,9 @@ When compilation ends an Android Archive (AAR) file is created with enabled plat
|
||||
|
||||
echo -e "GPL libraries:"
|
||||
|
||||
echo -e " --enable-libvidstab\t\tbuild with libvidstab [no]"
|
||||
echo -e " --enable-x264\t\t\tbuild with x264 [no]"
|
||||
echo -e " --enable-x265\t\t\tbuild with x265 [no]"
|
||||
echo -e " --enable-xvidcore\t\tbuild with xvidcore [no]\n"
|
||||
|
||||
echo -e "Advanced options:"
|
||||
@@ -145,6 +152,16 @@ 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."
|
||||
}
|
||||
|
||||
skip_library() {
|
||||
SKIP_VARIABLE=$(echo "SKIP_$1" | sed "s/\-/\_/g")
|
||||
|
||||
export ${SKIP_VARIABLE}=1
|
||||
}
|
||||
|
||||
enable_debug() {
|
||||
export MOBILE_FFMPEG_DEBUG="-d"
|
||||
}
|
||||
|
||||
reconf_library() {
|
||||
RECONF_VARIABLE=$(echo "RECONF_$1" | sed "s/\-/\_/g")
|
||||
|
||||
@@ -169,6 +186,9 @@ set_library() {
|
||||
android-zlib)
|
||||
ENABLED_LIBRARIES[LIBRARY_ZLIB]=$2
|
||||
;;
|
||||
chromaprint)
|
||||
ENABLED_LIBRARIES[LIBRARY_CHROMAPRINT]=$2
|
||||
;;
|
||||
fontconfig)
|
||||
ENABLED_LIBRARIES[LIBRARY_FONTCONFIG]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBUUID]=$2
|
||||
@@ -228,6 +248,9 @@ set_library() {
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBOGG]=$2
|
||||
set_library "libvorbis" $2
|
||||
;;
|
||||
libvidstab)
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBVIDSTAB]=$2
|
||||
;;
|
||||
libvorbis)
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBVORBIS]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBOGG]=$2
|
||||
@@ -271,6 +294,9 @@ set_library() {
|
||||
x264)
|
||||
ENABLED_LIBRARIES[LIBRARY_X264]=$2
|
||||
;;
|
||||
x265)
|
||||
ENABLED_LIBRARIES[LIBRARY_X265]=$2
|
||||
;;
|
||||
xvidcore)
|
||||
ENABLED_LIBRARIES[LIBRARY_XVIDCORE]=$2
|
||||
;;
|
||||
@@ -361,7 +387,7 @@ print_enabled_libraries() {
|
||||
let enabled=0;
|
||||
|
||||
# FIRST BUILT-IN LIBRARIES
|
||||
for library in {33..34}
|
||||
for library in {36..37}
|
||||
do
|
||||
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
|
||||
if [[ ${enabled} -ge 1 ]]; then
|
||||
@@ -373,7 +399,7 @@ print_enabled_libraries() {
|
||||
done
|
||||
|
||||
# THEN EXTERNAL LIBRARIES
|
||||
for library in {0..24}
|
||||
for library in {0..27}
|
||||
do
|
||||
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
|
||||
if [[ ${enabled} -ge 1 ]]; then
|
||||
@@ -425,6 +451,14 @@ do
|
||||
display_version
|
||||
exit 0
|
||||
;;
|
||||
--skip-*)
|
||||
SKIP_LIBRARY=`echo $1 | sed -e 's/^--[A-Za-z]*-//g'`
|
||||
|
||||
skip_library ${SKIP_LIBRARY}
|
||||
;;
|
||||
-d | --debug)
|
||||
enable_debug
|
||||
;;
|
||||
--reconf-*)
|
||||
CONF_LIBRARY=`echo $1 | sed -e 's/^--[A-Za-z]*-//g'`
|
||||
|
||||
@@ -436,9 +470,9 @@ do
|
||||
rebuild_library ${BUILD_LIBRARY}
|
||||
;;
|
||||
--full)
|
||||
for library in {0..34}
|
||||
for library in {0..37}
|
||||
do
|
||||
if [[ $library -ne 18 ]] && [[ $library -ne 19 ]]; then
|
||||
if [[ $library -ne 18 ]] && [[ $library -ne 19 ]] && [[ $library -ne 20 ]] && [[ $library -ne 21 ]]; then
|
||||
enable_library $(get_library_name $library)
|
||||
fi
|
||||
done
|
||||
@@ -469,40 +503,43 @@ if [[ -z ${ANDROID_NDK_ROOT} ]]; then
|
||||
fi
|
||||
|
||||
echo -e "Building mobile-ffmpeg for Android\n"
|
||||
echo -e -n "INFO: Building mobile-ffmpeg for Android: " >>${BASEDIR}/build.log
|
||||
echo -e `date` >>${BASEDIR}/build.log
|
||||
echo -e -n "INFO: Building mobile-ffmpeg for Android: " 1>>${BASEDIR}/build.log 2>&1
|
||||
echo -e `date` 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [[ ${ENABLED_ARCHITECTURES[0]} -eq 0 ]] && [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
|
||||
ENABLED_ARCHITECTURES[0]=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" 2>>${BASEDIR}/build.log 1>>${BASEDIR}/build.log
|
||||
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
|
||||
|
||||
# CHECKING GPL LIBRARIES
|
||||
for gpl_library in {18..19}
|
||||
for gpl_library in {18..21}
|
||||
do
|
||||
if [[ ${ENABLED_LIBRARIES[$gpl_library]} -eq 1 ]]; then
|
||||
library_name=$(get_library_name ${gpl_library})
|
||||
|
||||
if [ ${GPL_ENABLED} != "yes" ]; then
|
||||
echo -e "\n(*) Invalid configuration detected. GPL library ${library_name} enabled without --enable-gpl flag.\n"
|
||||
echo -e "\n(*) Invalid configuration detected. GPL library ${library_name} enabled without --enable-gpl flag.\n" >> ${BASEDIR}/build.log
|
||||
echo -e "\n(*) Invalid configuration detected. GPL library ${library_name} enabled without --enable-gpl flag.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
exit 1
|
||||
else
|
||||
DOWNLOAD_RESULT=$(download_gpl_library_source ${library_name})
|
||||
if [[ ${DOWNLOAD_RESULT} -ne 0 ]]; then
|
||||
echo -e "\n(*) Failed to download GPL library ${library_name} source. Please check build.log file for details. If the problem persists refer to offline building instructions.\n"
|
||||
echo -e "\n(*) Failed to download GPL library ${library_name} source.\n" >> ${BASEDIR}/build.log
|
||||
echo -e "\n(*) Failed to download GPL library ${library_name} source.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# CLEANING PREVIOUSLY COPIED c++_shared.so FILES
|
||||
rm -rf ${BASEDIR}/prebuilt/android-cpp-shared 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
for run_arch in {0..4}
|
||||
do
|
||||
if [[ ${ENABLED_ARCHITECTURES[$run_arch]} -eq 1 ]]; then
|
||||
@@ -512,10 +549,21 @@ do
|
||||
|
||||
create_toolchain || exit 1
|
||||
|
||||
# COPY libc++_shared.so FROM EACH TOOLCHAIN
|
||||
mkdir -p ${BASEDIR}/prebuilt/android-cpp-shared/${TOOLCHAIN_ARCH}
|
||||
|
||||
if [[ $run_arch -eq 0 ]] || [[ $run_arch -eq 1 ]]; then
|
||||
cp ${ANDROID_NDK_ROOT}/toolchains/mobile-ffmpeg-${TOOLCHAIN}/$(get_target_host)/lib/armv7-a/libc++_shared.so ${BASEDIR}/prebuilt/android-cpp-shared/${TOOLCHAIN_ARCH} 1>>${BASEDIR}/build.log 2>&1
|
||||
elif [[ $run_arch -eq 4 ]]; then
|
||||
cp ${ANDROID_NDK_ROOT}/toolchains/mobile-ffmpeg-${TOOLCHAIN}/$(get_target_host)/lib64/libc++_shared.so ${BASEDIR}/prebuilt/android-cpp-shared/${TOOLCHAIN_ARCH} 1>>${BASEDIR}/build.log 2>&1
|
||||
else
|
||||
cp ${ANDROID_NDK_ROOT}/toolchains/mobile-ffmpeg-${TOOLCHAIN}/$(get_target_host)/lib/libc++_shared.so ${BASEDIR}/prebuilt/android-cpp-shared/${TOOLCHAIN_ARCH} 1>>${BASEDIR}/build.log 2>&1
|
||||
fi
|
||||
|
||||
. ${BASEDIR}/build/main-android.sh "${ENABLED_LIBRARIES[@]}" || exit 1
|
||||
|
||||
# CLEAR FLAGS
|
||||
for library in {1..35}
|
||||
for library in {1..38}
|
||||
do
|
||||
library_name=$(get_library_name $((library - 1)))
|
||||
unset $(echo "OK_${library_name}" | sed "s/\-/\_/g")
|
||||
@@ -524,11 +572,11 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
rm -f ${BASEDIR}/android/build/.neon
|
||||
rm -f ${BASEDIR}/android/build/.neon 1>>${BASEDIR}/build.log 2>&1
|
||||
ANDROID_ARCHITECTURES=""
|
||||
if [[ ${ENABLED_ARCHITECTURES[1]} -eq 1 ]]; then
|
||||
ANDROID_ARCHITECTURES+="$(get_android_arch 0) "
|
||||
mkdir -p ${BASEDIR}/android/build
|
||||
mkdir -p ${BASEDIR}/android/build 1>>${BASEDIR}/build.log 2>&1
|
||||
cat > "${BASEDIR}/android/build/.neon" << EOF
|
||||
EOF
|
||||
elif [[ ${ENABLED_ARCHITECTURES[0]} -eq 1 ]]; then
|
||||
@@ -553,13 +601,13 @@ if [[ ! -z ${ANDROID_ARCHITECTURES} ]]; then
|
||||
MOBILE_FFMPEG_AAR=${BASEDIR}/prebuilt/android-aar/mobile-ffmpeg
|
||||
|
||||
# BUILDING ANDROID ARCHIVE LIBRARY
|
||||
rm -rf ${BASEDIR}/android/libs
|
||||
rm -rf ${BASEDIR}/android/libs 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
mkdir -p ${MOBILE_FFMPEG_AAR}
|
||||
mkdir -p ${MOBILE_FFMPEG_AAR} 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
cd ${BASEDIR}/android
|
||||
cd ${BASEDIR}/android 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
${ANDROID_NDK_ROOT}/ndk-build -B 2>>${BASEDIR}/build.log 1>>${BASEDIR}/build.log
|
||||
${ANDROID_NDK_ROOT}/ndk-build -B 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "ok"
|
||||
@@ -570,16 +618,21 @@ if [[ ! -z ${ANDROID_ARCHITECTURES} ]]; then
|
||||
|
||||
echo -e -n "\n\nCreating Android archive under prebuilt/android-aar: "
|
||||
|
||||
gradle clean build 2>>${BASEDIR}/build.log 1>>${BASEDIR}/build.log
|
||||
gradle clean build 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "failed\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ${BASEDIR}/android/app/build/outputs/aar/mobile-ffmpeg-*.aar ${MOBILE_FFMPEG_AAR}/mobile-ffmpeg.aar || exit 1
|
||||
cp ${BASEDIR}/android/app/build/outputs/aar/mobile-ffmpeg-*.aar ${MOBILE_FFMPEG_AAR}/mobile-ffmpeg.aar 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
echo -e "Created mobile-ffmpeg Android archive successfully.\n" >> ${BASEDIR}/build.log
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "failed\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "Created mobile-ffmpeg Android archive successfully.\n" 1>>${BASEDIR}/build.log 2>&1
|
||||
|
||||
echo -e "ok\n"
|
||||
fi
|
||||
|
||||
@@ -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 = 2.0
|
||||
PROJECT_NUMBER = 2.1.1
|
||||
|
||||
# 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
|
||||
|
||||
@@ -5,8 +5,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 20
|
||||
versionName "2.0"
|
||||
versionCode 21
|
||||
versionName "2.1.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
project.archivesBaseName = "mobile-ffmpeg"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.2'
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.1'
|
||||
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
|
||||
}
|
||||
|
||||
@@ -19,12 +19,22 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* CHANGES 03.2018 Taner Sener
|
||||
/*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and parent header
|
||||
*
|
||||
* CHANGES 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - Unused headers removed
|
||||
* - Parentheses placed around assignments in condition to prevent -Wparentheses warning
|
||||
* - exit_program updated with longjmp, disabling exit
|
||||
* - argc validation added for optional arguments inside split_commandline
|
||||
* - longjmp_value added to store exit code
|
||||
* - (optindex < argc) validation added before accessing argv[optindex] inside split_commandline()
|
||||
* and parse_options()
|
||||
* - All av_log_set_callback invocations updated to set mobileffmpeg_log_callback_function from mobileffmpeg.c. Unused
|
||||
* log_callback_help and log_callback_help methods removed
|
||||
* - (idx + 1 < argc) validation added in parse_loglevel()
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
@@ -33,7 +43,7 @@
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "exception.h"
|
||||
#include "mobileffmpeg_exception.h"
|
||||
|
||||
/* Include only the enabled headers since some compilers (namely, Sun
|
||||
Studio) will not omit unused inline functions and create undefined
|
||||
@@ -43,14 +53,16 @@
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavdevice/avdevice.h"
|
||||
#include "libswresample/swresample.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libswresample/swresample.h"
|
||||
#include "libavutil/attributes.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/bprint.h"
|
||||
#include "libavutil/display.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavutil/libm.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/eval.h"
|
||||
@@ -59,7 +71,10 @@
|
||||
#include "libavutil/cpu.h"
|
||||
#include "libavutil/ffversion.h"
|
||||
#include "libavutil/version.h"
|
||||
#include "cmdutils.h"
|
||||
#include "fftools_cmdutils.h"
|
||||
#if CONFIG_NETWORK
|
||||
#include "libavformat/network.h"
|
||||
#endif
|
||||
#if HAVE_SYS_RESOURCE_H
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
@@ -69,6 +84,7 @@
|
||||
#endif
|
||||
|
||||
static int init_report(const char *env);
|
||||
extern void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs);
|
||||
|
||||
AVDictionary *sws_dict;
|
||||
AVDictionary *swr_opts;
|
||||
@@ -77,6 +93,7 @@ AVDictionary *format_opts, *codec_opts, *resample_opts;
|
||||
static FILE *report_file;
|
||||
static int report_file_level = AV_LOG_DEBUG;
|
||||
int hide_banner = 0;
|
||||
int longjmp_value = 0;
|
||||
|
||||
enum show_muxdemuxers {
|
||||
SHOW_DEFAULT,
|
||||
@@ -98,27 +115,6 @@ void uninit_opts(void)
|
||||
av_dict_free(&resample_opts);
|
||||
}
|
||||
|
||||
void log_callback_help(void *ptr, int level, const char *fmt, va_list vl)
|
||||
{
|
||||
vfprintf(stdout, fmt, vl);
|
||||
}
|
||||
|
||||
static void log_callback_report(void *ptr, int level, const char *fmt, va_list vl)
|
||||
{
|
||||
va_list vl2;
|
||||
char line[1024];
|
||||
static int print_prefix = 1;
|
||||
|
||||
va_copy(vl2, vl);
|
||||
av_log_default_callback(ptr, level, fmt, vl);
|
||||
av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix);
|
||||
va_end(vl2);
|
||||
if (report_file_level >= level) {
|
||||
fputs(line, report_file);
|
||||
fflush(report_file);
|
||||
}
|
||||
}
|
||||
|
||||
void init_dynload(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -140,9 +136,10 @@ void exit_program(int ret)
|
||||
if (program_exit)
|
||||
program_exit(ret);
|
||||
|
||||
// exit disabled and replaced with longjmp
|
||||
// exit disabled and replaced with longjmp, exit value stored in longjmp_value
|
||||
// exit(ret);
|
||||
longjmp(ex_buf__, ret);;
|
||||
longjmp_value = ret;
|
||||
longjmp(ex_buf__, ret);
|
||||
}
|
||||
|
||||
double parse_number_or_die(const char *context, const char *numstr, int type,
|
||||
@@ -405,9 +402,11 @@ void parse_options(void *optctx, int argc, char **argv, const OptionDef *options
|
||||
}
|
||||
opt++;
|
||||
|
||||
if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)
|
||||
exit_program(1);
|
||||
optindex += ret;
|
||||
if (optindex < argc) {
|
||||
if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)
|
||||
exit_program(1);
|
||||
optindex += ret;
|
||||
}
|
||||
} else {
|
||||
if (parse_arg_function)
|
||||
parse_arg_function(optctx, opt);
|
||||
@@ -516,7 +515,7 @@ void parse_loglevel(int argc, char **argv, const OptionDef *options)
|
||||
|
||||
if (!idx)
|
||||
idx = locate_option(argc, argv, options, "v");
|
||||
if (idx && argv[idx + 1])
|
||||
if (idx && (idx + 1 < argc) && argv[idx + 1])
|
||||
opt_loglevel(NULL, "loglevel", argv[idx + 1]);
|
||||
idx = locate_option(argc, argv, options, "report");
|
||||
if ((env = getenv("FFREPORT")) || idx) {
|
||||
@@ -788,8 +787,9 @@ int split_commandline(OptionParseContext *octx, int argc, char *argv[],
|
||||
|
||||
#define GET_ARG(arg) \
|
||||
do { \
|
||||
arg = argv[optindex++]; \
|
||||
if (!arg) { \
|
||||
if (optindex < argc) { \
|
||||
arg = argv[optindex++]; \
|
||||
} else { \
|
||||
av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\n", opt);\
|
||||
return AVERROR(EINVAL); \
|
||||
} \
|
||||
@@ -827,7 +827,7 @@ do { \
|
||||
}
|
||||
|
||||
/* AVOptions */
|
||||
if (argv[optindex]) {
|
||||
if ((optindex < argc) && argv[optindex]) {
|
||||
ret = opt_default(NULL, opt, argv[optindex]);
|
||||
if (ret >= 0) {
|
||||
av_log(NULL, AV_LOG_DEBUG, " matched as AVOption '%s' with "
|
||||
@@ -889,28 +889,54 @@ int opt_loglevel(void *optctx, const char *opt, const char *arg)
|
||||
{ "debug" , AV_LOG_DEBUG },
|
||||
{ "trace" , AV_LOG_TRACE },
|
||||
};
|
||||
const char *token;
|
||||
char *tail;
|
||||
int level;
|
||||
int flags;
|
||||
int i;
|
||||
int flags = av_log_get_flags();
|
||||
int level = av_log_get_level();
|
||||
int cmd, i = 0;
|
||||
|
||||
flags = av_log_get_flags();
|
||||
tail = strstr(arg, "repeat");
|
||||
if (tail)
|
||||
flags &= ~AV_LOG_SKIP_REPEATED;
|
||||
else
|
||||
flags |= AV_LOG_SKIP_REPEATED;
|
||||
|
||||
av_log_set_flags(flags);
|
||||
if (tail == arg)
|
||||
arg += 6 + (arg[6]=='+');
|
||||
if(tail && !*arg)
|
||||
return 0;
|
||||
av_assert0(arg);
|
||||
while (*arg) {
|
||||
token = arg;
|
||||
if (*token == '+' || *token == '-') {
|
||||
cmd = *token++;
|
||||
} else {
|
||||
cmd = 0;
|
||||
}
|
||||
if (!i && !cmd) {
|
||||
flags = 0; /* missing relative prefix, build absolute value */
|
||||
}
|
||||
if (!strncmp(token, "repeat", 6)) {
|
||||
if (cmd == '-') {
|
||||
flags |= AV_LOG_SKIP_REPEATED;
|
||||
} else {
|
||||
flags &= ~AV_LOG_SKIP_REPEATED;
|
||||
}
|
||||
arg = token + 6;
|
||||
} else if (!strncmp(token, "level", 5)) {
|
||||
if (cmd == '-') {
|
||||
flags &= ~AV_LOG_PRINT_LEVEL;
|
||||
} else {
|
||||
flags |= AV_LOG_PRINT_LEVEL;
|
||||
}
|
||||
arg = token + 5;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!*arg) {
|
||||
goto end;
|
||||
} else if (*arg == '+') {
|
||||
arg++;
|
||||
} else if (!i) {
|
||||
flags = av_log_get_flags(); /* level value without prefix, reset flags */
|
||||
}
|
||||
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(log_levels); i++) {
|
||||
if (!strcmp(log_levels[i].name, arg)) {
|
||||
av_log_set_level(log_levels[i].level);
|
||||
return 0;
|
||||
level = log_levels[i].level;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,6 +948,9 @@ int opt_loglevel(void *optctx, const char *opt, const char *arg)
|
||||
av_log(NULL, AV_LOG_FATAL, "\"%s\"\n", log_levels[i].name);
|
||||
exit_program(1);
|
||||
}
|
||||
|
||||
end:
|
||||
av_log_set_flags(flags);
|
||||
av_log_set_level(level);
|
||||
return 0;
|
||||
}
|
||||
@@ -1013,7 +1042,7 @@ static int init_report(const char *env)
|
||||
filename.str, strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
av_log_set_callback(log_callback_report);
|
||||
av_log_set_callback(mobileffmpeg_log_callback_function);
|
||||
av_log(NULL, AV_LOG_INFO,
|
||||
"%s started on %04d-%02d-%02d at %02d:%02d:%02d\n"
|
||||
"Report written to \"%s\"\n",
|
||||
@@ -1110,6 +1139,7 @@ static void print_all_libs_info(int flags, int level)
|
||||
PRINT_LIB_INFO(avformat, AVFORMAT, flags, level);
|
||||
PRINT_LIB_INFO(avdevice, AVDEVICE, flags, level);
|
||||
PRINT_LIB_INFO(avfilter, AVFILTER, flags, level);
|
||||
PRINT_LIB_INFO(swscale, SWSCALE, flags, level);
|
||||
PRINT_LIB_INFO(swresample, SWRESAMPLE, flags, level);
|
||||
}
|
||||
|
||||
@@ -1166,7 +1196,7 @@ void show_banner(int argc, char **argv, const OptionDef *options)
|
||||
|
||||
int show_version(void *optctx, const char *opt, const char *arg)
|
||||
{
|
||||
av_log_set_callback(log_callback_help);
|
||||
av_log_set_callback(mobileffmpeg_log_callback_function);
|
||||
print_program_info (SHOW_COPYRIGHT, AV_LOG_INFO);
|
||||
print_all_libs_info(SHOW_VERSION, AV_LOG_INFO);
|
||||
|
||||
@@ -1175,7 +1205,7 @@ int show_version(void *optctx, const char *opt, const char *arg)
|
||||
|
||||
int show_buildconf(void *optctx, const char *opt, const char *arg)
|
||||
{
|
||||
av_log_set_callback(log_callback_help);
|
||||
av_log_set_callback(mobileffmpeg_log_callback_function);
|
||||
print_buildconf (INDENT|0, AV_LOG_INFO);
|
||||
|
||||
return 0;
|
||||
@@ -1264,8 +1294,10 @@ static int is_device(const AVClass *avclass)
|
||||
|
||||
static int show_formats_devices(void *optctx, const char *opt, const char *arg, int device_only, int muxdemuxers)
|
||||
{
|
||||
AVInputFormat *ifmt = NULL;
|
||||
AVOutputFormat *ofmt = NULL;
|
||||
void *ifmt_opaque = NULL;
|
||||
const AVInputFormat *ifmt = NULL;
|
||||
void *ofmt_opaque = NULL;
|
||||
const AVOutputFormat *ofmt = NULL;
|
||||
const char *last_name;
|
||||
int is_dev;
|
||||
|
||||
@@ -1281,7 +1313,8 @@ static int show_formats_devices(void *optctx, const char *opt, const char *arg,
|
||||
const char *long_name = NULL;
|
||||
|
||||
if (muxdemuxers !=SHOW_DEMUXERS) {
|
||||
while ((ofmt = av_oformat_next(ofmt))) {
|
||||
ofmt_opaque = NULL;
|
||||
while ((ofmt = av_muxer_iterate(&ofmt_opaque))) {
|
||||
is_dev = is_device(ofmt->priv_class);
|
||||
if (!is_dev && device_only)
|
||||
continue;
|
||||
@@ -1294,7 +1327,8 @@ static int show_formats_devices(void *optctx, const char *opt, const char *arg,
|
||||
}
|
||||
}
|
||||
if (muxdemuxers != SHOW_MUXERS) {
|
||||
while ((ifmt = av_iformat_next(ifmt))) {
|
||||
ifmt_opaque = NULL;
|
||||
while ((ifmt = av_demuxer_iterate(&ifmt_opaque))) {
|
||||
is_dev = is_device(ifmt->priv_class);
|
||||
if (!is_dev && device_only)
|
||||
continue;
|
||||
@@ -1608,7 +1642,7 @@ int show_bsfs(void *optctx, const char *opt, const char *arg)
|
||||
void *opaque = NULL;
|
||||
|
||||
printf("Bitstream filters:\n");
|
||||
while ((bsf = av_bsf_next(&opaque)))
|
||||
while ((bsf = av_bsf_iterate(&opaque)))
|
||||
printf("%s\n", bsf->name);
|
||||
printf("\n");
|
||||
return 0;
|
||||
@@ -1634,6 +1668,7 @@ int show_filters(void *optctx, const char *opt, const char *arg)
|
||||
#if CONFIG_AVFILTER
|
||||
const AVFilter *filter = NULL;
|
||||
char descr[64], *descr_cur;
|
||||
void *opaque = NULL;
|
||||
int i, j;
|
||||
const AVFilterPad *pad;
|
||||
|
||||
@@ -1645,7 +1680,7 @@ int show_filters(void *optctx, const char *opt, const char *arg)
|
||||
" V = Video input/output\n"
|
||||
" N = Dynamic number and/or type of input/output\n"
|
||||
" | = Source or sink filter\n");
|
||||
while ((filter = avfilter_next(filter))) {
|
||||
while ((filter = av_filter_iterate(&opaque))) {
|
||||
descr_cur = descr;
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (i) {
|
||||
@@ -1708,7 +1743,7 @@ int show_pix_fmts(void *optctx, const char *opt, const char *arg)
|
||||
#endif
|
||||
|
||||
while ((pix_desc = av_pix_fmt_desc_next(pix_desc))) {
|
||||
enum AVPixelFormat pix_fmt = av_pix_fmt_desc_get_id(pix_desc);
|
||||
enum AVPixelFormat av_unused pix_fmt = av_pix_fmt_desc_get_id(pix_desc);
|
||||
printf("%c%c%c%c%c %-16s %d %2d\n",
|
||||
sws_isSupportedInput (pix_fmt) ? 'I' : '.',
|
||||
sws_isSupportedOutput(pix_fmt) ? 'O' : '.',
|
||||
@@ -1902,10 +1937,26 @@ static void show_help_filter(const char *name)
|
||||
}
|
||||
#endif
|
||||
|
||||
static void show_help_bsf(const char *name)
|
||||
{
|
||||
const AVBitStreamFilter *bsf = av_bsf_get_by_name(name);
|
||||
|
||||
if (!bsf) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Unknown bit stream filter '%s'.\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Bit stream filter %s\n", bsf->name);
|
||||
PRINT_CODEC_SUPPORTED(bsf, codec_ids, enum AVCodecID, "codecs",
|
||||
AV_CODEC_ID_NONE, GET_CODEC_NAME);
|
||||
if (bsf->priv_class)
|
||||
show_help_children(bsf->priv_class, AV_OPT_FLAG_BSF_PARAM);
|
||||
}
|
||||
|
||||
int show_help(void *optctx, const char *opt, const char *arg)
|
||||
{
|
||||
char *topic, *par;
|
||||
av_log_set_callback(log_callback_help);
|
||||
av_log_set_callback(mobileffmpeg_log_callback_function);
|
||||
|
||||
topic = av_strdup(arg ? arg : "");
|
||||
if (!topic)
|
||||
@@ -1928,6 +1979,8 @@ int show_help(void *optctx, const char *opt, const char *arg)
|
||||
} else if (!strcmp(topic, "filter")) {
|
||||
show_help_filter(par);
|
||||
#endif
|
||||
} else if (!strcmp(topic, "bsf")) {
|
||||
show_help_bsf(par);
|
||||
} else {
|
||||
show_help_default(topic, par);
|
||||
}
|
||||
@@ -19,15 +19,19 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* CHANGES 03.2018 Taner Sener
|
||||
/*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and include guards
|
||||
*
|
||||
* CHANGES 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - Include guards renamed
|
||||
* - log.h included
|
||||
* - Unused headers removed
|
||||
*/
|
||||
|
||||
#ifndef MOBILEFFMPEG_CMDUTILS_H
|
||||
#define MOBILEFFMPEG_CMDUTILS_H
|
||||
#ifndef FFTOOLS_CMDUTILS_H
|
||||
#define FFTOOLS_CMDUTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -36,7 +40,6 @@
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef main /* We don't want SDL to override our main() */
|
||||
@@ -113,12 +116,6 @@ int opt_max_alloc(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
int opt_codec_debug(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
#if CONFIG_OPENCL
|
||||
int opt_opencl(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
int opt_opencl_bench(void *optctx, const char *opt, const char *arg);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Limit the execution time.
|
||||
*/
|
||||
@@ -163,6 +160,7 @@ typedef struct SpecifierOpt {
|
||||
uint8_t *str;
|
||||
int i;
|
||||
int64_t i64;
|
||||
uint64_t ui64;
|
||||
float f;
|
||||
double dbl;
|
||||
} u;
|
||||
@@ -214,17 +212,6 @@ typedef struct OptionDef {
|
||||
void show_help_options(const OptionDef *options, const char *msg, int req_flags,
|
||||
int rej_flags, int alt_flags);
|
||||
|
||||
#if CONFIG_OPENCL
|
||||
#define CMDUTILS_COMMON_OPTIONS_OPENCL \
|
||||
{ "opencl_bench", OPT_EXIT, {.func_arg = opt_opencl_bench}, \
|
||||
"run benchmark on all OpenCL devices and show results" }, \
|
||||
{ "opencl_options", HAS_ARG, {.func_arg = opt_opencl}, \
|
||||
"set OpenCL environment options" }, \
|
||||
|
||||
#else
|
||||
#define CMDUTILS_COMMON_OPTIONS_OPENCL
|
||||
#endif
|
||||
|
||||
#if CONFIG_AVDEVICE
|
||||
#define CMDUTILS_COMMON_OPTIONS_AVDEVICE \
|
||||
{ "sources" , OPT_EXIT | HAS_ARG, { .func_arg = show_sources }, \
|
||||
@@ -264,7 +251,6 @@ void show_help_options(const OptionDef *options, const char *msg, int req_flags,
|
||||
{ "max_alloc", HAS_ARG, { .func_arg = opt_max_alloc }, "set maximum size of a single allocated block", "bytes" }, \
|
||||
{ "cpuflags", HAS_ARG | OPT_EXPERT, { .func_arg = opt_cpuflags }, "force specific cpu flags", "flags" }, \
|
||||
{ "hide_banner", OPT_BOOL | OPT_EXPERT, {&hide_banner}, "do not show program banner", "hide_banner" }, \
|
||||
CMDUTILS_COMMON_OPTIONS_OPENCL \
|
||||
CMDUTILS_COMMON_OPTIONS_AVDEVICE \
|
||||
|
||||
/**
|
||||
@@ -650,6 +636,9 @@ void *grow_array(void *array, int elem_size, int *size, int new_size);
|
||||
#define GET_PIX_FMT_NAME(pix_fmt)\
|
||||
const char *name = av_get_pix_fmt_name(pix_fmt);
|
||||
|
||||
#define GET_CODEC_NAME(id)\
|
||||
const char *name = avcodec_descriptor_get(id)->name;
|
||||
|
||||
#define GET_SAMPLE_FMT_NAME(sample_fmt)\
|
||||
const char *name = av_get_sample_fmt_name(sample_fmt)
|
||||
|
||||
@@ -667,4 +656,4 @@ void *grow_array(void *array, int elem_size, int *size, int new_size);
|
||||
|
||||
double get_rotation(AVStream *st);
|
||||
|
||||
#endif /* MOBILEFFMPEG_CMDUTILS_H */
|
||||
#endif /* FFTOOLS_CMDUTILS_H */
|
||||
@@ -16,17 +16,20 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* CHANGES 03.2018 Taner Sener
|
||||
/*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and include guards
|
||||
* - set_report_callback() method declared
|
||||
* - cancel_operation() method declared
|
||||
*
|
||||
* CHANGES 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - Include guards renamed
|
||||
* - log.h included
|
||||
* - mid_pred copied from libavcodec/mathops.h
|
||||
* - ff_dlog copied from libavutil/internal.h
|
||||
* - SWS_BILINEAR and SWS_BITEXACT definitions copied from libswscale/swscale.h
|
||||
*/
|
||||
|
||||
#ifndef MOBILEFFMPEG_FFMPEG_H
|
||||
#define MOBILEFFMPEG_FFMPEG_H
|
||||
#ifndef FFTOOLS_FFMPEG_H
|
||||
#define FFTOOLS_FFMPEG_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -34,13 +37,7 @@
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#if HAVE_PTHREADS
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include "cmdutils.h"
|
||||
#include "fftools_cmdutils.h"
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavformat/avio.h"
|
||||
@@ -56,39 +53,11 @@
|
||||
#include "libavutil/hwcontext.h"
|
||||
#include "libavutil/pixfmt.h"
|
||||
#include "libavutil/rational.h"
|
||||
#include "libavutil/thread.h"
|
||||
#include "libavutil/threadmessage.h"
|
||||
|
||||
#include "libswresample/swresample.h"
|
||||
|
||||
/* median of 3 */
|
||||
#ifndef mid_pred
|
||||
#define mid_pred mid_pred
|
||||
static inline av_const int mid_pred(int a, int b, int c)
|
||||
{
|
||||
if(a>b){
|
||||
if(c>b){
|
||||
if(c>a) b=a;
|
||||
else b=c;
|
||||
}
|
||||
}else{
|
||||
if(b>c){
|
||||
if(c>a) b=c;
|
||||
else b=a;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define SWS_BILINEAR 2
|
||||
#define SWS_BITEXACT 0x80000
|
||||
|
||||
#ifdef DEBUG
|
||||
# define ff_dlog(ctx, ...) av_log(ctx, AV_LOG_DEBUG, __VA_ARGS__)
|
||||
#else
|
||||
# define ff_dlog(ctx, ...) do { if (0) av_log(ctx, AV_LOG_DEBUG, __VA_ARGS__); } while (0)
|
||||
#endif
|
||||
|
||||
#define VSYNC_AUTO -1
|
||||
#define VSYNC_PASSTHROUGH 0
|
||||
#define VSYNC_CFR 1
|
||||
@@ -101,14 +70,10 @@ static inline av_const int mid_pred(int a, int b, int c)
|
||||
enum HWAccelID {
|
||||
HWACCEL_NONE = 0,
|
||||
HWACCEL_AUTO,
|
||||
HWACCEL_VDPAU,
|
||||
HWACCEL_DXVA2,
|
||||
HWACCEL_VDA,
|
||||
HWACCEL_GENERIC,
|
||||
HWACCEL_VIDEOTOOLBOX,
|
||||
HWACCEL_QSV,
|
||||
HWACCEL_VAAPI,
|
||||
HWACCEL_CUVID,
|
||||
HWACCEL_D3D11VA,
|
||||
};
|
||||
|
||||
typedef struct HWAccel {
|
||||
@@ -116,7 +81,6 @@ typedef struct HWAccel {
|
||||
int (*init)(AVCodecContext *s);
|
||||
enum HWAccelID id;
|
||||
enum AVPixelFormat pix_fmt;
|
||||
enum AVHWDeviceType device_type;
|
||||
} HWAccel;
|
||||
|
||||
typedef struct HWDevice {
|
||||
@@ -201,6 +165,7 @@ typedef struct OptionsContext {
|
||||
float mux_preload;
|
||||
float mux_max_delay;
|
||||
int shortest;
|
||||
int bitexact;
|
||||
|
||||
int video_disable;
|
||||
int audio_disable;
|
||||
@@ -409,11 +374,11 @@ typedef struct InputStream {
|
||||
|
||||
/* hwaccel options */
|
||||
enum HWAccelID hwaccel_id;
|
||||
enum AVHWDeviceType hwaccel_device_type;
|
||||
char *hwaccel_device;
|
||||
enum AVPixelFormat hwaccel_output_format;
|
||||
|
||||
/* hwaccel context */
|
||||
enum HWAccelID active_hwaccel_id;
|
||||
void *hwaccel_ctx;
|
||||
void (*hwaccel_uninit)(AVCodecContext *s);
|
||||
int (*hwaccel_get_buffer)(AVCodecContext *s, AVFrame *frame, int flags);
|
||||
@@ -459,7 +424,7 @@ typedef struct InputFile {
|
||||
int rate_emu;
|
||||
int accurate_seek;
|
||||
|
||||
#if HAVE_PTHREADS
|
||||
#if HAVE_THREADS
|
||||
AVThreadMessageQueue *in_thread_queue;
|
||||
pthread_t thread; /* thread reading from this file */
|
||||
int non_blocking; /* reading packets from the thread should not block */
|
||||
@@ -573,9 +538,6 @@ typedef struct OutputStream {
|
||||
|
||||
int keep_pix_fmt;
|
||||
|
||||
AVCodecParserContext *parser;
|
||||
AVCodecContext *parser_avctx;
|
||||
|
||||
/* stats */
|
||||
// combined size of all the packets written
|
||||
uint64_t data_size;
|
||||
@@ -664,7 +626,6 @@ extern const AVIOInterruptCB int_cb;
|
||||
|
||||
extern const OptionDef options[];
|
||||
extern const HWAccel hwaccels[];
|
||||
extern int hwaccel_lax_profile_check;
|
||||
extern AVBufferRef *hw_device_ctx;
|
||||
#if CONFIG_QSV
|
||||
extern char *qsv_device;
|
||||
@@ -702,7 +663,6 @@ int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame);
|
||||
|
||||
int ffmpeg_parse_options(int argc, char **argv);
|
||||
|
||||
int vda_init(AVCodecContext *s);
|
||||
int videotoolbox_init(AVCodecContext *s);
|
||||
int qsv_init(AVCodecContext *s);
|
||||
int cuvid_init(AVCodecContext *s);
|
||||
@@ -716,4 +676,8 @@ int hw_device_setup_for_encode(OutputStream *ost);
|
||||
|
||||
int hwaccel_decode_init(AVCodecContext *avctx);
|
||||
|
||||
#endif /* MOBILEFFMPEG_FFMPEG_H */
|
||||
void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double));
|
||||
|
||||
void cancel_operation();
|
||||
|
||||
#endif /* FFTOOLS_FFMPEG_H */
|
||||
@@ -18,14 +18,19 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* CHANGES 03.2018 Taner Sener
|
||||
/*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and parent header
|
||||
*
|
||||
* CHANGES 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - Unused headers removed
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ffmpeg.h"
|
||||
#include "fftools_ffmpeg.h"
|
||||
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavfilter/buffersink.h"
|
||||
@@ -343,6 +348,7 @@ int init_complex_filtergraph(FilterGraph *fg)
|
||||
graph = avfilter_graph_alloc();
|
||||
if (!graph)
|
||||
return AVERROR(ENOMEM);
|
||||
graph->nb_threads = 1;
|
||||
|
||||
ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs);
|
||||
if (ret < 0)
|
||||
@@ -16,11 +16,17 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and parent header
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
|
||||
#include "ffmpeg.h"
|
||||
#include "fftools_ffmpeg.h"
|
||||
|
||||
static int nb_hw_devices;
|
||||
static HWDevice **hw_devices;
|
||||
@@ -64,6 +70,31 @@ static HWDevice *hw_device_add(void)
|
||||
return hw_devices[nb_hw_devices++];
|
||||
}
|
||||
|
||||
static char *hw_device_default_name(enum AVHWDeviceType type)
|
||||
{
|
||||
// Make an automatic name of the form "type%d". We arbitrarily
|
||||
// limit at 1000 anonymous devices of the same type - there is
|
||||
// probably something else very wrong if you get to this limit.
|
||||
const char *type_name = av_hwdevice_get_type_name(type);
|
||||
char *name;
|
||||
size_t index_pos;
|
||||
int index, index_limit = 1000;
|
||||
index_pos = strlen(type_name);
|
||||
name = av_malloc(index_pos + 4);
|
||||
if (!name)
|
||||
return NULL;
|
||||
for (index = 0; index < index_limit; index++) {
|
||||
snprintf(name, index_pos + 4, "%s%d", type_name, index);
|
||||
if (!hw_device_get_by_name(name))
|
||||
break;
|
||||
}
|
||||
if (index >= index_limit) {
|
||||
av_freep(&name);
|
||||
return NULL;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
int hw_device_init_from_string(const char *arg, HWDevice **dev_out)
|
||||
{
|
||||
// "type=name:device,key=value,key2=value2"
|
||||
@@ -111,27 +142,11 @@ int hw_device_init_from_string(const char *arg, HWDevice **dev_out)
|
||||
|
||||
p += 1 + k;
|
||||
} else {
|
||||
// Give the device an automatic name of the form "type%d".
|
||||
// We arbitrarily limit at 1000 anonymous devices of the same
|
||||
// type - there is probably something else very wrong if you
|
||||
// get to this limit.
|
||||
size_t index_pos;
|
||||
int index, index_limit = 1000;
|
||||
index_pos = strlen(type_name);
|
||||
name = av_malloc(index_pos + 4);
|
||||
name = hw_device_default_name(type);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
for (index = 0; index < index_limit; index++) {
|
||||
snprintf(name, index_pos + 4, "%s%d", type_name, index);
|
||||
if (!hw_device_get_by_name(name))
|
||||
break;
|
||||
}
|
||||
if (index >= index_limit) {
|
||||
errmsg = "too many devices";
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!*p) {
|
||||
@@ -214,6 +229,49 @@ fail:
|
||||
goto done;
|
||||
}
|
||||
|
||||
static int hw_device_init_from_type(enum AVHWDeviceType type,
|
||||
const char *device,
|
||||
HWDevice **dev_out)
|
||||
{
|
||||
AVBufferRef *device_ref = NULL;
|
||||
HWDevice *dev;
|
||||
char *name;
|
||||
int err;
|
||||
|
||||
name = hw_device_default_name(type);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create(&device_ref, type, device, NULL, 0);
|
||||
if (err < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Device creation failed: %d.\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev = hw_device_add();
|
||||
if (!dev) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev->name = name;
|
||||
dev->type = type;
|
||||
dev->device_ref = device_ref;
|
||||
|
||||
if (dev_out)
|
||||
*dev_out = dev;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_freep(&name);
|
||||
av_buffer_unref(&device_ref);
|
||||
return err;
|
||||
}
|
||||
|
||||
void hw_device_free_all(void)
|
||||
{
|
||||
int i;
|
||||
@@ -226,80 +284,130 @@ void hw_device_free_all(void)
|
||||
nb_hw_devices = 0;
|
||||
}
|
||||
|
||||
static enum AVHWDeviceType hw_device_match_type_by_hwaccel(enum HWAccelID hwaccel_id)
|
||||
static HWDevice *hw_device_match_by_codec(const AVCodec *codec)
|
||||
{
|
||||
const AVCodecHWConfig *config;
|
||||
HWDevice *dev;
|
||||
int i;
|
||||
if (hwaccel_id == HWACCEL_NONE)
|
||||
return AV_HWDEVICE_TYPE_NONE;
|
||||
for (i = 0; hwaccels[i].name; i++) {
|
||||
if (hwaccels[i].id == hwaccel_id)
|
||||
return hwaccels[i].device_type;
|
||||
for (i = 0;; i++) {
|
||||
config = avcodec_get_hw_config(codec, i);
|
||||
if (!config)
|
||||
return NULL;
|
||||
if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
|
||||
continue;
|
||||
dev = hw_device_get_by_type(config->device_type);
|
||||
if (dev)
|
||||
return dev;
|
||||
}
|
||||
return AV_HWDEVICE_TYPE_NONE;
|
||||
}
|
||||
|
||||
static enum AVHWDeviceType hw_device_match_type_in_name(const char *codec_name)
|
||||
{
|
||||
const char *type_name;
|
||||
enum AVHWDeviceType type;
|
||||
for (type = av_hwdevice_iterate_types(AV_HWDEVICE_TYPE_NONE);
|
||||
type != AV_HWDEVICE_TYPE_NONE;
|
||||
type = av_hwdevice_iterate_types(type)) {
|
||||
type_name = av_hwdevice_get_type_name(type);
|
||||
if (strstr(codec_name, type_name))
|
||||
return type;
|
||||
}
|
||||
return AV_HWDEVICE_TYPE_NONE;
|
||||
}
|
||||
|
||||
int hw_device_setup_for_decode(InputStream *ist)
|
||||
{
|
||||
const AVCodecHWConfig *config;
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev;
|
||||
int err;
|
||||
HWDevice *dev = NULL;
|
||||
int err, auto_device = 0;
|
||||
|
||||
if (ist->hwaccel_device) {
|
||||
dev = hw_device_get_by_name(ist->hwaccel_device);
|
||||
if (!dev) {
|
||||
char *tmp;
|
||||
type = hw_device_match_type_by_hwaccel(ist->hwaccel_id);
|
||||
if (type == AV_HWDEVICE_TYPE_NONE) {
|
||||
// No match - this isn't necessarily invalid, though,
|
||||
// because an explicit device might not be needed or
|
||||
// the hwaccel setup could be handled elsewhere.
|
||||
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
||||
auto_device = 1;
|
||||
} else if (ist->hwaccel_id == HWACCEL_GENERIC) {
|
||||
type = ist->hwaccel_device_type;
|
||||
err = hw_device_init_from_type(type, ist->hwaccel_device,
|
||||
&dev);
|
||||
} else {
|
||||
// This will be dealt with by API-specific initialisation
|
||||
// (using hwaccel_device), so nothing further needed here.
|
||||
return 0;
|
||||
}
|
||||
tmp = av_asprintf("%s:%s", av_hwdevice_get_type_name(type),
|
||||
ist->hwaccel_device);
|
||||
if (!tmp)
|
||||
return AVERROR(ENOMEM);
|
||||
err = hw_device_init_from_string(tmp, &dev);
|
||||
av_free(tmp);
|
||||
if (err < 0)
|
||||
return err;
|
||||
} else {
|
||||
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
||||
ist->hwaccel_device_type = dev->type;
|
||||
} else if (ist->hwaccel_device_type != dev->type) {
|
||||
av_log(ist->dec_ctx, AV_LOG_ERROR, "Invalid hwaccel device "
|
||||
"specified for decoder: device %s of type %s is not "
|
||||
"usable with hwaccel %s.\n", dev->name,
|
||||
av_hwdevice_get_type_name(dev->type),
|
||||
av_hwdevice_get_type_name(ist->hwaccel_device_type));
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ist->hwaccel_id != HWACCEL_NONE)
|
||||
type = hw_device_match_type_by_hwaccel(ist->hwaccel_id);
|
||||
else
|
||||
type = hw_device_match_type_in_name(ist->dec->name);
|
||||
if (type != AV_HWDEVICE_TYPE_NONE) {
|
||||
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
||||
auto_device = 1;
|
||||
} else if (ist->hwaccel_id == HWACCEL_GENERIC) {
|
||||
type = ist->hwaccel_device_type;
|
||||
dev = hw_device_get_by_type(type);
|
||||
if (!dev) {
|
||||
hw_device_init_from_string(av_hwdevice_get_type_name(type),
|
||||
&dev);
|
||||
}
|
||||
if (!dev)
|
||||
err = hw_device_init_from_type(type, NULL, &dev);
|
||||
} else {
|
||||
// No device required.
|
||||
dev = hw_device_match_by_codec(ist->dec);
|
||||
if (!dev) {
|
||||
// No device for this codec, but not using generic hwaccel
|
||||
// and therefore may well not need one - ignore.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto_device) {
|
||||
int i;
|
||||
if (!avcodec_get_hw_config(ist->dec, 0)) {
|
||||
// Decoder does not support any hardware devices.
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; !dev; i++) {
|
||||
config = avcodec_get_hw_config(ist->dec, i);
|
||||
if (!config)
|
||||
break;
|
||||
type = config->device_type;
|
||||
dev = hw_device_get_by_type(type);
|
||||
if (dev) {
|
||||
av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto "
|
||||
"hwaccel type %s with existing device %s.\n",
|
||||
av_hwdevice_get_type_name(type), dev->name);
|
||||
}
|
||||
}
|
||||
for (i = 0; !dev; i++) {
|
||||
config = avcodec_get_hw_config(ist->dec, i);
|
||||
if (!config)
|
||||
break;
|
||||
type = config->device_type;
|
||||
// Try to make a new device of this type.
|
||||
err = hw_device_init_from_type(type, ist->hwaccel_device,
|
||||
&dev);
|
||||
if (err < 0) {
|
||||
// Can't make a device of this type.
|
||||
continue;
|
||||
}
|
||||
if (ist->hwaccel_device) {
|
||||
av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto "
|
||||
"hwaccel type %s with new device created "
|
||||
"from %s.\n", av_hwdevice_get_type_name(type),
|
||||
ist->hwaccel_device);
|
||||
} else {
|
||||
av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto "
|
||||
"hwaccel type %s with new default device.\n",
|
||||
av_hwdevice_get_type_name(type));
|
||||
}
|
||||
}
|
||||
if (dev) {
|
||||
ist->hwaccel_device_type = type;
|
||||
} else {
|
||||
av_log(ist->dec_ctx, AV_LOG_INFO, "Auto hwaccel "
|
||||
"disabled: no device found.\n");
|
||||
ist->hwaccel_id = HWACCEL_NONE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev) {
|
||||
av_log(ist->dec_ctx, AV_LOG_WARNING, "No device available "
|
||||
"for decoder (device type %s for codec %s).\n",
|
||||
av_log(ist->dec_ctx, AV_LOG_ERROR, "No device available "
|
||||
"for decoder: device type %s needed for codec %s.\n",
|
||||
av_hwdevice_get_type_name(type), ist->dec->name);
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
ist->dec_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
||||
@@ -311,24 +419,16 @@ int hw_device_setup_for_decode(InputStream *ist)
|
||||
|
||||
int hw_device_setup_for_encode(OutputStream *ost)
|
||||
{
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev;
|
||||
|
||||
type = hw_device_match_type_in_name(ost->enc->name);
|
||||
if (type != AV_HWDEVICE_TYPE_NONE) {
|
||||
dev = hw_device_get_by_type(type);
|
||||
if (!dev) {
|
||||
av_log(ost->enc_ctx, AV_LOG_WARNING, "No device available "
|
||||
"for encoder (device type %s for codec %s).\n",
|
||||
av_hwdevice_get_type_name(type), ost->enc->name);
|
||||
return 0;
|
||||
}
|
||||
dev = hw_device_match_by_codec(ost->enc);
|
||||
if (dev) {
|
||||
ost->enc_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
||||
if (!ost->enc_ctx->hw_device_ctx)
|
||||
return AVERROR(ENOMEM);
|
||||
return 0;
|
||||
} else {
|
||||
// No device required.
|
||||
// No device required, or no device available.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,20 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* CHANGES 03.2018 Taner Sener
|
||||
/*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and parent headers
|
||||
*
|
||||
* CHANGES 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - Parentheses placed around assignments in condition to prevent -Wparentheses warning
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ffmpeg.h"
|
||||
#include "cmdutils.h"
|
||||
#include "fftools_ffmpeg.h"
|
||||
#include "fftools_cmdutils.h"
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
|
||||
@@ -71,41 +76,17 @@
|
||||
}
|
||||
|
||||
const HWAccel hwaccels[] = {
|
||||
#if HAVE_VDPAU_X11
|
||||
{ "vdpau", hwaccel_decode_init, HWACCEL_VDPAU, AV_PIX_FMT_VDPAU,
|
||||
AV_HWDEVICE_TYPE_VDPAU },
|
||||
#endif
|
||||
#if CONFIG_D3D11VA
|
||||
{ "d3d11va", hwaccel_decode_init, HWACCEL_D3D11VA, AV_PIX_FMT_D3D11,
|
||||
AV_HWDEVICE_TYPE_D3D11VA },
|
||||
#endif
|
||||
#if CONFIG_DXVA2
|
||||
{ "dxva2", hwaccel_decode_init, HWACCEL_DXVA2, AV_PIX_FMT_DXVA2_VLD,
|
||||
AV_HWDEVICE_TYPE_DXVA2 },
|
||||
#endif
|
||||
#if CONFIG_VDA
|
||||
{ "vda", videotoolbox_init, HWACCEL_VDA, AV_PIX_FMT_VDA,
|
||||
AV_HWDEVICE_TYPE_NONE },
|
||||
#endif
|
||||
#if CONFIG_VIDEOTOOLBOX
|
||||
{ "videotoolbox", videotoolbox_init, HWACCEL_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX,
|
||||
AV_HWDEVICE_TYPE_NONE },
|
||||
{ "videotoolbox", videotoolbox_init, HWACCEL_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX },
|
||||
#endif
|
||||
#if CONFIG_LIBMFX
|
||||
{ "qsv", qsv_init, HWACCEL_QSV, AV_PIX_FMT_QSV,
|
||||
AV_HWDEVICE_TYPE_NONE },
|
||||
#endif
|
||||
#if CONFIG_VAAPI
|
||||
{ "vaapi", hwaccel_decode_init, HWACCEL_VAAPI, AV_PIX_FMT_VAAPI,
|
||||
AV_HWDEVICE_TYPE_VAAPI },
|
||||
{ "qsv", qsv_init, HWACCEL_QSV, AV_PIX_FMT_QSV },
|
||||
#endif
|
||||
#if CONFIG_CUVID
|
||||
{ "cuvid", cuvid_init, HWACCEL_CUVID, AV_PIX_FMT_CUDA,
|
||||
AV_HWDEVICE_TYPE_NONE },
|
||||
{ "cuvid", cuvid_init, HWACCEL_CUVID, AV_PIX_FMT_CUDA },
|
||||
#endif
|
||||
{ 0 },
|
||||
};
|
||||
int hwaccel_lax_profile_check = 0;
|
||||
AVBufferRef *hw_device_ctx;
|
||||
HWDevice *filter_hw_device;
|
||||
|
||||
@@ -146,7 +127,6 @@ static int file_overwrite = 0;
|
||||
static int no_file_overwrite = 0;
|
||||
static int do_psnr = 0;
|
||||
static int input_sync;
|
||||
static int override_ffserver = 0;
|
||||
static int input_stream_potentially_available = 0;
|
||||
static int ignore_unknown_streams = 0;
|
||||
static int copy_unknown_streams = 0;
|
||||
@@ -200,12 +180,15 @@ static void init_options(OptionsContext *o)
|
||||
|
||||
static int show_hwaccels(void *optctx, const char *opt, const char *arg)
|
||||
{
|
||||
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
|
||||
int i;
|
||||
|
||||
printf("Hardware acceleration methods:\n");
|
||||
for (i = 0; hwaccels[i].name; i++) {
|
||||
while ((type = av_hwdevice_iterate_types(type)) !=
|
||||
AV_HWDEVICE_TYPE_NONE)
|
||||
printf("%s\n", av_hwdevice_get_type_name(type));
|
||||
for (i = 0; hwaccels[i].name; i++)
|
||||
printf("%s\n", hwaccels[i].name);
|
||||
}
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -728,7 +711,8 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic)
|
||||
AVStream *st = ic->streams[i];
|
||||
AVCodecParameters *par = st->codecpar;
|
||||
InputStream *ist = av_mallocz(sizeof(*ist));
|
||||
char *framerate = NULL, *hwaccel = NULL, *hwaccel_device = NULL;
|
||||
char *framerate = NULL, *hwaccel_device = NULL;
|
||||
const char *hwaccel = NULL;
|
||||
char *hwaccel_output_format = NULL;
|
||||
char *codec_tag = NULL;
|
||||
char *next;
|
||||
@@ -792,20 +776,20 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic)
|
||||
exit_program(1);
|
||||
}
|
||||
|
||||
if (o->bitexact)
|
||||
ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT;
|
||||
|
||||
switch (par->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if(!ist->dec)
|
||||
ist->dec = avcodec_find_decoder(par->codec_id);
|
||||
#if FF_API_LOWRES
|
||||
if (av_codec_get_lowres(st->codec)) {
|
||||
av_codec_set_lowres(ist->dec_ctx, av_codec_get_lowres(st->codec));
|
||||
if (st->codec->lowres) {
|
||||
ist->dec_ctx->lowres = st->codec->lowres;
|
||||
ist->dec_ctx->width = st->codec->width;
|
||||
ist->dec_ctx->height = st->codec->height;
|
||||
ist->dec_ctx->coded_width = st->codec->coded_width;
|
||||
ist->dec_ctx->coded_height = st->codec->coded_height;
|
||||
#if FF_API_EMU_EDGE
|
||||
ist->dec_ctx->flags |= CODEC_FLAG_EMU_EDGE;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -825,11 +809,16 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic)
|
||||
|
||||
MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st);
|
||||
if (hwaccel) {
|
||||
// The NVDEC hwaccels use a CUDA device, so remap the name here.
|
||||
if (!strcmp(hwaccel, "nvdec"))
|
||||
hwaccel = "cuda";
|
||||
|
||||
if (!strcmp(hwaccel, "none"))
|
||||
ist->hwaccel_id = HWACCEL_NONE;
|
||||
else if (!strcmp(hwaccel, "auto"))
|
||||
ist->hwaccel_id = HWACCEL_AUTO;
|
||||
else {
|
||||
enum AVHWDeviceType type;
|
||||
int i;
|
||||
for (i = 0; hwaccels[i].name; i++) {
|
||||
if (!strcmp(hwaccels[i].name, hwaccel)) {
|
||||
@@ -838,10 +827,23 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic)
|
||||
}
|
||||
}
|
||||
|
||||
if (!ist->hwaccel_id) {
|
||||
type = av_hwdevice_find_type_by_name(hwaccel);
|
||||
if (type != AV_HWDEVICE_TYPE_NONE) {
|
||||
ist->hwaccel_id = HWACCEL_GENERIC;
|
||||
ist->hwaccel_device_type = type;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ist->hwaccel_id) {
|
||||
av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n",
|
||||
hwaccel);
|
||||
av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: ");
|
||||
type = AV_HWDEVICE_TYPE_NONE;
|
||||
while ((type = av_hwdevice_iterate_types(type)) !=
|
||||
AV_HWDEVICE_TYPE_NONE)
|
||||
av_log(NULL, AV_LOG_FATAL, "%s ",
|
||||
av_hwdevice_get_type_name(type));
|
||||
for (i = 0; hwaccels[i].name; i++)
|
||||
av_log(NULL, AV_LOG_FATAL, "%s ", hwaccels[i].name);
|
||||
av_log(NULL, AV_LOG_FATAL, "\n");
|
||||
@@ -982,6 +984,21 @@ static int open_input_file(OptionsContext *o, const char *filename)
|
||||
char * data_codec_name = NULL;
|
||||
int scan_all_pmts_set = 0;
|
||||
|
||||
if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) {
|
||||
o->stop_time = INT64_MAX;
|
||||
av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n");
|
||||
}
|
||||
|
||||
if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) {
|
||||
int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time;
|
||||
if (o->stop_time <= start_time) {
|
||||
av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n");
|
||||
exit_program(1);
|
||||
} else {
|
||||
o->recording_time = o->stop_time - start_time;
|
||||
}
|
||||
}
|
||||
|
||||
if (o->format) {
|
||||
if (!(file_iformat = av_find_input_format(o->format))) {
|
||||
av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format);
|
||||
@@ -1001,7 +1018,6 @@ static int open_input_file(OptionsContext *o, const char *filename)
|
||||
print_error(filename, AVERROR(ENOMEM));
|
||||
exit_program(1);
|
||||
}
|
||||
ic->flags |= AVFMT_FLAG_KEEP_SIDE_DATA;
|
||||
if (o->nb_audio_sample_rate) {
|
||||
av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0);
|
||||
}
|
||||
@@ -1036,25 +1052,23 @@ static int open_input_file(OptionsContext *o, const char *filename)
|
||||
MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s");
|
||||
MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d");
|
||||
|
||||
ic->video_codec_id = video_codec_name ?
|
||||
find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0)->id : AV_CODEC_ID_NONE;
|
||||
ic->audio_codec_id = audio_codec_name ?
|
||||
find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0)->id : AV_CODEC_ID_NONE;
|
||||
ic->subtitle_codec_id= subtitle_codec_name ?
|
||||
find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0)->id : AV_CODEC_ID_NONE;
|
||||
ic->data_codec_id = data_codec_name ?
|
||||
find_codec_or_die(data_codec_name, AVMEDIA_TYPE_DATA, 0)->id : AV_CODEC_ID_NONE;
|
||||
|
||||
if (video_codec_name)
|
||||
av_format_set_video_codec (ic, find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0));
|
||||
ic->video_codec = find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0);
|
||||
if (audio_codec_name)
|
||||
av_format_set_audio_codec (ic, find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0));
|
||||
ic->audio_codec = find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0);
|
||||
if (subtitle_codec_name)
|
||||
av_format_set_subtitle_codec(ic, find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0));
|
||||
ic->subtitle_codec = find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0);
|
||||
if (data_codec_name)
|
||||
av_format_set_data_codec(ic, find_codec_or_die(data_codec_name, AVMEDIA_TYPE_DATA, 0));
|
||||
ic->data_codec = find_codec_or_die(data_codec_name , AVMEDIA_TYPE_DATA , 0);
|
||||
|
||||
ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE;
|
||||
ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE;
|
||||
ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE;
|
||||
ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE;
|
||||
|
||||
ic->flags |= AVFMT_FLAG_NONBLOCK;
|
||||
if (o->bitexact)
|
||||
ic->flags |= AVFMT_FLAG_BITEXACT;
|
||||
ic->interrupt_callback = int_cb;
|
||||
|
||||
if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
|
||||
@@ -1156,7 +1170,7 @@ static int open_input_file(OptionsContext *o, const char *filename)
|
||||
f->loop = o->loop;
|
||||
f->duration = 0;
|
||||
f->time_base = (AVRational){ 1, 1 };
|
||||
#if HAVE_PTHREADS
|
||||
#if HAVE_THREADS
|
||||
f->thread_queue_size = o->thread_queue_size > 0 ? o->thread_queue_size : 8;
|
||||
#endif
|
||||
|
||||
@@ -1267,7 +1281,7 @@ static int choose_encoder(OptionsContext *o, AVFormatContext *s, OutputStream *o
|
||||
if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st);
|
||||
if (!codec_name) {
|
||||
ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->filename,
|
||||
ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url,
|
||||
NULL, ost->st->codecpar->codec_type);
|
||||
ost->enc = avcodec_find_encoder(ost->st->codecpar->codec_id);
|
||||
if (!ost->enc) {
|
||||
@@ -1376,6 +1390,10 @@ static OutputStream *new_output_stream(OptionsContext *o, AVFormatContext *oc, e
|
||||
ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL);
|
||||
}
|
||||
|
||||
|
||||
if (o->bitexact)
|
||||
ost->enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT;
|
||||
|
||||
MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st);
|
||||
if (time_base) {
|
||||
AVRational q;
|
||||
@@ -1668,7 +1686,7 @@ static OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, in
|
||||
av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n");
|
||||
exit_program(1);
|
||||
}
|
||||
av_codec_set_chroma_intra_matrix(video_enc, p);
|
||||
video_enc->chroma_intra_matrix = p;
|
||||
parse_matrix_coeffs(p, chroma_intra_matrix);
|
||||
}
|
||||
MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st);
|
||||
@@ -1988,57 +2006,6 @@ static int copy_chapters(InputFile *ifile, OutputFile *ofile, int copy_metadata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_ffserver_streams(OptionsContext *o, AVFormatContext *s, const char *filename)
|
||||
{
|
||||
int i, err;
|
||||
AVFormatContext *ic = avformat_alloc_context();
|
||||
|
||||
ic->flags |= AVFMT_FLAG_KEEP_SIDE_DATA;
|
||||
ic->interrupt_callback = int_cb;
|
||||
err = avformat_open_input(&ic, filename, NULL, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
/* copy stream format */
|
||||
for(i=0;i<ic->nb_streams;i++) {
|
||||
AVStream *st;
|
||||
OutputStream *ost;
|
||||
AVCodec *codec;
|
||||
const char *enc_config;
|
||||
|
||||
codec = avcodec_find_encoder(ic->streams[i]->codecpar->codec_id);
|
||||
if (!codec) {
|
||||
av_log(s, AV_LOG_ERROR, "no encoder found for codec id %i\n", ic->streams[i]->codecpar->codec_id);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (codec->type == AVMEDIA_TYPE_AUDIO)
|
||||
opt_audio_codec(o, "c:a", codec->name);
|
||||
else if (codec->type == AVMEDIA_TYPE_VIDEO)
|
||||
opt_video_codec(o, "c:v", codec->name);
|
||||
ost = new_output_stream(o, s, codec->type, -1);
|
||||
st = ost->st;
|
||||
|
||||
avcodec_get_context_defaults3(st->codec, codec);
|
||||
enc_config = av_stream_get_recommended_encoder_configuration(ic->streams[i]);
|
||||
if (enc_config) {
|
||||
AVDictionary *opts = NULL;
|
||||
av_dict_parse_string(&opts, enc_config, "=", ",", 0);
|
||||
av_opt_set_dict2(st->codec, &opts, AV_OPT_SEARCH_CHILDREN);
|
||||
av_dict_free(&opts);
|
||||
}
|
||||
|
||||
if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && !ost->stream_copy)
|
||||
choose_sample_fmt(st, codec);
|
||||
else if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !ost->stream_copy)
|
||||
choose_pixel_fmt(st, st->codec, codec, st->codecpar->format);
|
||||
avcodec_copy_context(ost->enc_ctx, st->codec);
|
||||
if (enc_config)
|
||||
av_dict_parse_string(&ost->encoder_opts, enc_config, "=", ",", 0);
|
||||
}
|
||||
|
||||
avformat_close_input(&ic);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void init_output_filter(OutputFilter *ofilter, OptionsContext *o,
|
||||
AVFormatContext *oc)
|
||||
{
|
||||
@@ -2097,7 +2064,6 @@ static int open_output_file(OptionsContext *o, const char *filename)
|
||||
{
|
||||
AVFormatContext *oc;
|
||||
int i, j, err;
|
||||
AVOutputFormat *file_oformat;
|
||||
OutputFile *of;
|
||||
OutputStream *ost;
|
||||
InputStream *ist;
|
||||
@@ -2146,7 +2112,6 @@ static int open_output_file(OptionsContext *o, const char *filename)
|
||||
if (o->recording_time != INT64_MAX)
|
||||
oc->duration = o->recording_time;
|
||||
|
||||
file_oformat= oc->oformat;
|
||||
oc->interrupt_callback = int_cb;
|
||||
|
||||
e = av_dict_get(o->g->format_opts, "fflags", NULL, 0);
|
||||
@@ -2154,6 +2119,10 @@ static int open_output_file(OptionsContext *o, const char *filename)
|
||||
const AVOption *o = av_opt_find(oc, "fflags", NULL, 0, 0);
|
||||
av_opt_eval_flags(oc, o, e->value, &format_flags);
|
||||
}
|
||||
if (o->bitexact) {
|
||||
format_flags |= AVFMT_FLAG_BITEXACT;
|
||||
oc->flags |= AVFMT_FLAG_BITEXACT;
|
||||
}
|
||||
|
||||
/* create streams for all unlabeled output pads */
|
||||
for (i = 0; i < nb_filtergraphs; i++) {
|
||||
@@ -2173,47 +2142,7 @@ static int open_output_file(OptionsContext *o, const char *filename)
|
||||
}
|
||||
}
|
||||
|
||||
/* ffserver seeking with date=... needs a date reference */
|
||||
if (!strcmp(file_oformat->name, "ffm") &&
|
||||
!(format_flags & AVFMT_FLAG_BITEXACT) &&
|
||||
av_strstart(filename, "http:", NULL)) {
|
||||
int err = parse_option(o, "metadata", "creation_time=now", options);
|
||||
if (err < 0) {
|
||||
print_error(filename, err);
|
||||
exit_program(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(file_oformat->name, "ffm") && !override_ffserver &&
|
||||
av_strstart(filename, "http:", NULL)) {
|
||||
int j;
|
||||
/* special case for files sent to ffserver: we get the stream
|
||||
parameters from ffserver */
|
||||
int err = read_ffserver_streams(o, oc, filename);
|
||||
if (err < 0) {
|
||||
print_error(filename, err);
|
||||
exit_program(1);
|
||||
}
|
||||
for(j = nb_output_streams - oc->nb_streams; j < nb_output_streams; j++) {
|
||||
ost = output_streams[j];
|
||||
for (i = 0; i < nb_input_streams; i++) {
|
||||
ist = input_streams[i];
|
||||
if(ist->st->codecpar->codec_type == ost->st->codecpar->codec_type){
|
||||
ost->sync_ist= ist;
|
||||
ost->source_index= i;
|
||||
if(ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ost->avfilter = av_strdup("anull");
|
||||
if(ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) ost->avfilter = av_strdup("null");
|
||||
ist->discard = 0;
|
||||
ist->st->discard = ist->user_set_discard;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!ost->sync_ist){
|
||||
av_log(NULL, AV_LOG_FATAL, "Missing %s stream which is required by this ffm\n", av_get_media_type_string(ost->st->codecpar->codec_type));
|
||||
exit_program(1);
|
||||
}
|
||||
}
|
||||
} else if (!o->nb_stream_maps) {
|
||||
if (!o->nb_stream_maps) {
|
||||
char *subtitle_codec_name = NULL;
|
||||
/* pick the "best" stream of each type */
|
||||
|
||||
@@ -2415,7 +2344,7 @@ loop_end:
|
||||
#endif
|
||||
|
||||
if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) {
|
||||
av_dump_format(oc, nb_output_files - 1, oc->filename, 1);
|
||||
av_dump_format(oc, nb_output_files - 1, oc->url, 1);
|
||||
av_log(NULL, AV_LOG_ERROR, "Output file #%d does not contain any stream\n", nb_output_files - 1);
|
||||
exit_program(1);
|
||||
}
|
||||
@@ -2547,8 +2476,8 @@ loop_end:
|
||||
|
||||
/* check filename in case of an image number is expected */
|
||||
if (oc->oformat->flags & AVFMT_NEEDNUMBER) {
|
||||
if (!av_filename_number_test(oc->filename)) {
|
||||
print_error(oc->filename, AVERROR(EINVAL));
|
||||
if (!av_filename_number_test(oc->url)) {
|
||||
print_error(oc->url, AVERROR(EINVAL));
|
||||
exit_program(1);
|
||||
}
|
||||
}
|
||||
@@ -3195,7 +3124,7 @@ void show_help_default(const char *opt, const char *arg)
|
||||
" -h -- print basic options\n"
|
||||
" -h long -- print more options\n"
|
||||
" -h full -- print all options (including all format and codec specific options, very long)\n"
|
||||
" -h type=name -- print all options for the named decoder/encoder/demuxer/muxer/filter\n"
|
||||
" -h type=name -- print all options for the named decoder/encoder/demuxer/muxer/filter/bsf\n"
|
||||
" See man %s for detailed description of the options.\n"
|
||||
"\n", program_name);
|
||||
|
||||
@@ -3240,6 +3169,7 @@ void show_help_default(const char *opt, const char *arg)
|
||||
#endif
|
||||
show_help_children(swr_get_class(), AV_OPT_FLAG_AUDIO_PARAM);
|
||||
show_help_children(avfilter_get_class(), AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM);
|
||||
show_help_children(av_bsf_get_class(), AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_BSF_PARAM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3409,7 +3339,7 @@ const OptionDef options[] = {
|
||||
OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(recording_time) },
|
||||
"record or transcode \"duration\" seconds of audio/video",
|
||||
"duration" },
|
||||
{ "to", HAS_ARG | OPT_TIME | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(stop_time) },
|
||||
{ "to", HAS_ARG | OPT_TIME | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(stop_time) },
|
||||
"record or transcode stop time", "time_stop" },
|
||||
{ "fs", HAS_ARG | OPT_INT64 | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(limit_filesize) },
|
||||
"set the limit file size in bytes", "limit_size" },
|
||||
@@ -3477,6 +3407,9 @@ const OptionDef options[] = {
|
||||
{ "shortest", OPT_BOOL | OPT_EXPERT | OPT_OFFSET |
|
||||
OPT_OUTPUT, { .off = OFFSET(shortest) },
|
||||
"finish encoding within shortest input" },
|
||||
{ "bitexact", OPT_BOOL | OPT_EXPERT | OPT_OFFSET |
|
||||
OPT_OUTPUT | OPT_INPUT, { .off = OFFSET(bitexact) },
|
||||
"bitexact mode" },
|
||||
{ "apad", OPT_STRING | HAS_ARG | OPT_SPEC |
|
||||
OPT_OUTPUT, { .off = OFFSET(apad) },
|
||||
"audio pad", "" },
|
||||
@@ -3535,7 +3468,7 @@ const OptionDef options[] = {
|
||||
{ "debug_ts", OPT_BOOL | OPT_EXPERT, { &debug_ts },
|
||||
"print timestamp debugging info" },
|
||||
{ "max_error_rate", HAS_ARG | OPT_FLOAT, { &max_error_rate },
|
||||
"maximum error rate", "ratio of errors (0.0: no errors, 1.0: 100% errors) above which ffmpeg returns an error instead of success." },
|
||||
"ratio of errors (0.0: no errors, 1.0: 100% errors) above which ffmpeg returns an error instead of success.", "maximum error rate" },
|
||||
{ "discard", OPT_STRING | HAS_ARG | OPT_SPEC |
|
||||
OPT_INPUT, { .off = OFFSET(discard) },
|
||||
"discard", "" },
|
||||
@@ -3637,7 +3570,7 @@ const OptionDef options[] = {
|
||||
{ "hwaccel_output_format", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT |
|
||||
OPT_SPEC | OPT_INPUT, { .off = OFFSET(hwaccel_output_formats) },
|
||||
"select output format used with HW accelerated decoding", "format" },
|
||||
#if CONFIG_VDA || CONFIG_VIDEOTOOLBOX
|
||||
#if CONFIG_VIDEOTOOLBOX
|
||||
{ "videotoolbox_pixfmt", HAS_ARG | OPT_STRING | OPT_EXPERT, { &videotoolbox_pixfmt}, "" },
|
||||
#endif
|
||||
{ "hwaccels", OPT_EXIT, { .func_arg = show_hwaccels },
|
||||
@@ -3645,8 +3578,6 @@ const OptionDef options[] = {
|
||||
{ "autorotate", HAS_ARG | OPT_BOOL | OPT_SPEC |
|
||||
OPT_EXPERT | OPT_INPUT, { .off = OFFSET(autorotate) },
|
||||
"automatically insert correct rotate filters" },
|
||||
{ "hwaccel_lax_profile_check", OPT_BOOL | OPT_EXPERT, { &hwaccel_lax_profile_check},
|
||||
"attempt to decode anyway if HW accelerated decoder's supported profiles do not exactly match the stream" },
|
||||
|
||||
/* audio options */
|
||||
{ "aframes", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_frames },
|
||||
@@ -3704,8 +3635,6 @@ const OptionDef options[] = {
|
||||
"set the maximum demux-decode delay", "seconds" },
|
||||
{ "muxpreload", OPT_FLOAT | HAS_ARG | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(mux_preload) },
|
||||
"set the initial demux-decode delay", "seconds" },
|
||||
{ "override_ffserver", OPT_BOOL | OPT_EXPERT | OPT_OUTPUT, { &override_ffserver },
|
||||
"override the options from ffserver", "" },
|
||||
{ "sdp_file", HAS_ARG | OPT_EXPERT | OPT_OUTPUT, { .func_arg = opt_sdp_file },
|
||||
"specify a file in which to print sdp information", "file" },
|
||||
|
||||
@@ -1,175 +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/>.
|
||||
*/
|
||||
|
||||
#include "log.h"
|
||||
|
||||
/** Global reference to the virtual machine running */
|
||||
static JavaVM *globalVm;
|
||||
|
||||
/** Global reference of Log class in Java side */
|
||||
static jclass logClass;
|
||||
|
||||
/** Global reference of forward log method in Java side */
|
||||
static jmethodID logMethod;
|
||||
|
||||
/** file descriptor of the pipe created */
|
||||
static int pipeFd[2];
|
||||
|
||||
/** log collector thread variable */
|
||||
static pthread_t logThread;
|
||||
|
||||
/** Holds whether log collector thread is enabled or not */
|
||||
static int logThreadEnabled = 1;
|
||||
|
||||
/** Full name of the Java class that owns native functions in this file. */
|
||||
const char *logClassName = "com/arthenica/mobileffmpeg/Log";
|
||||
|
||||
/** Prototypes of native functions defined by this file. */
|
||||
JNINativeMethod logMethods[] = {
|
||||
{"startNativeCollector", "()I", (void*) Java_com_arthenica_mobileffmpeg_Log_startNativeCollector},
|
||||
{"stopNativeCollector", "()I", (void*) Java_com_arthenica_mobileffmpeg_Log_stopNativeCollector}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when 'ffmpeglog' native library is loaded.
|
||||
*
|
||||
* \param vm pointer to the running virtual machine
|
||||
* \param reserved reserved
|
||||
* \return JNI version needed by 'ffmpeglog' library
|
||||
*/
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
LOGE("OnLoad failed to GetEnv for class %s.", logClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
(*env)->GetJavaVM(env, &globalVm);
|
||||
|
||||
jclass clazz = (*env)->FindClass(env, logClassName);
|
||||
if (clazz == NULL) {
|
||||
LOGE("OnLoad failed to FindClass %s.", logClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
logMethod = (*env)->GetStaticMethodID(env, clazz, "log", "([B)V");
|
||||
if (logMethod == NULL) {
|
||||
LOGE("OnLoad thread failed to GetMethodID for %s.", "log");
|
||||
(*globalVm)->DetachCurrentThread(globalVm);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if ((*env)->RegisterNatives(env, clazz, logMethods, 2) < 0) {
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.", logClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
logClass = (jclass) ((*env)->NewGlobalRef(env, clazz));
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Native log collector main thread function.
|
||||
*/
|
||||
static void *logThreadFunction() {
|
||||
int readSize;
|
||||
char buffer[512];
|
||||
|
||||
JNIEnv *env;
|
||||
jint getEnvRc = (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
|
||||
if (getEnvRc != JNI_OK) {
|
||||
if (getEnvRc != JNI_EDETACHED) {
|
||||
LOGE("Log thread failed to GetEnv for class %s with rc %d.", logClassName, getEnvRc);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if ((*globalVm)->AttachCurrentThread(globalVm, &env, NULL) != 0) {
|
||||
LOGE("Log thread failed to AttachCurrentThread for class %s.", logClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
LOGI("Native log thread started.");
|
||||
|
||||
while(logThreadEnabled && ((readSize = (int)read(pipeFd[0], buffer, sizeof(buffer) - 1)) > 0)) {
|
||||
if (readSize > 0) {
|
||||
if (buffer[readSize - 1] == '\n') {
|
||||
readSize--;
|
||||
}
|
||||
buffer[readSize] = 0; /* add null-terminator */
|
||||
|
||||
jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, readSize);
|
||||
(*env)->SetByteArrayRegion(env, byteArray, 0, readSize, (jbyte *)buffer);
|
||||
(*env)->CallStaticVoidMethod(env, logClass, logMethod, byteArray);
|
||||
(*env)->DeleteLocalRef(env, byteArray);
|
||||
}
|
||||
}
|
||||
|
||||
(*globalVm)->DetachCurrentThread(globalVm);
|
||||
|
||||
LOGI("Native log thread stopped.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts native log collector. Native log collector creates a pipe and redirects stdout and stderr to it. Then starts
|
||||
* a thread, reads data written to this pipe and forwards it to the Java side.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the object on which this method is invoked
|
||||
* \return zero on success, non-zero if an error occurs
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Log_startNativeCollector(JNIEnv *env, jobject object) {
|
||||
|
||||
/* make stdout line-buffered and stderr unbuffered */
|
||||
setvbuf(stdout, 0, _IOLBF, 0);
|
||||
setvbuf(stderr, 0, _IONBF, 0);
|
||||
|
||||
/* create the pipe and redirect stdout and stderr */
|
||||
pipe(pipeFd);
|
||||
dup2(pipeFd[1], 1);
|
||||
dup2(pipeFd[1], 2);
|
||||
|
||||
/* spawn the logging thread */
|
||||
int rc = pthread_create(&logThread, 0, logThreadFunction, 0);
|
||||
if (rc != 0) {
|
||||
LOGE("Failed to create native log thread (rc=%d).", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a stop request for native log collector thread. Please note that when this function returns collector
|
||||
* thread might be still alive.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the object on which this method is invoked
|
||||
* \return zero on success, non-zero if an error occurs
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Log_stopNativeCollector(JNIEnv *env, jobject object) {
|
||||
logThreadEnabled = 0;
|
||||
|
||||
LOGI("Stopping native log thread\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,61 +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/>.
|
||||
*/
|
||||
|
||||
#ifndef MOBILEFFMPEG_LOG_H
|
||||
#define MOBILEFFMPEG_LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
/** Defines tag used for Android logging. */
|
||||
#define LIB_NAME "mobile-ffmpeg"
|
||||
|
||||
/** Verbose Android logging macro. */
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Debug Android logging macro. */
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Info Android logging macro. */
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Warn Android logging macro. */
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Error Android logging macro. */
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Log
|
||||
* Method: startNativeCollector
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Log_startNativeCollector(JNIEnv *, jobject);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Log
|
||||
* Method: stopNativeCollector
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Log_stopNativeCollector(JNIEnv *, jobject);
|
||||
|
||||
#endif /* MOBILEFFMPEG_LOG_H */
|
||||
@@ -17,20 +17,441 @@
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mobileffmpeg.h"
|
||||
/*
|
||||
* CHANGES 09.2018
|
||||
* --------------------------------------------------------
|
||||
* - Merged with mobileffmpeg_config
|
||||
*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - Copied methods with avutil_log_ prefix from libavutil/log.c
|
||||
*/
|
||||
|
||||
/** Forward declaration for function defined in ffmpeg.c */
|
||||
#include <pthread.h>
|
||||
|
||||
#include "libavutil/bprint.h"
|
||||
#include "mobileffmpeg.h"
|
||||
#include "fftools_ffmpeg.h"
|
||||
|
||||
/** Callback data structure */
|
||||
struct CallbackData {
|
||||
int type; // 1 (log callback) or 2 (statistics callback)
|
||||
|
||||
int logLevel; // log level
|
||||
char *logData; // log data
|
||||
|
||||
int statisticsFrameNumber; // statistics frame number
|
||||
float statisticsFps; // statistics fps
|
||||
float statisticsQuality; // statistics quality
|
||||
int64_t statisticsSize; // statistics size
|
||||
int statisticsTime; // statistics time
|
||||
double statisticsBitrate; // statistics bitrate
|
||||
double statisticsSpeed; // statistics speed
|
||||
|
||||
struct CallbackData *next;
|
||||
};
|
||||
|
||||
/** Redirection control variables */
|
||||
pthread_mutex_t lockMutex;
|
||||
pthread_mutex_t monitorMutex;
|
||||
pthread_cond_t monitorCondition;
|
||||
|
||||
pthread_t callbackThread;
|
||||
int redirectionEnabled;
|
||||
|
||||
struct CallbackData *callbackDataHead;
|
||||
struct CallbackData *callbackDataTail;
|
||||
|
||||
/** Global reference to the virtual machine running */
|
||||
static JavaVM *globalVm;
|
||||
|
||||
/** Global reference of Config class in Java */
|
||||
static jclass configClass;
|
||||
|
||||
/** Global reference of log redirection method in Java */
|
||||
static jmethodID logMethod;
|
||||
|
||||
/** Global reference of statistics redirection method in Java */
|
||||
static jmethodID statisticsMethod;
|
||||
|
||||
/** Full name of the Config class */
|
||||
const char *configClassName = "com/arthenica/mobileffmpeg/Config";
|
||||
|
||||
/** Prototypes of native functions defined by Config class. */
|
||||
JNINativeMethod configMethods[] = {
|
||||
{"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection},
|
||||
{"disableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection},
|
||||
{"setNativeLogLevel", "(I)V", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel},
|
||||
{"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel}
|
||||
};
|
||||
|
||||
/** Prototypes of native functions defined by FFmpeg class. */
|
||||
JNINativeMethod ffmpegMethods[] = {
|
||||
{"getNativeFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion},
|
||||
{"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeVersion},
|
||||
{"nativeExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeExecute},
|
||||
{"nativeCancel", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeCancel}
|
||||
};
|
||||
|
||||
/** Forward declaration for function defined in fftools_ffmpeg.c */
|
||||
int execute(int argc, char **argv);
|
||||
|
||||
/** Full name of the Java class that owns native functions in this file. */
|
||||
const char *ffmpegClassName = "com/arthenica/mobileffmpeg/FFmpeg";
|
||||
/** DEFINES LINE SIZE USED FOR LOGGING */
|
||||
#define LOG_LINE_SIZE 1024
|
||||
|
||||
/** Prototypes of native functions defined by this file. */
|
||||
JNINativeMethod ffmpegMethods[] = {
|
||||
{"getFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_FFmpeg_getFFmpegVersion},
|
||||
{"getVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_FFmpeg_getVersion},
|
||||
{"execute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_FFmpeg_execute}
|
||||
};
|
||||
static const char *avutil_log_get_level_str(int level) {
|
||||
switch (level) {
|
||||
case AV_LOG_QUIET:
|
||||
return "quiet";
|
||||
case AV_LOG_DEBUG:
|
||||
return "debug";
|
||||
case AV_LOG_VERBOSE:
|
||||
return "verbose";
|
||||
case AV_LOG_INFO:
|
||||
return "info";
|
||||
case AV_LOG_WARNING:
|
||||
return "warning";
|
||||
case AV_LOG_ERROR:
|
||||
return "error";
|
||||
case AV_LOG_FATAL:
|
||||
return "fatal";
|
||||
case AV_LOG_PANIC:
|
||||
return "panic";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static void avutil_log_format_line(void *avcl, int level, const char *fmt, va_list vl, AVBPrint part[4], int *print_prefix) {
|
||||
int flags = av_log_get_flags();
|
||||
AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
|
||||
av_bprint_init(part+0, 0, 1);
|
||||
av_bprint_init(part+1, 0, 1);
|
||||
av_bprint_init(part+2, 0, 1);
|
||||
av_bprint_init(part+3, 0, 65536);
|
||||
|
||||
if (*print_prefix && avc) {
|
||||
if (avc->parent_log_context_offset) {
|
||||
AVClass** parent = *(AVClass ***) (((uint8_t *) avcl) +
|
||||
avc->parent_log_context_offset);
|
||||
if (parent && *parent) {
|
||||
av_bprintf(part+0, "[%s @ %p] ",
|
||||
(*parent)->item_name(parent), parent);
|
||||
}
|
||||
}
|
||||
av_bprintf(part+1, "[%s @ %p] ",
|
||||
avc->item_name(avcl), avcl);
|
||||
}
|
||||
|
||||
if (*print_prefix && (level > AV_LOG_QUIET) && (flags & AV_LOG_PRINT_LEVEL))
|
||||
av_bprintf(part+2, "[%s] ", avutil_log_get_level_str(level));
|
||||
|
||||
av_vbprintf(part+3, fmt, vl);
|
||||
|
||||
if(*part[0].str || *part[1].str || *part[2].str || *part[3].str) {
|
||||
char lastc = part[3].len && part[3].len <= part[3].size ? part[3].str[part[3].len - 1] : 0;
|
||||
*print_prefix = lastc == '\n' || lastc == '\r';
|
||||
}
|
||||
}
|
||||
|
||||
static void avutil_log_sanitize(uint8_t *line) {
|
||||
while(*line){
|
||||
if(*line < 0x08 || (*line > 0x0D && *line < 0x20))
|
||||
*line='?';
|
||||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
void mutexInit() {
|
||||
pthread_mutexattr_t attributes;
|
||||
pthread_mutexattr_init(&attributes);
|
||||
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
|
||||
|
||||
pthread_mutex_init(&lockMutex, &attributes);
|
||||
pthread_mutexattr_destroy(&attributes);
|
||||
}
|
||||
|
||||
void monitorInit() {
|
||||
pthread_mutexattr_t attributes;
|
||||
pthread_mutexattr_init(&attributes);
|
||||
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
|
||||
|
||||
pthread_condattr_t cattributes;
|
||||
pthread_condattr_init(&cattributes);
|
||||
pthread_condattr_setpshared(&cattributes, PTHREAD_PROCESS_PRIVATE);
|
||||
|
||||
pthread_mutex_init(&monitorMutex, &attributes);
|
||||
pthread_mutexattr_destroy(&attributes);
|
||||
|
||||
pthread_cond_init(&monitorCondition, &cattributes);
|
||||
pthread_condattr_destroy(&cattributes);
|
||||
}
|
||||
|
||||
void mutexUnInit() {
|
||||
pthread_mutex_destroy(&lockMutex);
|
||||
}
|
||||
|
||||
void monitorUnInit() {
|
||||
pthread_mutex_destroy(&monitorMutex);
|
||||
pthread_cond_destroy(&monitorCondition);
|
||||
}
|
||||
|
||||
void mutexLock() {
|
||||
pthread_mutex_lock(&lockMutex);
|
||||
}
|
||||
|
||||
void mutexUnlock() {
|
||||
pthread_mutex_unlock(&lockMutex);
|
||||
}
|
||||
|
||||
void monitorWait(int milliSeconds) {
|
||||
struct timeval tp;
|
||||
struct timespec ts;
|
||||
int rc;
|
||||
|
||||
rc = gettimeofday(&tp, NULL);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
|
||||
ts.tv_sec = tp.tv_sec;
|
||||
ts.tv_nsec = tp.tv_usec * 1000;
|
||||
ts.tv_sec += milliSeconds / 1000;
|
||||
ts.tv_nsec += (milliSeconds % 1000)*1000000;
|
||||
|
||||
pthread_mutex_lock(&monitorMutex);
|
||||
pthread_cond_timedwait(&monitorCondition, &monitorMutex, &ts);
|
||||
pthread_mutex_unlock(&monitorMutex);
|
||||
}
|
||||
|
||||
void monitorNotify() {
|
||||
pthread_mutex_lock(&monitorMutex);
|
||||
pthread_cond_signal(&monitorCondition);
|
||||
pthread_mutex_unlock(&monitorMutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds log data to the end of callback data list.
|
||||
*/
|
||||
void logCallbackDataAdd(int level, const char *data) {
|
||||
|
||||
// CREATE DATA STRUCT FIRST
|
||||
struct CallbackData *newData = (struct CallbackData*)malloc(sizeof(struct CallbackData));
|
||||
newData->type = 1;
|
||||
newData->logLevel = level;
|
||||
size_t dataSize = strlen(data) + 1;
|
||||
newData->logData = (char*)malloc(dataSize);
|
||||
memcpy(newData->logData, data, dataSize);
|
||||
newData->next = NULL;
|
||||
|
||||
mutexLock();
|
||||
|
||||
// INSERT IT TO THE END OF QUEUE
|
||||
if (callbackDataTail == NULL) {
|
||||
callbackDataTail = newData;
|
||||
|
||||
if (callbackDataHead != NULL) {
|
||||
LOGE("Dangling callback data head detected. This can cause memory leak.");
|
||||
} else {
|
||||
callbackDataHead = newData;
|
||||
}
|
||||
} else {
|
||||
struct CallbackData *oldTail = callbackDataTail;
|
||||
oldTail->next = newData;
|
||||
|
||||
callbackDataTail = newData;
|
||||
}
|
||||
|
||||
mutexUnlock();
|
||||
|
||||
monitorNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds statistics data to the end of callback data list.
|
||||
*/
|
||||
void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
|
||||
|
||||
// CREATE DATA STRUCT FIRST
|
||||
struct CallbackData *newData = (struct CallbackData*)malloc(sizeof(struct CallbackData));
|
||||
newData->type = 2;
|
||||
newData->statisticsFrameNumber = frameNumber;
|
||||
newData->statisticsFps = fps;
|
||||
newData->statisticsQuality = quality;
|
||||
newData->statisticsSize = size;
|
||||
newData->statisticsTime = time;
|
||||
newData->statisticsBitrate = bitrate;
|
||||
newData->statisticsSpeed = speed;
|
||||
|
||||
newData->next = NULL;
|
||||
|
||||
mutexLock();
|
||||
|
||||
// INSERT IT TO THE END OF QUEUE
|
||||
if (callbackDataTail == NULL) {
|
||||
callbackDataTail = newData;
|
||||
|
||||
if (callbackDataHead != NULL) {
|
||||
LOGE("Dangling callback data head detected. This can cause memory leak.");
|
||||
} else {
|
||||
callbackDataHead = newData;
|
||||
}
|
||||
} else {
|
||||
struct CallbackData *oldTail = callbackDataTail;
|
||||
oldTail->next = newData;
|
||||
|
||||
callbackDataTail = newData;
|
||||
}
|
||||
|
||||
mutexUnlock();
|
||||
|
||||
monitorNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes head of callback data list.
|
||||
*/
|
||||
struct CallbackData *callbackDataRemove() {
|
||||
struct CallbackData *currentData;
|
||||
|
||||
mutexLock();
|
||||
|
||||
if (callbackDataHead == NULL) {
|
||||
currentData = NULL;
|
||||
} else {
|
||||
currentData = callbackDataHead;
|
||||
|
||||
struct CallbackData *nextHead = currentData->next;
|
||||
if (nextHead == NULL) {
|
||||
if (callbackDataHead != callbackDataTail) {
|
||||
LOGE("Head and tail callback data pointers do not match for single callback data element. This can cause memory leak.");
|
||||
} else {
|
||||
callbackDataTail = NULL;
|
||||
}
|
||||
callbackDataHead = NULL;
|
||||
|
||||
} else {
|
||||
callbackDataHead = nextHead;
|
||||
}
|
||||
}
|
||||
|
||||
mutexUnlock();
|
||||
|
||||
return currentData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for FFmpeg logs.
|
||||
*
|
||||
* \param pointer to AVClass struct
|
||||
* \param level
|
||||
* \param format
|
||||
* \param arguments
|
||||
*/
|
||||
void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs) {
|
||||
char line[LOG_LINE_SIZE];
|
||||
AVBPrint part[4];
|
||||
int print_prefix = 1;
|
||||
|
||||
if (level >= 0) {
|
||||
level &= 0xff;
|
||||
}
|
||||
|
||||
avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix);
|
||||
snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
|
||||
|
||||
avutil_log_sanitize(part[0].str);
|
||||
logCallbackDataAdd(level, part[0].str);
|
||||
avutil_log_sanitize(part[1].str);
|
||||
logCallbackDataAdd(level, part[1].str);
|
||||
avutil_log_sanitize(part[2].str);
|
||||
logCallbackDataAdd(level, part[2].str);
|
||||
avutil_log_sanitize(part[3].str);
|
||||
logCallbackDataAdd(level, part[3].str);
|
||||
|
||||
av_bprint_finalize(part+3, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for FFmpeg statistics.
|
||||
*
|
||||
* \param frameNumber last processed frame number
|
||||
* \param fps frames processed per second
|
||||
* \param quality quality of the output stream (video only)
|
||||
* \param size size in bytes
|
||||
* \param time processed output duration
|
||||
* \param bitrate output bit rate in kbits/s
|
||||
* \param speed processing speed = processed duration / operation duration
|
||||
*/
|
||||
void mobileffmpeg_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
|
||||
statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards callback messages to Java classes.
|
||||
*/
|
||||
void *callbackThreadFunction() {
|
||||
JNIEnv *env;
|
||||
jint getEnvRc = (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
|
||||
if (getEnvRc != JNI_OK) {
|
||||
if (getEnvRc != JNI_EDETACHED) {
|
||||
LOGE("Callback thread failed to GetEnv for class %s with rc %d.\n", configClassName, getEnvRc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((*globalVm)->AttachCurrentThread(globalVm, &env, NULL) != 0) {
|
||||
LOGE("Callback thread failed to AttachCurrentThread for class %s.\n", configClassName);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
LOGD("Callback thread started.\n");
|
||||
|
||||
while(redirectionEnabled) {
|
||||
|
||||
struct CallbackData *callbackData = callbackDataRemove();
|
||||
if (callbackData != NULL) {
|
||||
if (callbackData->type == 1) {
|
||||
|
||||
// LOG CALLBACK
|
||||
|
||||
size_t size = strlen(callbackData->logData);
|
||||
|
||||
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)->DeleteLocalRef(env, byteArray);
|
||||
|
||||
// CLEAN LOG DATA
|
||||
free(callbackData->logData);
|
||||
|
||||
} else {
|
||||
|
||||
// STATISTICS CALLBACK
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
|
||||
callbackData->statisticsFrameNumber, callbackData->statisticsFps,
|
||||
callbackData->statisticsQuality, callbackData->statisticsSize,
|
||||
callbackData->statisticsTime, callbackData->statisticsBitrate,
|
||||
callbackData->statisticsSpeed);
|
||||
|
||||
}
|
||||
|
||||
// CLEAN STRUCT
|
||||
callbackData->next = NULL;
|
||||
free(callbackData);
|
||||
|
||||
} else {
|
||||
monitorWait(100);
|
||||
}
|
||||
}
|
||||
|
||||
(*globalVm)->DetachCurrentThread(globalVm);
|
||||
|
||||
LOGD("Callback thread stopped.\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when 'mobileffmpeg' native library is loaded.
|
||||
@@ -42,55 +463,158 @@ JNINativeMethod ffmpegMethods[] = {
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
if ((*vm)->GetEnv(vm, (void**)(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
LOGE("OnLoad failed to GetEnv for class %s.", ffmpegClassName);
|
||||
LOGE("OnLoad failed to GetEnv for class %s.\n", configClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
jclass ffmpegClass = (*env)->FindClass(env, ffmpegClassName);
|
||||
if (ffmpegClass == NULL) {
|
||||
LOGE("OnLoad failed to FindClass %s.", ffmpegClassName);
|
||||
jclass localConfigClass = (*env)->FindClass(env, configClassName);
|
||||
if (localConfigClass == NULL) {
|
||||
LOGE("OnLoad failed to FindClass %s.\n", configClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if ((*env)->RegisterNatives(env, ffmpegClass, ffmpegMethods, 3) < 0) {
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.", ffmpegClassName);
|
||||
if ((*env)->RegisterNatives(env, localConfigClass, ffmpegMethods, 4) < 0) {
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 4) < 0) {
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
(*env)->GetJavaVM(env, &globalVm);
|
||||
|
||||
logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(I[B)V");
|
||||
if (logMethod == NULL) {
|
||||
LOGE("OnLoad thread failed to GetMethodID for %s.\n", "log");
|
||||
(*globalVm)->DetachCurrentThread(globalVm);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(IFFJIDD)V");
|
||||
if (logMethod == NULL) {
|
||||
LOGE("OnLoad thread failed to GetMethodID for %s.\n", "statistics");
|
||||
(*globalVm)->DetachCurrentThread(globalVm);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
configClass = (jclass) ((*env)->NewGlobalRef(env, localConfigClass));
|
||||
|
||||
redirectionEnabled = 0;
|
||||
|
||||
callbackDataHead = NULL;
|
||||
callbackDataTail = NULL;
|
||||
|
||||
mutexInit();
|
||||
monitorInit();
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns FFmpeg version bundled within the library.
|
||||
* Sets log level.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param reference to the class on which this method is invoked
|
||||
* \param log level
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) {
|
||||
av_log_set_level(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current log level.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables log and statistics redirection.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param reference to the class on which this method is invoked
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection(JNIEnv *env, jclass object) {
|
||||
mutexLock();
|
||||
|
||||
if (redirectionEnabled != 0) {
|
||||
mutexUnlock();
|
||||
return;
|
||||
}
|
||||
redirectionEnabled = 1;
|
||||
|
||||
mutexUnlock();
|
||||
|
||||
int rc = pthread_create(&callbackThread, 0, callbackThreadFunction, 0);
|
||||
if (rc != 0) {
|
||||
LOGE("Failed to create callback thread (rc=%d).\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
av_log_set_callback(mobileffmpeg_log_callback_function);
|
||||
set_report_callback(mobileffmpeg_statistics_callback_function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables log and statistics redirection.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param reference to the class on which this method is invoked
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection(JNIEnv *env, jclass object) {
|
||||
|
||||
mutexLock();
|
||||
|
||||
if (redirectionEnabled != 1) {
|
||||
mutexUnlock();
|
||||
return;
|
||||
}
|
||||
redirectionEnabled = 0;
|
||||
|
||||
mutexUnlock();
|
||||
|
||||
av_log_set_callback(av_log_default_callback);
|
||||
set_report_callback(NULL);
|
||||
|
||||
monitorNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns FFmpeg version bundled within the library natively.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the class on which this method is invoked
|
||||
* \return FFmpeg version string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_getFFmpegVersion(JNIEnv *env, jclass object) {
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion(JNIEnv *env, jclass object) {
|
||||
return (*env)->NewStringUTF(env, FFMPEG_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns MobileFFmpeg library version.
|
||||
* Returns MobileFFmpeg library version natively.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the class on which this method is invoked
|
||||
* \return MobileFFmpeg version string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_getVersion(JNIEnv *env, jclass object) {
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersion(JNIEnv *env, jclass object) {
|
||||
return (*env)->NewStringUTF(env, MOBILE_FFMPEG_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously executes FFmpeg command with arguments provided.
|
||||
* Synchronously executes FFmpeg command natively with arguments provided.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the class on which this method is invoked
|
||||
* \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_FFmpeg_execute(JNIEnv *env, jclass object, jobjectArray stringArray) {
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeExecute(JNIEnv *env, jclass object, jobjectArray stringArray) {
|
||||
jstring *tempArray = NULL;
|
||||
int argumentCount = 1;
|
||||
char **argv = NULL;
|
||||
@@ -102,7 +626,7 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_execute(JNIEnv *en
|
||||
tempArray = (jstring *) malloc(sizeof(jstring) * programArgumentCount);
|
||||
}
|
||||
|
||||
/* PRESERVING USAGE FORMAT
|
||||
/* PRESERVE USAGE FORMAT
|
||||
*
|
||||
* ffmpeg <arguments>
|
||||
*/
|
||||
@@ -114,7 +638,9 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_execute(JNIEnv *en
|
||||
if (stringArray != NULL) {
|
||||
for (int i = 0; i < (argumentCount - 1); i++) {
|
||||
tempArray[i] = (jstring) (*env)->GetObjectArrayElement(env, stringArray, i);
|
||||
argv[i + 1] = (char *) (*env)->GetStringUTFChars(env, tempArray[i], 0);
|
||||
if (tempArray[i] != NULL) {
|
||||
argv[i + 1] = (char *) (*env)->GetStringUTFChars(env, tempArray[i], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,3 +660,13 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_execute(JNIEnv *en
|
||||
|
||||
return retCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels an ongoing operation natively.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the class on which this method is invoked
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeCancel(JNIEnv *env, jclass object) {
|
||||
cancel_operation();
|
||||
}
|
||||
|
||||
@@ -17,38 +17,90 @@
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MOBILEFFMPEG_H
|
||||
#define MOBILEFFMPEG_H
|
||||
#ifndef MOBILE_FFMPEG_H
|
||||
#define MOBILE_FFMPEG_H
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/ffversion.h"
|
||||
#include "log.h"
|
||||
|
||||
/** Library version string */
|
||||
#define MOBILE_FFMPEG_VERSION "2.0"
|
||||
#define MOBILE_FFMPEG_VERSION "2.1.1"
|
||||
|
||||
/** Defines tag used for Android logging. */
|
||||
#define LIB_NAME "mobile-ffmpeg"
|
||||
|
||||
/** Verbose Android logging macro. */
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Debug Android logging macro. */
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Info Android logging macro. */
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Warn Android logging macro. */
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/** Error Android logging macro. */
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LIB_NAME, __VA_ARGS__)
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_FFmpeg
|
||||
* Method: getFFmpegVersion
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: enableNativeRedirection
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: disableNativeRedirection
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: setNativeLogLevel
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *, jclass, jint);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: getNativeLogLevel
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: getNativeFFmpegVersion
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_getFFmpegVersion(JNIEnv *, jclass);
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_FFmpeg
|
||||
* Method: getVersion
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: getNativeVersion
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_getVersion(JNIEnv *, jclass);
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersion(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_FFmpeg
|
||||
* Method: execute
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: nativeExecute
|
||||
* Signature: ([Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_execute(JNIEnv *, jclass, jobjectArray);
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeExecute(JNIEnv *, jclass, jobjectArray);
|
||||
|
||||
#endif /* MOBILEFFMPEG_H */
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: nativeCancel
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeCancel(JNIEnv *, jclass);
|
||||
|
||||
#endif /* MOBILE_FFMPEG_H */
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "cpu-features.h"
|
||||
#include "abidetect.h"
|
||||
#include "mobileffmpeg_abidetect.h"
|
||||
|
||||
/** Full name of the Java class that owns native functions in this file. */
|
||||
const char *abiDetectClassName = "com/arthenica/mobileffmpeg/AbiDetect";
|
||||
@@ -38,18 +38,18 @@ JNINativeMethod abiDetectMethods[] = {
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
LOGE("OnLoad failed to GetEnv for class %s.", abiDetectClassName);
|
||||
LOGE("OnLoad failed to GetEnv for class %s.\n", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
jclass abiDetectClass = (*env)->FindClass(env, abiDetectClassName);
|
||||
if (abiDetectClass == NULL) {
|
||||
LOGE("OnLoad failed to FindClass %s.", abiDetectClassName);
|
||||
LOGE("OnLoad failed to FindClass %s.\n", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if ((*env)->RegisterNatives(env, abiDetectClass, abiDetectMethods, 1) < 0) {
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.", abiDetectClassName);
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.\n", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MOBILEFFMPEG_ABIDETECT_H
|
||||
#define MOBILEFFMPEG_ABIDETECT_H
|
||||
#ifndef MOBILE_FFMPEG_ABIDETECT_H
|
||||
#define MOBILE_FFMPEG_ABIDETECT_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "log.h"
|
||||
#include "mobileffmpeg.h"
|
||||
|
||||
/** Represents armeabi-v7a ABI with NEON support. */
|
||||
#define ABI_ARMV7A_NEON "armeabi-v7a-neon"
|
||||
@@ -51,4 +51,4 @@
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_getAbi(JNIEnv *, jclass);
|
||||
|
||||
#endif /* MOBILEFFMPEG_ABIDETECT_H */
|
||||
#endif /* MOBILE_FFMPEG_ABIDETECT_H */
|
||||
@@ -17,8 +17,8 @@
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MOBILEFFMPEG_EXCEPTION_H
|
||||
#define MOBILEFFMPEG_EXCEPTION_H
|
||||
#ifndef MOBILE_FFMPEG_EXCEPTION_H
|
||||
#define MOBILE_FFMPEG_EXCEPTION_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
@@ -26,4 +26,4 @@
|
||||
/** Holds information to implement exception handling. */
|
||||
jmp_buf ex_buf__;
|
||||
|
||||
#endif // MOBILEFFMPEG_EXCEPTION_H
|
||||
#endif // MOBILE_FFMPEG_EXCEPTION_H
|
||||
@@ -29,7 +29,11 @@ package com.arthenica.mobileffmpeg;
|
||||
public class AbiDetect {
|
||||
|
||||
static {
|
||||
System.loadLibrary("abidetect");
|
||||
System.loadLibrary("mobileffmpeg-abidetect");
|
||||
|
||||
/* ALL LIBRARIES LOADED AT STARTUP */
|
||||
Config.class.getName();
|
||||
FFmpeg.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* 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 android.content.Context;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static com.arthenica.mobileffmpeg.FFmpeg.getVersion;
|
||||
|
||||
/**
|
||||
* <p>This class is used to configure MobileFFmpeg library utilities/tools.
|
||||
*
|
||||
* <p>1. {@link LogCallback}: By default this class redirects FFmpeg output to Logcat. As another
|
||||
* option, it is possible not to print messages to Logcat and pass them to a {@link LogCallback}
|
||||
* function. This function can decide whether to print these logs, show them inside another
|
||||
* container or ignore them.
|
||||
*
|
||||
* <p>2. {@link #setLogLevel(Level)}/{@link #getLogLevel()}: Use this methods to see/control FFmpeg
|
||||
* log severity.
|
||||
*
|
||||
* <p>3. {@link StatisticsCallback}: It is possible to receive statistics about ongoing operation by
|
||||
* defining a {@link StatisticsCallback} function or by calling {@link #getLastReceivedStatistics()}
|
||||
* method.
|
||||
*
|
||||
* <p>4. Font configuration: It is possible to register custom fonts with
|
||||
* {@link #setFontconfigConfigurationPath(String)} and
|
||||
* {@link #setFontDirectory(Context, String, Map)} methods.
|
||||
*
|
||||
* <p>PS: This class is introduced in v2.1 as an enhanced version of older <code>Log</code> class.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
public class Config {
|
||||
|
||||
/**
|
||||
* Defines tag used for logging.
|
||||
*/
|
||||
public static final String TAG = "mobile-ffmpeg";
|
||||
|
||||
private static LogCallback logCallbackFunction;
|
||||
|
||||
private static Level activeLogLevel;
|
||||
|
||||
private static StatisticsCallback statisticsCallbackFunction;
|
||||
|
||||
private static Statistics lastReceivedStatistics;
|
||||
|
||||
static {
|
||||
|
||||
Log.i(Config.TAG, "Loading mobile-ffmpeg.");
|
||||
|
||||
/* ALL LIBRARIES LOADED AT STARTUP */
|
||||
String abiName = AbiDetect.getAbi();
|
||||
Abi abi = Abi.from(abiName);
|
||||
FFmpeg.class.getName();
|
||||
|
||||
/*
|
||||
* NEON supported arm-v7a library has a different name
|
||||
*/
|
||||
boolean nativeLibraryLoaded = false;
|
||||
if (abi == Abi.ABI_ARMV7A_NEON) {
|
||||
try {
|
||||
System.loadLibrary("mobileffmpeg-armv7a-neon");
|
||||
nativeLibraryLoaded = true;
|
||||
} catch (final UnsatisfiedLinkError e) {
|
||||
Log.i(Config.TAG, "NEON supported armeabi-v7a library not found. Loading default armeabi-v7a library.", e);
|
||||
abi = Abi.ABI_ARMV7A;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeLibraryLoaded) {
|
||||
System.loadLibrary("mobileffmpeg");
|
||||
}
|
||||
|
||||
Log.i(Config.TAG, String.format("Loaded mobile-ffmpeg-%s-%s.", abi.getName(), getVersion()));
|
||||
|
||||
/* NATIVE LOG LEVEL IS RECEIVED ONLY ON STARTUP */
|
||||
activeLogLevel = Level.from(getNativeLogLevel());
|
||||
|
||||
lastReceivedStatistics = new Statistics();
|
||||
|
||||
Config.enableRedirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor hidden.
|
||||
*/
|
||||
private Config() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Enables log and statistics redirection.
|
||||
* <p>When redirection is not enabled FFmpeg logs are printed to stderr. By enabling redirection, they are routed
|
||||
* to Logcat and can be routed further to a callback function.
|
||||
* <p>Statistics redirection behaviour is similar. Statistics are not printed at all if redirection is not enabled.
|
||||
* If it is enabled then it is possible to define a statistics callback function but if you don't, they are not
|
||||
* printed anywhere and only saved as <code>lastReceivedStatistics</code> data which can be polled with
|
||||
* {@link #getLastReceivedStatistics()}.
|
||||
* <p>Note that redirection is enabled by default. If you do not want to use its functionality please use
|
||||
* {@link #disableRedirection()} to disable it.
|
||||
*/
|
||||
public static void enableRedirection() {
|
||||
enableNativeRedirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Disables log and statistics redirection.
|
||||
*/
|
||||
public static void disableRedirection() {
|
||||
disableNativeRedirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns log level.
|
||||
*
|
||||
* @return log level
|
||||
*/
|
||||
public static Level getLogLevel() {
|
||||
return activeLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets log level.
|
||||
*
|
||||
* @param level log level
|
||||
*/
|
||||
public static void setLogLevel(final Level level) {
|
||||
if (level != null) {
|
||||
activeLogLevel = level;
|
||||
setNativeLogLevel(level.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets a callback function to redirect FFmpeg logs.
|
||||
*
|
||||
* @param newLogCallback new log callback function or NULL to disable a previously defined callback
|
||||
*/
|
||||
public static void enableLogCallback(final LogCallback newLogCallback) {
|
||||
logCallbackFunction = newLogCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets a callback function to redirect FFmpeg statistics.
|
||||
*
|
||||
* @param statisticsCallback new statistics callback function or NULL to disable a previously defined callback
|
||||
*/
|
||||
public static void enableStatisticsCallback(final StatisticsCallback statisticsCallback) {
|
||||
statisticsCallbackFunction = statisticsCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Log redirection method called by JNI/native part.
|
||||
*
|
||||
* @param levelValue log level as defined in {@link Level}
|
||||
* @param logMessage redirected log message
|
||||
*/
|
||||
private static void log(final int levelValue, final byte[] logMessage) {
|
||||
final Level level = Level.from(levelValue);
|
||||
final String text = new String(logMessage);
|
||||
|
||||
if (activeLogLevel == Level.AV_LOG_QUIET || levelValue > activeLogLevel.getValue()) {
|
||||
// LOG NEITHER PRINTED NOR FORWARDED
|
||||
return;
|
||||
}
|
||||
|
||||
if (logCallbackFunction != null) {
|
||||
logCallbackFunction.apply(new LogMessage(level, text));
|
||||
} else {
|
||||
switch (level) {
|
||||
case AV_LOG_QUIET: {
|
||||
// PRINT NO OUTPUT
|
||||
}
|
||||
break;
|
||||
case AV_LOG_TRACE:
|
||||
case AV_LOG_DEBUG: {
|
||||
android.util.Log.d(TAG, text);
|
||||
}
|
||||
break;
|
||||
case AV_LOG_VERBOSE: {
|
||||
android.util.Log.v(TAG, text);
|
||||
}
|
||||
break;
|
||||
case AV_LOG_INFO: {
|
||||
android.util.Log.i(TAG, text);
|
||||
}
|
||||
break;
|
||||
case AV_LOG_WARNING: {
|
||||
android.util.Log.w(TAG, text);
|
||||
}
|
||||
break;
|
||||
case AV_LOG_ERROR:
|
||||
case AV_LOG_FATAL:
|
||||
case AV_LOG_PANIC: {
|
||||
android.util.Log.e(TAG, text);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
android.util.Log.v(TAG, text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Statistics redirection method called by JNI/native part.
|
||||
*
|
||||
* @param videoFrameNumber last processed frame number for videos
|
||||
* @param videoFps frames processed per second for videos
|
||||
* @param videoQuality quality of the video stream
|
||||
* @param size size in bytes
|
||||
* @param time processed duration in milliseconds
|
||||
* @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);
|
||||
lastReceivedStatistics.update(newStatistics);
|
||||
|
||||
if (statisticsCallbackFunction != null) {
|
||||
statisticsCallbackFunction.apply(lastReceivedStatistics);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the last received statistics data.
|
||||
*
|
||||
* @return last received statistics data
|
||||
*/
|
||||
public static Statistics getLastReceivedStatistics() {
|
||||
return lastReceivedStatistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Resets last received statistics. It is recommended to call it before starting a new execution.
|
||||
*/
|
||||
public static void resetStatistics() {
|
||||
lastReceivedStatistics = new Statistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets and overrides <code>fontconfig</code> configuration directory.
|
||||
*
|
||||
* @param path directory which contains fontconfig configuration (fonts.conf)
|
||||
* @throws ErrnoException if an error occurs
|
||||
*/
|
||||
public static void setFontconfigConfigurationPath(final String path) throws ErrnoException {
|
||||
Os.setenv("FONTCONFIG_PATH", path, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers fonts inside the given path, so they are available in FFmpeg filters.
|
||||
*
|
||||
* <p>Note that you need to build <code>MobileFFmpeg</code> with <code>fontconfig</code>
|
||||
* enabled or use a prebuilt package with <code>fontconfig</code> inside to use this feature.
|
||||
*
|
||||
* @param context application context to access application data
|
||||
* @param fontDirectoryPath directory which contains fonts (.ttf and .otf files)
|
||||
* @param fontNameMapping custom font name mappings, useful to access your fonts with more friendly names
|
||||
*/
|
||||
public static void setFontDirectory(final Context context, final String fontDirectoryPath, final Map<String, String> fontNameMapping) {
|
||||
final File cacheDir = context.getCacheDir();
|
||||
int validFontNameMappingCount = 0;
|
||||
|
||||
final File tempConfigurationDirectory = new File(cacheDir, ".mobileffmpeg");
|
||||
if (!tempConfigurationDirectory.exists()) {
|
||||
boolean tempFontConfDirectoryCreated = tempConfigurationDirectory.mkdirs();
|
||||
Log.d(TAG, String.format("Created temporary font conf directory: %s.", tempFontConfDirectoryCreated));
|
||||
}
|
||||
|
||||
final File fontConfiguration = new File(tempConfigurationDirectory, "fonts.conf");
|
||||
if (fontConfiguration.exists()) {
|
||||
boolean fontConfigurationDeleted = fontConfiguration.delete();
|
||||
Log.d(TAG, String.format("Deleted old temporary font configuration: %s.", fontConfigurationDeleted));
|
||||
}
|
||||
|
||||
/* PROCESS MAPPINGS FIRST */
|
||||
final StringBuilder fontNameMappingBlock = new StringBuilder("");
|
||||
if (fontNameMapping != null && (fontNameMapping.size() > 0)) {
|
||||
fontNameMapping.entrySet();
|
||||
for (Map.Entry<String, String> mapping : fontNameMapping.entrySet()) {
|
||||
String fontName = mapping.getKey();
|
||||
String mappedFontName = mapping.getValue();
|
||||
|
||||
if ((fontName != null) && (mappedFontName != null) && (fontName.trim().length() > 0) && (mappedFontName.trim().length() > 0)) {
|
||||
fontNameMappingBlock.append(" <match target=\"pattern\">\n");
|
||||
fontNameMappingBlock.append(" <test qual=\"any\" name=\"family\">\n");
|
||||
fontNameMappingBlock.append(String.format(" <string>%s</string>\n", fontName));
|
||||
fontNameMappingBlock.append(" </test>\n");
|
||||
fontNameMappingBlock.append(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
|
||||
fontNameMappingBlock.append(String.format(" <string>%s</string>\n", mappedFontName));
|
||||
fontNameMappingBlock.append(" </edit>\n");
|
||||
fontNameMappingBlock.append(" </match>\n");
|
||||
|
||||
validFontNameMappingCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String fontConfig = "<?xml version=\"1.0\"?>\n" +
|
||||
"<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n" +
|
||||
"<fontconfig>\n" +
|
||||
" <dir>.</dir>\n" +
|
||||
" <dir>" + fontDirectoryPath + "</dir>\n" +
|
||||
fontNameMappingBlock +
|
||||
"</fontconfig>";
|
||||
|
||||
final AtomicReference<FileOutputStream> reference = new AtomicReference<>();
|
||||
try {
|
||||
final FileOutputStream outputStream = new FileOutputStream(fontConfiguration);
|
||||
reference.set(outputStream);
|
||||
|
||||
outputStream.write(fontConfig.getBytes());
|
||||
outputStream.flush();
|
||||
|
||||
Log.d(TAG, String.format("Saved new temporary font configuration with %d font name mappings.", validFontNameMappingCount));
|
||||
|
||||
setFontconfigConfigurationPath(tempConfigurationDirectory.getAbsolutePath());
|
||||
|
||||
Log.d(TAG, String.format("Font directory %s registered successfully.", fontDirectoryPath));
|
||||
|
||||
} catch (final ErrnoException | IOException e) {
|
||||
Log.e(TAG, String.format("Failed to set font directory: %s.", fontDirectoryPath), e);
|
||||
} finally {
|
||||
if (reference.get() != null) {
|
||||
try {
|
||||
reference.get().close();
|
||||
} catch (IOException e) {
|
||||
// DO NOT PRINT THIS ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Enables native redirection. Necessary for log and statistics callback functions.
|
||||
*/
|
||||
private static native void enableNativeRedirection();
|
||||
|
||||
/**
|
||||
* <p>Disables native redirection
|
||||
*/
|
||||
private static native void disableNativeRedirection();
|
||||
|
||||
/**
|
||||
* Sets native log level
|
||||
*
|
||||
* @param level log level
|
||||
*/
|
||||
private static native void setNativeLogLevel(int level);
|
||||
|
||||
/**
|
||||
* Returns native log level.
|
||||
*
|
||||
* @return log level
|
||||
*/
|
||||
private static native int getNativeLogLevel();
|
||||
|
||||
/**
|
||||
* <p>Returns FFmpeg version bundled within the library natively.
|
||||
*
|
||||
* @return FFmpeg version
|
||||
*/
|
||||
native static String getNativeFFmpegVersion();
|
||||
|
||||
/**
|
||||
* <p>Returns MobileFFmpeg library version natively.
|
||||
*
|
||||
* @return MobileFFmpeg version
|
||||
*/
|
||||
native static String getNativeVersion();
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFmpeg natively 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
|
||||
*/
|
||||
native static int nativeExecute(final String[] arguments);
|
||||
|
||||
/**
|
||||
* <p>Cancels an ongoing operation natively.
|
||||
*
|
||||
* <p>This function does not wait for termination to complete and returns immediately.
|
||||
*/
|
||||
native static void nativeCancel();
|
||||
|
||||
}
|
||||
@@ -19,49 +19,28 @@
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Main class for FFmpeg operations. Provides {@link #execute(String...)} method to execute
|
||||
* FFmpeg commands.
|
||||
* <pre>
|
||||
* int rc = FFmpeg.execute("-i", "file1.mp4", "-c:v", "libxvid", "file1.avi");
|
||||
* Log.i(Log.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
|
||||
* Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
|
||||
* </pre>
|
||||
* <p>Note that it is recommended to call {@link #shutdown()} method before terminating your
|
||||
* Android app.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v1.0
|
||||
*/
|
||||
public class FFmpeg {
|
||||
|
||||
public static final int RETURN_CODE_SUCCESS = 0;
|
||||
|
||||
public static final int RETURN_CODE_CANCEL = 255;
|
||||
|
||||
static {
|
||||
final Abi abi = Abi.from(AbiDetect.getAbi());
|
||||
String abiName = abi.getName();
|
||||
|
||||
Log.enableCollectingStdOutErr();
|
||||
|
||||
android.util.Log.i(Log.TAG, "Loading mobile-ffmpeg.");
|
||||
|
||||
/*
|
||||
* NEON supported arm-v7a library has a different name
|
||||
*/
|
||||
boolean nativeLibraryLoaded = false;
|
||||
if (abi == Abi.ABI_ARMV7A_NEON) {
|
||||
try {
|
||||
System.loadLibrary("mobileffmpeg-armv7a-neon");
|
||||
android.util.Log.i(Log.TAG, String.format("Loaded mobile-ffmpeg-%s-%s.", abiName, getVersion()));
|
||||
nativeLibraryLoaded = true;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
android.util.Log.i(Log.TAG, "NEON supported armeabi-v7a library not found. Loading default armeabi-v7a library.", e);
|
||||
abiName = Abi.ABI_ARMV7A.getName();
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeLibraryLoaded) {
|
||||
System.loadLibrary("mobileffmpeg");
|
||||
|
||||
android.util.Log.i(Log.TAG, String.format("Loaded mobile-ffmpeg-%s-%s.", abiName, getVersion()));
|
||||
}
|
||||
AbiDetect.class.getName();
|
||||
Config.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,36 +54,46 @@ public class FFmpeg {
|
||||
*
|
||||
* @return FFmpeg version
|
||||
*/
|
||||
public native static String getFFmpegVersion();
|
||||
public static String getFFmpegVersion() {
|
||||
return Config.getNativeFFmpegVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns MobileFFmpeg library version.
|
||||
*
|
||||
* @return MobileFFmpeg version
|
||||
*/
|
||||
public native static String getVersion();
|
||||
public static String getVersion() {
|
||||
return Config.getNativeVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFmpeg with arguments provided.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments
|
||||
* @return zero on successful execution, non-zero on error
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
*/
|
||||
public native static int execute(final String ... arguments);
|
||||
|
||||
/**
|
||||
* <p>Shuts down library capabilities.
|
||||
*/
|
||||
public static void shutdown() {
|
||||
Log.disableCollectingStdOutErr();
|
||||
public static int execute(final String[] arguments) {
|
||||
return Config.nativeExecute(arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Overrides default {@link Object#finalize()} method.
|
||||
* <p>Synchronously executes FFmpeg with arguments provided.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments in one string
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() {
|
||||
shutdown();
|
||||
public static int execute(final String arguments) {
|
||||
return execute((arguments == null) ? new String[]{""} : arguments.split(" "));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Cancels an ongoing operation.
|
||||
*
|
||||
* <p>This function does not wait for termination to complete and returns immediately.
|
||||
*/
|
||||
public static void cancel() {
|
||||
Config.nativeCancel();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>Helper enumeration type for log levels.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
public enum Level {
|
||||
|
||||
/**
|
||||
* Print no output.
|
||||
*/
|
||||
AV_LOG_QUIET(-8),
|
||||
|
||||
/**
|
||||
* Something went really wrong and we will crash now.
|
||||
*/
|
||||
AV_LOG_PANIC(0),
|
||||
|
||||
/**
|
||||
* Something went wrong and recovery is not possible.
|
||||
* For example, no header was found for a format which depends
|
||||
* on headers or an illegal combination of parameters is used.
|
||||
*/
|
||||
AV_LOG_FATAL(8),
|
||||
|
||||
/**
|
||||
* Something went wrong and cannot losslessly be recovered.
|
||||
* However, not all future data is affected.
|
||||
*/
|
||||
AV_LOG_ERROR(16),
|
||||
|
||||
/**
|
||||
* Something somehow does not look correct. This may or may not
|
||||
* lead to problems. An example would be the use of '-vstrict -2'.
|
||||
*/
|
||||
AV_LOG_WARNING(24),
|
||||
|
||||
/**
|
||||
* Standard information.
|
||||
*/
|
||||
AV_LOG_INFO(32),
|
||||
|
||||
/**
|
||||
* Detailed information.
|
||||
*/
|
||||
AV_LOG_VERBOSE(40),
|
||||
|
||||
/**
|
||||
* Stuff which is only useful for libav* developers.
|
||||
*/
|
||||
AV_LOG_DEBUG(48),
|
||||
|
||||
/**
|
||||
* Extremely verbose debugging, useful for libav* development.
|
||||
*/
|
||||
AV_LOG_TRACE(56);
|
||||
|
||||
private int value;
|
||||
|
||||
/**
|
||||
* <p>Returns enumeration defined by value.
|
||||
*
|
||||
* @param value level value
|
||||
* @return enumeration defined by value
|
||||
*/
|
||||
public static Level from(final int value) {
|
||||
if (value == AV_LOG_QUIET.getValue()) {
|
||||
return AV_LOG_QUIET;
|
||||
} else if (value == AV_LOG_PANIC.getValue()) {
|
||||
return AV_LOG_PANIC;
|
||||
} else if (value == AV_LOG_FATAL.getValue()) {
|
||||
return AV_LOG_FATAL;
|
||||
} else if (value == AV_LOG_ERROR.getValue()) {
|
||||
return AV_LOG_ERROR;
|
||||
} else if (value == AV_LOG_WARNING.getValue()) {
|
||||
return AV_LOG_WARNING;
|
||||
} else if (value == AV_LOG_INFO.getValue()) {
|
||||
return AV_LOG_INFO;
|
||||
} else if (value == AV_LOG_VERBOSE.getValue()) {
|
||||
return AV_LOG_VERBOSE;
|
||||
} else if (value == AV_LOG_DEBUG.getValue()) {
|
||||
return AV_LOG_DEBUG;
|
||||
} else {
|
||||
return AV_LOG_TRACE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns level value.
|
||||
*
|
||||
* @return level value
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new enum.
|
||||
*
|
||||
* @param value level value
|
||||
*/
|
||||
Level(final int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +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 android.arch.core.util.Function;
|
||||
|
||||
/**
|
||||
* <p>This class is used to process stdout and stderr logs from native libraries.
|
||||
*
|
||||
* <p>By default stdout and stderr is redirected to <code>/dev/null</code> in Android. This class
|
||||
* redirects these streams to Logcat in order to view logs printed by FFmpeg native libraries.
|
||||
*
|
||||
* <p>Alternatively, it is possible not to print messages to Logcat and pass them to a callback
|
||||
* function. This function can decide whether to print these logs or ignore them according to its
|
||||
* own rules.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v1.0
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
/**
|
||||
* Defines tag used for logging.
|
||||
*/
|
||||
public static final String TAG = "mobile-ffmpeg";
|
||||
|
||||
private static Function<byte[], Void> callbackFunction;
|
||||
|
||||
static {
|
||||
System.loadLibrary("ffmpeglog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor hidden.
|
||||
*/
|
||||
private Log() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Enables redirecting stdout and stderr.
|
||||
*/
|
||||
public static void enableCollectingStdOutErr() {
|
||||
startNativeCollector();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Disables redirecting stdout and stderr.
|
||||
*/
|
||||
public static void disableCollectingStdOutErr() {
|
||||
stopNativeCollector();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets a callback function to receive logs from FFmpeg native libraries.
|
||||
*
|
||||
* @param newCallbackFunction callback to receive logs
|
||||
*/
|
||||
public static void enableCallbackFunction(final Function<byte[], Void> newCallbackFunction) {
|
||||
callbackFunction = newCallbackFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Main method called by JNI part to redirect log messages. It is not designed to be called
|
||||
* manually by Java classes.
|
||||
*
|
||||
* @param logMessage log message
|
||||
*/
|
||||
public static void log(final byte[] logMessage) {
|
||||
if (callbackFunction != null) {
|
||||
callbackFunction.apply(logMessage);
|
||||
} else {
|
||||
android.util.Log.d(TAG, new String(logMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts native log collector.
|
||||
*
|
||||
* @return zero on success, non-zero if an error occurs
|
||||
*/
|
||||
public static native int startNativeCollector();
|
||||
|
||||
/**
|
||||
* <p>Stops native log collector.
|
||||
*
|
||||
* @return zero on success, non-zero if an error occurs
|
||||
*/
|
||||
public static native int stopNativeCollector();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>Represents a callback function to redirect logs.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LogCallback {
|
||||
|
||||
void apply(final LogMessage message);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>Represents a redirected log message.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
public class LogMessage {
|
||||
|
||||
private final Level level;
|
||||
private final String text;
|
||||
|
||||
public LogMessage(final Level level, final String text) {
|
||||
this.level = level;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>Represents statistics data.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
public class Statistics {
|
||||
|
||||
private int videoFrameNumber;
|
||||
private float videoFps;
|
||||
private float videoQuality;
|
||||
private long size;
|
||||
private int time;
|
||||
private double bitrate;
|
||||
private double speed;
|
||||
|
||||
public Statistics() {
|
||||
videoFrameNumber = 0;
|
||||
videoFps = 0;
|
||||
videoQuality = 0;
|
||||
size = 0;
|
||||
time = 0;
|
||||
bitrate = 0;
|
||||
speed = 0;
|
||||
}
|
||||
|
||||
public Statistics(int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
|
||||
this.videoFrameNumber = videoFrameNumber;
|
||||
this.videoFps = videoFps;
|
||||
this.videoQuality = videoQuality;
|
||||
this.size = size;
|
||||
this.time = time;
|
||||
this.bitrate = bitrate;
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public void update(final Statistics newStatistics) {
|
||||
if (newStatistics != null) {
|
||||
if (newStatistics.getVideoFrameNumber() > 0) {
|
||||
this.videoFrameNumber = newStatistics.getVideoFrameNumber();
|
||||
}
|
||||
if (newStatistics.getVideoFps() > 0){
|
||||
this.videoFps = newStatistics.getVideoFps();
|
||||
}
|
||||
|
||||
if (newStatistics.getVideoQuality() > 0){
|
||||
this.videoQuality = newStatistics.getVideoQuality();
|
||||
}
|
||||
|
||||
if (newStatistics.getSize() > 0){
|
||||
this.size = newStatistics.getSize();
|
||||
}
|
||||
|
||||
if (newStatistics.getTime() > 0){
|
||||
this.time = newStatistics.getTime();
|
||||
}
|
||||
|
||||
if (newStatistics.getBitrate() > 0){
|
||||
this.bitrate = newStatistics.getBitrate();
|
||||
}
|
||||
|
||||
if (newStatistics.getSpeed() > 0){
|
||||
this.speed = newStatistics.getSpeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getVideoFrameNumber() {
|
||||
return videoFrameNumber;
|
||||
}
|
||||
|
||||
public void setVideoFrameNumber(int videoFrameNumber) {
|
||||
this.videoFrameNumber = videoFrameNumber;
|
||||
}
|
||||
|
||||
public float getVideoFps() {
|
||||
return videoFps;
|
||||
}
|
||||
|
||||
public void setVideoFps(float videoFps) {
|
||||
this.videoFps = videoFps;
|
||||
}
|
||||
|
||||
public float getVideoQuality() {
|
||||
return videoQuality;
|
||||
}
|
||||
|
||||
public void setVideoQuality(float videoQuality) {
|
||||
this.videoQuality = videoQuality;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(int time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public double getBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
public void setBitrate(double bitrate) {
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
public double getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public void setSpeed(double speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>Represents a callback function to receive statistics of running operation.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface StatisticsCallback {
|
||||
|
||||
void apply(final Statistics statistics);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 run result.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since v2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RunCallback {
|
||||
|
||||
void apply(final int returnCode);
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 66 KiB |
@@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.2'
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
@@ -15,28 +15,20 @@ include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_ARM_MODE := $(MY_ARM_MODE)
|
||||
LOCAL_MODULE := abidetect
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/abidetect.c
|
||||
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
|
||||
LOCAL_LDLIBS := -llog -lz -landroid
|
||||
LOCAL_SHARED_LIBRARIES := cpufeatures
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_ARM_MODE := $(MY_ARM_MODE)
|
||||
LOCAL_MODULE := ffmpeglog
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/log.c
|
||||
LOCAL_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter
|
||||
LOCAL_LDLIBS := -llog -lz -landroid
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_ARM_MODE := $(MY_ARM_MODE)
|
||||
LOCAL_MODULE := mobileffmpeg
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/cmdutils.c $(MY_PATH)/ffmpeg.c $(MY_PATH)/ffmpeg_opt.c $(MY_PATH)/ffmpeg_hw.c $(MY_PATH)/ffmpeg_filter.c
|
||||
LOCAL_CFLAGS := -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
|
||||
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 := libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
|
||||
LOCAL_SHARED_LIBRARIES := c++_shared libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
|
||||
@@ -45,10 +37,10 @@ ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_ARM_MODE := $(MY_ARM_MODE)
|
||||
LOCAL_MODULE := mobileffmpeg-armv7a-neon
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/cmdutils.c $(MY_PATH)/ffmpeg.c $(MY_PATH)/ffmpeg_opt.c $(MY_PATH)/ffmpeg_hw.c $(MY_PATH)/ffmpeg_filter.c
|
||||
LOCAL_CFLAGS := -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include
|
||||
LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c
|
||||
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 := libavcodec-neon libavfilter-neon libswscale-neon libavformat libavutil libswresample libavdevice
|
||||
LOCAL_SHARED_LIBRARIES := c++_shared libavcodec-neon libavfilter-neon libswscale-neon libavformat libavutil libswresample libavdevice
|
||||
LOCAL_ARM_NEON := true
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
@@ -57,4 +49,4 @@ ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
|
||||
endif
|
||||
endif
|
||||
|
||||
$(call import-module, ffmpeg)
|
||||
$(call import-module, ffmpeg)
|
||||
|
||||
@@ -6,8 +6,8 @@ android {
|
||||
applicationId "com.arthenica.mobileffmpeg.test"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 20
|
||||
versionName "2.0"
|
||||
versionCode 21
|
||||
versionName "2.1.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
@@ -20,12 +20,12 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'com.android.support:support-v4:27.1.1'
|
||||
implementation 'com.android.support:design:27.1.1'
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:2.0'
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:2.1.1'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
implementation 'com.android.support:support-v4:27.1.1'
|
||||
implementation 'com.android.support:design:27.1.1'
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.arthenica.mobileffmpeg.test">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -12,8 +15,10 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:largeHeap="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name="com.arthenica.mobileffmpeg.test.MainActivity">
|
||||
android:theme="@style/AppTheme"
|
||||
tools:replace="android:icon">
|
||||
<activity android:name="com.arthenica.mobileffmpeg.test.MainActivity"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
import com.arthenica.mobileffmpeg.LogCallback;
|
||||
import com.arthenica.mobileffmpeg.LogMessage;
|
||||
import com.arthenica.mobileffmpeg.util.RunCallback;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.arthenica.mobileffmpeg.FFmpeg.RETURN_CODE_SUCCESS;
|
||||
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
|
||||
|
||||
public class AudioTabFragment extends Fragment implements AdapterView.OnItemSelectedListener {
|
||||
|
||||
private MainActivity mainActivity;
|
||||
private AlertDialog progressDialog;
|
||||
private Button encodeButton;
|
||||
private TextView outputText;
|
||||
private String selectedCodec;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_audio_tab, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (getView() != null) {
|
||||
Spinner audioCodecSpinner = getView().findViewById(R.id.audioCodecSpinner);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mainActivity,
|
||||
R.array.audio_codec, R.layout.spinner_item);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
audioCodecSpinner.setAdapter(adapter);
|
||||
audioCodecSpinner.setOnItemSelectedListener(this);
|
||||
|
||||
encodeButton = getView().findViewById(R.id.encodeButton);
|
||||
encodeButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encodeAudio();
|
||||
}
|
||||
});
|
||||
encodeButton.setEnabled(false);
|
||||
|
||||
outputText = getView().findViewById(R.id.outputText);
|
||||
outputText.setMovementMethod(new ScrollingMovementMethod());
|
||||
}
|
||||
|
||||
progressDialog = mainActivity.createProgressDialog("Encoding audio");
|
||||
|
||||
selectedCodec = getResources().getStringArray(R.array.audio_codec)[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
setActive();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainActivity(MainActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
||||
public static AudioTabFragment newInstance(final MainActivity mainActivity) {
|
||||
final AudioTabFragment fragment = new AudioTabFragment();
|
||||
fragment.setMainActivity(mainActivity);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void disableLogCallback() {
|
||||
Config.enableLogCallback(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
selectedCodec = parent.getItemAtPosition(position).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
// DO NOTHING
|
||||
}
|
||||
|
||||
public void encodeAudio() {
|
||||
File audioOutputFile = getAudioOutputFile();
|
||||
if (audioOutputFile.exists()) {
|
||||
audioOutputFile.delete();
|
||||
}
|
||||
|
||||
final String audioCodec = selectedCodec;
|
||||
|
||||
android.util.Log.d(TAG, String.format("Testing AUDIO encoding with '%s' codec", audioCodec));
|
||||
|
||||
final String ffmpegCommand = generateAudioEncodeScript();
|
||||
|
||||
showProgressDialog();
|
||||
|
||||
clearLog();
|
||||
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final int returnCode) {
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
|
||||
hideProgressDialog();
|
||||
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
Popup.show(mainActivity, "Encode completed successfully.");
|
||||
android.util.Log.d(TAG, "Encode completed successfully.");
|
||||
} else {
|
||||
Popup.show(mainActivity, "Encode failed. Please check log for the details.");
|
||||
android.util.Log.d(TAG, String.format("Encode failed with rc=%d", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, ffmpegCommand);
|
||||
}
|
||||
|
||||
public void createAudioSample() {
|
||||
android.util.Log.d(TAG, "Creating AUDIO sample before the test.");
|
||||
|
||||
File audioSampleFile = getAudioSampleFile();
|
||||
if (audioSampleFile.exists()) {
|
||||
audioSampleFile.delete();
|
||||
}
|
||||
|
||||
String ffmpegCommand = String.format("-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));
|
||||
|
||||
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));
|
||||
Popup.show(mainActivity, "Creating AUDIO sample failed. Please check log for the details.");
|
||||
}
|
||||
}
|
||||
|
||||
public File getAudioOutputFile() {
|
||||
String audioCodec = selectedCodec;
|
||||
|
||||
String extension;
|
||||
switch (audioCodec) {
|
||||
case "mp3 (liblame)":
|
||||
case "mp3 (libshine)":
|
||||
extension = "mp3";
|
||||
break;
|
||||
case "vorbis":
|
||||
extension = "ogg";
|
||||
break;
|
||||
case "opus":
|
||||
extension = "opus";
|
||||
break;
|
||||
case "amr":
|
||||
extension = "amr";
|
||||
break;
|
||||
case "ilbc":
|
||||
extension = "lbc";
|
||||
break;
|
||||
case "speex":
|
||||
extension = "spx";
|
||||
break;
|
||||
case "wavpack":
|
||||
extension = "wv";
|
||||
break;
|
||||
default:
|
||||
|
||||
// soxr
|
||||
extension = "wav";
|
||||
break;
|
||||
}
|
||||
|
||||
final String audio = "audio." + extension;
|
||||
return new File(mainActivity.getFilesDir(), audio);
|
||||
}
|
||||
|
||||
public File getAudioSampleFile() {
|
||||
return new File(mainActivity.getFilesDir(), "audio-sample.wav");
|
||||
}
|
||||
|
||||
public void setActive() {
|
||||
android.util.Log.i(MainActivity.TAG, "Audio Tab Activated");
|
||||
disableLogCallback();
|
||||
createAudioSample();
|
||||
enableLogCallback();
|
||||
Popup.show(mainActivity, Tooltip.AUDIO_TEST_TOOLTIP_TEXT);
|
||||
}
|
||||
|
||||
public void appendLog(final String logMessage) {
|
||||
outputText.append(logMessage);
|
||||
}
|
||||
|
||||
public void clearLog() {
|
||||
outputText.setText("");
|
||||
}
|
||||
|
||||
protected void showProgressDialog() {
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
protected void hideProgressDialog() {
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
|
||||
public String generateAudioEncodeScript() {
|
||||
String audioCodec = selectedCodec;
|
||||
String audioSampleFile = getAudioSampleFile().getAbsolutePath();
|
||||
String audioOutputFile = getAudioOutputFile().getAbsolutePath();
|
||||
|
||||
switch (audioCodec) {
|
||||
case "mp3 (liblame)":
|
||||
return String.format("-y -i %s -c:a libmp3lame -qscale:a 2 %s", audioSampleFile, audioOutputFile);
|
||||
case "mp3 (libshine)":
|
||||
return String.format("-y -i %s -c:a libshine -qscale:a 2 %s", audioSampleFile, audioOutputFile);
|
||||
case "vorbis":
|
||||
return String.format("-y -i %s -c:a libvorbis -b:a 64k %s", audioSampleFile, audioOutputFile);
|
||||
case "opus":
|
||||
return String.format("-y -i %s -c:a libopus -b:a 64k -vbr on -compression_level 10 %s", audioSampleFile, audioOutputFile);
|
||||
case "amr":
|
||||
return String.format("-y -i %s -ar 8000 -ab 12.2k %s", audioSampleFile, audioOutputFile);
|
||||
case "ilbc":
|
||||
return String.format("-y -i %s -c:a ilbc -ar 8000 -b:a 15200 %s", audioSampleFile, audioOutputFile);
|
||||
case "speex":
|
||||
return String.format("-y -i %s -c:a libspeex -ar 16000 %s", audioSampleFile, audioOutputFile);
|
||||
case "wavpack":
|
||||
return String.format("-y -i %s -c:a wavpack -b:a 64k %s", audioSampleFile, audioOutputFile);
|
||||
default:
|
||||
|
||||
// soxr
|
||||
return String.format("-y -i %s -af aresample=resampler=soxr -ar 44100 %s", audioSampleFile, audioOutputFile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,11 +19,7 @@
|
||||
|
||||
package com.arthenica.mobileffmpeg.test;
|
||||
|
||||
import android.arch.core.util.Function;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -33,50 +29,20 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
import com.arthenica.mobileffmpeg.Log;
|
||||
import com.arthenica.mobileffmpeg.LogCallback;
|
||||
import com.arthenica.mobileffmpeg.LogMessage;
|
||||
import com.arthenica.mobileffmpeg.util.RunCallback;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class CommandTabFragment extends Fragment {
|
||||
|
||||
private Context context;
|
||||
private MainActivity mainActivity;
|
||||
private EditText commandText;
|
||||
private TextView logText;
|
||||
private final Queue<String> logQueue;
|
||||
|
||||
public CommandTabFragment() {
|
||||
logQueue = new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
public void enableLogRedirection() {
|
||||
Log.enableCallbackFunction(new Function<byte[], Void>() {
|
||||
|
||||
@Override
|
||||
public Void apply(byte[] logMessageBytes) {
|
||||
logQueue.add(new String(logMessageBytes));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void disableLogRedirection() {
|
||||
Log.enableCallbackFunction(null);
|
||||
}
|
||||
|
||||
public static CommandTabFragment newInstance(final Context context) {
|
||||
CommandTabFragment fragment = new CommandTabFragment();
|
||||
fragment.setContext(context);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
private TextView outputText;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
@@ -90,11 +56,6 @@ public class CommandTabFragment extends Fragment {
|
||||
if (getView() != null) {
|
||||
commandText = getView().findViewById(R.id.commandText);
|
||||
|
||||
// CHANGE LOG TEXT COLOR
|
||||
logText = getView().findViewById(R.id.logText);
|
||||
logText.setBackgroundColor(Color.LTGRAY);
|
||||
logText.setMovementMethod(new ScrollingMovementMethod());
|
||||
|
||||
View runButton = getView().findViewById(R.id.runButton);
|
||||
runButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@@ -112,81 +73,107 @@ public class CommandTabFragment extends Fragment {
|
||||
runFFmpegAsync();
|
||||
}
|
||||
});
|
||||
|
||||
outputText = getView().findViewById(R.id.outputText);
|
||||
outputText.setMovementMethod(new ScrollingMovementMethod());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
setActive();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainActivity(MainActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
||||
public static CommandTabFragment newInstance(final MainActivity mainActivity) {
|
||||
final CommandTabFragment fragment = new CommandTabFragment();
|
||||
fragment.setMainActivity(mainActivity);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void runFFmpeg() {
|
||||
enableLogRedirection();
|
||||
String command = commandText.getText().toString();
|
||||
String[] split = command.split(" ");
|
||||
|
||||
final CountDownLatch logSync = new CountDownLatch(1);
|
||||
clearLog();
|
||||
waitForLogs(logSync);
|
||||
|
||||
int returnCode = FFmpeg.execute(split);
|
||||
android.util.Log.i(MainActivity.TAG, String.format("Process exited with rc %d.", returnCode));
|
||||
Toast.makeText(context, "Run completed", Toast.LENGTH_SHORT).show();
|
||||
logSync.countDown();
|
||||
disableLogRedirection();
|
||||
final String ffmpegCommand = commandText.getText().toString();
|
||||
|
||||
android.util.Log.d(MainActivity.TAG, "Testing 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));
|
||||
|
||||
if (result != 0) {
|
||||
Popup.show(mainActivity, "Command failed. Please check output for the details.");
|
||||
}
|
||||
}
|
||||
|
||||
public void runFFmpegAsync() {
|
||||
enableLogRedirection();
|
||||
String command = commandText.getText().toString();
|
||||
String[] arguments = command.split(" ");
|
||||
|
||||
final CountDownLatch logSync = new CountDownLatch(1);
|
||||
clearLog();
|
||||
waitForLogs(logSync);
|
||||
|
||||
MainActivity.executeAsync(new Function<Integer, Void>() {
|
||||
final String ffmpegCommand = commandText.getText().toString();
|
||||
|
||||
android.util.Log.d(MainActivity.TAG, "Testing COMMAND asynchronously.");
|
||||
|
||||
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process started with arguments\n\'%s\'", ffmpegCommand));
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public Void apply(Integer returnCode) {
|
||||
android.util.Log.i(MainActivity.TAG, String.format("Async process exited with rc %d.", returnCode));
|
||||
logSync.countDown();
|
||||
disableLogRedirection();
|
||||
return null;
|
||||
public void apply(int result) {
|
||||
|
||||
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process exited with rc %d", result));
|
||||
|
||||
if (result != 0) {
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
@Override
|
||||
public Object call() {
|
||||
Popup.show(mainActivity, "Command failed. Please check output for the details.");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}, arguments);
|
||||
}, ffmpegCommand);
|
||||
}
|
||||
|
||||
public void setActive() {
|
||||
android.util.Log.i(MainActivity.TAG, "Command Tab Activated");
|
||||
enableLogCallback();
|
||||
Popup.show(mainActivity, Tooltip.COMMAND_TEST_TOOLTIP_TEXT);
|
||||
}
|
||||
|
||||
public void appendLog(final String logMessage) {
|
||||
logText.append(logMessage);
|
||||
logText.append("\n");
|
||||
outputText.append(logMessage);
|
||||
}
|
||||
|
||||
public void clearLog() {
|
||||
logQueue.clear();
|
||||
logText.setText("");
|
||||
}
|
||||
|
||||
public void waitForLogs(final CountDownLatch count) {
|
||||
final Handler handler = new Handler();
|
||||
final Runnable runnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String logMessage;
|
||||
|
||||
do {
|
||||
logMessage = logQueue.poll();
|
||||
if (logMessage != null) {
|
||||
appendLog(logMessage);
|
||||
}
|
||||
} while (logMessage != null);
|
||||
|
||||
if (count.getCount() < 1) {
|
||||
Toast.makeText(context, "Run completed", Toast.LENGTH_SHORT).show();
|
||||
handler.removeCallbacks(this);
|
||||
|
||||
} else {
|
||||
handler.postDelayed(this, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
handler.postDelayed(runnable, 1000);
|
||||
outputText.setText("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
import com.arthenica.mobileffmpeg.LogCallback;
|
||||
import com.arthenica.mobileffmpeg.LogMessage;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class HttpsTabFragment extends Fragment {
|
||||
|
||||
public static final String HTTPS_TEST_DEFAULT_URL = "https://download.blender.org/peach/trailer/trailer_400p.ogg";
|
||||
|
||||
private MainActivity mainActivity;
|
||||
private EditText urlText;
|
||||
private TextView outputText;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_https_tab, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (getView() != null) {
|
||||
urlText = getView().findViewById(R.id.urlText);
|
||||
|
||||
View getInfoButton = getView().findViewById(R.id.getInfoButton);
|
||||
getInfoButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
getInfo();
|
||||
}
|
||||
});
|
||||
|
||||
outputText = getView().findViewById(R.id.outputText);
|
||||
outputText.setMovementMethod(new ScrollingMovementMethod());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
setActive();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainActivity(MainActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
||||
public static HttpsTabFragment newInstance(final MainActivity mainActivity) {
|
||||
final HttpsTabFragment fragment = new HttpsTabFragment();
|
||||
fragment.setMainActivity(mainActivity);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void getInfo() {
|
||||
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));
|
||||
} else {
|
||||
android.util.Log.d(MainActivity.TAG, String.format("Testing HTTPS with url '%s'", testUrl));
|
||||
}
|
||||
|
||||
// HTTPS COMMAND ARGUMENTS
|
||||
final String ffmpegCommand = String.format("-hide_banner -i %s", testUrl);
|
||||
|
||||
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
|
||||
|
||||
// EXECUTE
|
||||
int result = FFmpeg.execute(ffmpegCommand);
|
||||
|
||||
android.util.Log.d(MainActivity.TAG, String.format("FFmpeg process exited with rc %d", result));
|
||||
}
|
||||
|
||||
public void setActive() {
|
||||
android.util.Log.i(MainActivity.TAG, "Https Tab Activated");
|
||||
enableLogCallback();
|
||||
Popup.show(mainActivity, Tooltip.HTTPS_TEST_TOOLTIP_TEXT);
|
||||
}
|
||||
|
||||
public void appendLog(final String logMessage) {
|
||||
outputText.append(logMessage);
|
||||
}
|
||||
|
||||
public void clearLog() {
|
||||
outputText.setText("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,50 +19,140 @@
|
||||
|
||||
package com.arthenica.mobileffmpeg.test;
|
||||
|
||||
import android.arch.core.util.Function;
|
||||
import android.Manifest;
|
||||
import android.app.ActionBar;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.view.PagerTabStrip;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
import com.arthenica.mobileffmpeg.util.RunCallback;
|
||||
import com.arthenica.mobileffmpeg.util.AsynchronousTaskService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "mobile-ffmpeg-test";
|
||||
|
||||
protected static AsynchronousTaskService asynchronousTaskService = new AsynchronousTaskService();
|
||||
public static final int REQUEST_EXTERNAL_STORAGE = 1;
|
||||
public static String[] PERMISSIONS_STORAGE = {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
};
|
||||
|
||||
protected static final AsynchronousTaskService asynchronousTaskService = new AsynchronousTaskService();
|
||||
|
||||
protected static final Queue<Callable> actionQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
protected static final Handler handler = new Handler();
|
||||
|
||||
protected static final Runnable runnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Callable callable;
|
||||
|
||||
do {
|
||||
callable = actionQueue.poll();
|
||||
if (callable != null) {
|
||||
try {
|
||||
callable.call();
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(MainActivity.TAG, "Running UI action received error.", e);
|
||||
}
|
||||
}
|
||||
} while (callable != null);
|
||||
|
||||
handler.postDelayed(this, 250);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
final ViewPager viewPager = findViewById(R.id.pager);
|
||||
android.support.v7.app.ActionBar supportActionBar = getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
|
||||
supportActionBar.setCustomView(R.layout.action_bar);
|
||||
}
|
||||
|
||||
final PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager(), this, 2);
|
||||
viewPager.setAdapter(adapter);
|
||||
PagerTabStrip pagerTabStrip = findViewById(R.id.pagerTabStrip);
|
||||
if (pagerTabStrip != null) {
|
||||
pagerTabStrip.setDrawFullUnderline(false);
|
||||
pagerTabStrip.setTabIndicatorColorResource(R.color.navigationColor);
|
||||
pagerTabStrip.setTextColor(Color.parseColor("#f39c12"));
|
||||
}
|
||||
|
||||
final ViewPager viewPager = findViewById(R.id.pager);
|
||||
viewPager.setAdapter(new PagerAdapter(getSupportFragmentManager(), this, 6));
|
||||
|
||||
waitForUIAction();
|
||||
|
||||
// VERIFY PERMISSIONS
|
||||
int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
PERMISSIONS_STORAGE,
|
||||
REQUEST_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
try {
|
||||
registerAppFont();
|
||||
Log.d(TAG, "Application fonts registered.");
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "Font registration failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
handler.removeCallbacks(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts a new asynchronous FFmpeg operation with arguments provided.
|
||||
*
|
||||
* @param callbackFunction callback function to receive result of this execution
|
||||
* @param arguments FFmpeg command options/arguments
|
||||
* @param runCallback callback function to receive result of this execution
|
||||
* @param arguments FFmpeg command options/arguments
|
||||
* @return <code>Future</code> instance of asynchronous operation started
|
||||
*/
|
||||
public static Future executeAsync(final Function<Integer, Void> callbackFunction, final String ... arguments) {
|
||||
public static Future executeAsync(final RunCallback runCallback, final String arguments) {
|
||||
return asynchronousTaskService.runAsynchronously(new Callable<Integer>() {
|
||||
|
||||
@Override
|
||||
public Integer call() {
|
||||
int returnCode = FFmpeg.execute(arguments);
|
||||
if (callbackFunction != null) {
|
||||
callbackFunction.apply(returnCode);
|
||||
if (runCallback != null) {
|
||||
runCallback.apply(returnCode);
|
||||
}
|
||||
|
||||
return returnCode;
|
||||
@@ -70,11 +160,97 @@ public class MainActivity extends AppCompatActivity {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
public static void waitForUIAction() {
|
||||
handler.postDelayed(runnable, 250);
|
||||
}
|
||||
|
||||
FFmpeg.shutdown();
|
||||
public static void addUIAction(final Callable callable) {
|
||||
actionQueue.add(callable);
|
||||
}
|
||||
|
||||
public AlertDialog createProgressDialog(final String text) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
if (inflater != null) {
|
||||
View dialogView = inflater.inflate(R.layout.progress_dialog_layout, null);
|
||||
builder.setView(dialogView);
|
||||
|
||||
TextView textView = dialogView.findViewById(R.id.progressDialogText);
|
||||
if (textView != null) {
|
||||
textView.setText(text);
|
||||
}
|
||||
}
|
||||
builder.setCancelable(false);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public AlertDialog createCancellableProgressDialog(final String text, final View.OnClickListener onClickListener) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
if (inflater != null) {
|
||||
View dialogView = inflater.inflate(R.layout.cancellable_progress_dialog_layout, null);
|
||||
builder.setView(dialogView);
|
||||
|
||||
TextView textView = dialogView.findViewById(R.id.progressDialogText);
|
||||
if (textView != null) {
|
||||
textView.setText(text);
|
||||
}
|
||||
Button cancelButton = dialogView.findViewById(R.id.cancelButton);
|
||||
if (cancelButton != null) {
|
||||
cancelButton.setOnClickListener(onClickListener);
|
||||
}
|
||||
}
|
||||
builder.setCancelable(false);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
protected void resourceToFile(final int resourceId, final File file) throws IOException {
|
||||
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resourceId);
|
||||
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
protected void rawResourceToFile(final int resourceId, final File file) throws IOException {
|
||||
final InputStream inputStream = getResources().openRawResource(resourceId);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
final FileOutputStream outputStream = new FileOutputStream(file);
|
||||
|
||||
try {
|
||||
final byte[] buffer = new byte[1024];
|
||||
int readSize;
|
||||
|
||||
while ((readSize = inputStream.read(buffer)) > 0) {
|
||||
outputStream.write(buffer, 0, readSize);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "Saving raw resource failed.", e);
|
||||
} finally {
|
||||
inputStream.close();
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerAppFont() throws IOException {
|
||||
final File cacheDirectory = getCacheDir();
|
||||
|
||||
// SAVE FONTS
|
||||
rawResourceToFile(R.raw.doppioone_regular, new File(cacheDirectory, "doppioone_regular.ttf"));
|
||||
rawResourceToFile(R.raw.truenorg, new File(cacheDirectory, "truenorg.otf"));
|
||||
|
||||
final HashMap<String, String> fontNameMapping = new HashMap<>();
|
||||
fontNameMapping.put("MyFontName", "Doppio One");
|
||||
Config.setFontDirectory(this, cacheDirectory.getAbsolutePath(), fontNameMapping);
|
||||
// Config.setFontDirectory(this, cacheDirectory.getAbsolutePath(), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,30 +19,41 @@
|
||||
|
||||
package com.arthenica.mobileffmpeg.test;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
|
||||
public class PagerAdapter extends FragmentPagerAdapter {
|
||||
private Context context;
|
||||
private final MainActivity mainActivity;
|
||||
private int numberOfTabs;
|
||||
|
||||
PagerAdapter(FragmentManager fragmentManager, Context context, int numberOfTabs) {
|
||||
PagerAdapter(final FragmentManager fragmentManager, final MainActivity mainActivity, final int numberOfTabs) {
|
||||
super(fragmentManager);
|
||||
|
||||
this.context = context;
|
||||
this.mainActivity = mainActivity;
|
||||
this.numberOfTabs = numberOfTabs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
public Fragment getItem(final int position) {
|
||||
switch (position) {
|
||||
case 0: {
|
||||
return CommandTabFragment.newInstance(context);
|
||||
return CommandTabFragment.newInstance(mainActivity);
|
||||
}
|
||||
case 1: {
|
||||
return SlideshowTabFragment.newInstance(context);
|
||||
return VideoTabFragment.newInstance(mainActivity);
|
||||
}
|
||||
case 2: {
|
||||
return HttpsTabFragment.newInstance(mainActivity);
|
||||
}
|
||||
case 3: {
|
||||
return AudioTabFragment.newInstance(mainActivity);
|
||||
}
|
||||
case 4: {
|
||||
return SubtitleTabFragment.newInstance(mainActivity);
|
||||
}
|
||||
case 5: {
|
||||
return VidStabTabFragment.newInstance(mainActivity);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
@@ -56,13 +67,25 @@ public class PagerAdapter extends FragmentPagerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
public CharSequence getPageTitle(final int position) {
|
||||
switch (position) {
|
||||
case 0: {
|
||||
return context.getString(R.string.command_tab);
|
||||
return mainActivity.getString(R.string.command_tab);
|
||||
}
|
||||
case 1: {
|
||||
return context.getString(R.string.slideshow_tab);
|
||||
return mainActivity.getString(R.string.video_tab);
|
||||
}
|
||||
case 2: {
|
||||
return mainActivity.getString(R.string.https_tab);
|
||||
}
|
||||
case 3: {
|
||||
return mainActivity.getString(R.string.audio_tab);
|
||||
}
|
||||
case 4: {
|
||||
return mainActivity.getString(R.string.subtitle_tab);
|
||||
}
|
||||
case 5: {
|
||||
return mainActivity.getString(R.string.vidstab_tab);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class Popup {
|
||||
|
||||
public static void show(final Context context, final String message) {
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +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;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* <p>Generates FFmpeg command arguments to create a 640x427 video from provided images
|
||||
*
|
||||
* @author Taner Sener
|
||||
*/
|
||||
public class Slideshow {
|
||||
|
||||
public static String generateScript(final File fileDirectory, final String image1, final String image2, final String image3, final String videoFile, final String videoCodec, final String customOptions) {
|
||||
return
|
||||
"-loop 1 -i " + fileDirectory + "/" + image1 + " " +
|
||||
"-loop 1 -i " + fileDirectory + "/" + image2 + " " +
|
||||
"-loop 1 -i " + fileDirectory + "/" + image3 + " " +
|
||||
"-filter_complex " +
|
||||
"[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];"+
|
||||
"[1:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream2out1][stream2out2];"+
|
||||
"[2:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream3out1][stream3out2];"+
|
||||
"[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];"+
|
||||
"[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];"+
|
||||
"[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];"+
|
||||
"[stream2out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30),split=2[stream2starting][stream2ending];"+
|
||||
"[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];"+
|
||||
"[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];"+
|
||||
"[stream2starting][stream1ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream2blended];"+
|
||||
"[stream3starting][stream2ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream3blended];"+
|
||||
"[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,format=yuv420p[video]"+
|
||||
" -map [video] -vsync 2 -async 1 " + customOptions + "-c:v " + videoCodec.toLowerCase() + " -r 30 " + fileDirectory + "/" + videoFile;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,242 +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;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.arch.core.util.Function;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.Toast;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
|
||||
|
||||
public class SlideshowTabFragment extends Fragment {
|
||||
|
||||
public static final String DEFAULT_VIDEO_CODEC = "mpeg4";
|
||||
|
||||
private Context context;
|
||||
private View playButton;
|
||||
private String asyncResult;
|
||||
private EditText videoCodecText;
|
||||
|
||||
public SlideshowTabFragment() {
|
||||
}
|
||||
|
||||
public static SlideshowTabFragment newInstance(final Context context) {
|
||||
final SlideshowTabFragment fragment = new SlideshowTabFragment();
|
||||
fragment.setContext(context);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_slideshow_tab, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (getView() != null) {
|
||||
View createButton = getView().findViewById(R.id.slideshowCreateButton);
|
||||
if (createButton != null) {
|
||||
createButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createSlideshow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final VideoView videoView = getView().findViewById(R.id.videoView);
|
||||
videoView.setBackgroundColor(Color.LTGRAY);
|
||||
|
||||
videoCodecText = getView().findViewById(R.id.videoCodecText);
|
||||
videoCodecText.setText(DEFAULT_VIDEO_CODEC);
|
||||
videoCodecText.setSelection(DEFAULT_VIDEO_CODEC.length());
|
||||
|
||||
// PLAY BUTTON IS DISABLED AT STARTUP
|
||||
playButton = getView().findViewById(R.id.slideshowPlayButton);
|
||||
if (playButton != null) {
|
||||
playButton.setEnabled(false);
|
||||
playButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
MediaController mediaController = new MediaController(context);
|
||||
mediaController.setAnchorView(videoView);
|
||||
videoView.setVideoURI(Uri.parse("file://" + getVideoPath()));
|
||||
videoView.setMediaController(mediaController);
|
||||
videoView.requestFocus();
|
||||
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
videoView.setBackgroundColor(0x00000000);
|
||||
}
|
||||
});
|
||||
videoView.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getVideoPath() {
|
||||
String videoCodec = videoCodecText.getText().toString();
|
||||
|
||||
final String extension;
|
||||
switch (videoCodec) {
|
||||
case "libaom-av1": {
|
||||
extension = "mkv";
|
||||
} break;
|
||||
default: {
|
||||
extension = "mp4";
|
||||
}
|
||||
}
|
||||
final String video = "slideshow." + extension;
|
||||
|
||||
return new File(context.getFilesDir(), video).getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getCustomOptions() {
|
||||
String videoCodec = videoCodecText.getText().toString();
|
||||
|
||||
switch (videoCodec) {
|
||||
case "libaom-av1": {
|
||||
|
||||
// libaom support is still experimental, requires this option
|
||||
return "-strict experimental ";
|
||||
}
|
||||
default: {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createSlideshow() {
|
||||
final String image1 = "colosseum.jpg";
|
||||
final String image2 = "pyramid.jpg";
|
||||
final String image3 = "tajmahal.jpg";
|
||||
|
||||
final ProgressDialog progressDialog = ProgressDialog.show(context, "", "Creating video slideshow");
|
||||
|
||||
try {
|
||||
String videoCodec = videoCodecText.getText().toString();
|
||||
if (videoCodec.trim().length() == 0) {
|
||||
videoCodec = DEFAULT_VIDEO_CODEC;
|
||||
}
|
||||
Log.i(TAG, String.format("Creating slideshow using video codec: %s", videoCodec));
|
||||
|
||||
resourceToFile(R.drawable.colosseum, image1);
|
||||
resourceToFile(R.drawable.pyramid, image2);
|
||||
resourceToFile(R.drawable.tajmahal, image3);
|
||||
|
||||
File file = new File(getVideoPath());
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
asyncResult = null;
|
||||
|
||||
final Handler handler = new Handler();
|
||||
final Runnable runnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (asyncResult != null) {
|
||||
String message = (asyncResult.equals("0")) ? "Slideshow created" : "Create failed with rc=" + asyncResult;
|
||||
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
|
||||
handler.removeCallbacks(this);
|
||||
|
||||
if (asyncResult.equals("0")) {
|
||||
playButton.setEnabled(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
handler.postDelayed(this, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
handler.postDelayed(runnable, 1000);
|
||||
|
||||
String script = Slideshow.generateScript(context.getFilesDir(), image1, image2, image3, file.getName(), videoCodec, getCustomOptions());
|
||||
Log.d(TAG, "Creating slideshow: " + script);
|
||||
MainActivity.executeAsync(new Function<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public Void apply(Integer returnCode) {
|
||||
progressDialog.cancel();
|
||||
Log.i(TAG, "Create completed");
|
||||
asyncResult = String.valueOf(returnCode);
|
||||
return null;
|
||||
}
|
||||
}, script.split(" "));
|
||||
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Creating slideshow failed", e);
|
||||
Toast.makeText(context, "Creating slideshow failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
protected void resourceToFile(final int resourceId, final String fileName) throws IOException {
|
||||
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resourceId);
|
||||
|
||||
File file = new File(context.getFilesDir(), fileName);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
Log.d(TAG, String.format("Saved resource %d to file %s.", resourceId, fileName));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* 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.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.TextView;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
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.RunCallback;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.arthenica.mobileffmpeg.FFmpeg.RETURN_CODE_CANCEL;
|
||||
import static com.arthenica.mobileffmpeg.FFmpeg.RETURN_CODE_SUCCESS;
|
||||
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
|
||||
|
||||
public class SubtitleTabFragment extends Fragment {
|
||||
|
||||
private enum State {
|
||||
IDLE,
|
||||
CREATING,
|
||||
BURNING
|
||||
}
|
||||
|
||||
private MainActivity mainActivity;
|
||||
private VideoView videoView;
|
||||
private AlertDialog createProgressDialog;
|
||||
private AlertDialog burnProgressDialog;
|
||||
private Statistics statistics;
|
||||
private State state;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_subtitle_tab, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (getView() != null) {
|
||||
View burnSubtitlesButton = getView().findViewById(R.id.burnSubtitlesButton);
|
||||
burnSubtitlesButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
burnSubtitles();
|
||||
}
|
||||
});
|
||||
|
||||
videoView = getView().findViewById(R.id.videoPlayerFrame);
|
||||
}
|
||||
|
||||
state = State.IDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
setActive();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainActivity(MainActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
||||
public static SubtitleTabFragment newInstance(final MainActivity mainActivity) {
|
||||
final SubtitleTabFragment fragment = new SubtitleTabFragment();
|
||||
fragment.setMainActivity(mainActivity);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void enableLogCallback() {
|
||||
Config.enableLogCallback(new LogCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(LogMessage message) {
|
||||
android.util.Log.d(MainActivity.TAG, message.getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void enableStatisticsCallback() {
|
||||
Config.enableStatisticsCallback(new StatisticsCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final Statistics newStatistics) {
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
SubtitleTabFragment.this.statistics = newStatistics;
|
||||
updateProgressDialog();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void burnSubtitles() {
|
||||
final File image1File = new File(mainActivity.getCacheDir(), "colosseum.jpg");
|
||||
final File image2File = new File(mainActivity.getCacheDir(), "pyramid.jpg");
|
||||
final File image3File = new File(mainActivity.getCacheDir(), "tajmahal.jpg");
|
||||
final File videoFile = getVideoFile();
|
||||
final File videoWithSubtitlesFile = getVideoWithSubtitlesFile();
|
||||
|
||||
try {
|
||||
|
||||
// IF VIDEO IS PLAYING STOP PLAYBACK
|
||||
videoView.stopPlayback();
|
||||
|
||||
if (videoFile.exists()) {
|
||||
videoFile.delete();
|
||||
}
|
||||
|
||||
if (videoWithSubtitlesFile.exists()) {
|
||||
videoWithSubtitlesFile.delete();
|
||||
}
|
||||
|
||||
Log.d(TAG, "Testing SUBTITLE burning");
|
||||
|
||||
showCreateProgressDialog();
|
||||
|
||||
mainActivity.resourceToFile(R.drawable.colosseum, image1File);
|
||||
mainActivity.resourceToFile(R.drawable.pyramid, image2File);
|
||||
mainActivity.resourceToFile(R.drawable.tajmahal, image3File);
|
||||
mainActivity.rawResourceToFile(R.raw.subtitle, getSubtitleFile());
|
||||
|
||||
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));
|
||||
|
||||
state = State.CREATING;
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final int returnCode) {
|
||||
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
|
||||
state = State.IDLE;
|
||||
|
||||
hideCreateProgressDialog();
|
||||
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
|
||||
Log.d(TAG, "Create completed successfully; burning subtitles.");
|
||||
|
||||
String burnSubtitlesCommand = String.format("-y -i %s -vf subtitles=%s:force_style='FontName=MyFontName' -c:v mpeg4 %s", videoFile.getAbsolutePath(), getSubtitleFile().getAbsolutePath(), videoWithSubtitlesFile.getAbsolutePath());
|
||||
|
||||
showBurnProgressDialog();
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", burnSubtitlesCommand));
|
||||
|
||||
state = State.BURNING;
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final int returnCode) {
|
||||
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
|
||||
state = State.IDLE;
|
||||
|
||||
hideBurnProgressDialog();
|
||||
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
Log.d(TAG, "Burn subtitles completed successfully; playing video.");
|
||||
playVideo();
|
||||
} else if (returnCode == RETURN_CODE_CANCEL) {
|
||||
Popup.show(mainActivity, "Burn subtitles operation cancelled.");
|
||||
Log.d(TAG, "Burn subtitles operation cancelled");
|
||||
} else {
|
||||
Popup.show(mainActivity, "Burn subtitles failed. Please check log for the details.");
|
||||
Log.d(TAG, String.format("Burn subtitles failed with rc=%d", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, burnSubtitlesCommand);
|
||||
|
||||
} else if (returnCode == RETURN_CODE_CANCEL) {
|
||||
Popup.show(mainActivity, "Create operation cancelled.");
|
||||
Log.d(TAG, "Create operation cancelled");
|
||||
} else {
|
||||
Popup.show(mainActivity, "Create video failed. Please check log for the details.");
|
||||
Log.d(TAG, String.format("Create failed with rc=%d", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, ffmpegCommand);
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Burn subtitles failed", e);
|
||||
Popup.show(mainActivity, "Burn subtitles failed");
|
||||
}
|
||||
}
|
||||
|
||||
protected void playVideo() {
|
||||
MediaController mediaController = new MediaController(mainActivity);
|
||||
mediaController.setAnchorView(videoView);
|
||||
videoView.setVideoURI(Uri.parse("file://" + getVideoWithSubtitlesFile().getAbsolutePath()));
|
||||
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.start();
|
||||
}
|
||||
|
||||
public File getSubtitleFile() {
|
||||
return new File(mainActivity.getCacheDir(), "subtitle.srt");
|
||||
}
|
||||
|
||||
public File getVideoFile() {
|
||||
return new File(mainActivity.getFilesDir(), "video.mp4");
|
||||
}
|
||||
|
||||
public File getVideoWithSubtitlesFile() {
|
||||
return new File(mainActivity.getFilesDir(), "video-with-subtitles.mp4");
|
||||
}
|
||||
|
||||
public void setActive() {
|
||||
android.util.Log.i(MainActivity.TAG, "Subtitle Tab Activated");
|
||||
enableLogCallback();
|
||||
enableStatisticsCallback();
|
||||
Popup.show(mainActivity, Tooltip.SUBTITLE_TEST_TOOLTIP_TEXT);
|
||||
}
|
||||
|
||||
protected void showCreateProgressDialog() {
|
||||
|
||||
// CLEAN STATISTICS
|
||||
statistics = null;
|
||||
Config.resetStatistics();
|
||||
|
||||
createProgressDialog = mainActivity.createCancellableProgressDialog("Creating video", new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
FFmpeg.cancel();
|
||||
}
|
||||
});
|
||||
createProgressDialog.show();
|
||||
}
|
||||
|
||||
protected void updateProgressDialog() {
|
||||
if (statistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int timeInMilliseconds = this.statistics.getTime();
|
||||
int totalVideoDuration = 9000;
|
||||
|
||||
String completePercentage = new BigDecimal(timeInMilliseconds).multiply(new BigDecimal(100)).divide(new BigDecimal(totalVideoDuration), 0, BigDecimal.ROUND_HALF_UP).toString();
|
||||
|
||||
if (state == State.CREATING) {
|
||||
TextView textView = createProgressDialog.findViewById(R.id.progressDialogText);
|
||||
if (textView != null) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void hideCreateProgressDialog() {
|
||||
createProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
protected void showBurnProgressDialog() {
|
||||
|
||||
// CLEAN STATISTICS
|
||||
statistics = null;
|
||||
Config.resetStatistics();
|
||||
|
||||
burnProgressDialog = mainActivity.createCancellableProgressDialog("Burning subtitles", new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
FFmpeg.cancel();
|
||||
}
|
||||
});
|
||||
burnProgressDialog.show();
|
||||
}
|
||||
|
||||
protected void hideBurnProgressDialog() {
|
||||
burnProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.LogCallback;
|
||||
import com.arthenica.mobileffmpeg.LogMessage;
|
||||
import com.arthenica.mobileffmpeg.util.RunCallback;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.arthenica.mobileffmpeg.FFmpeg.RETURN_CODE_SUCCESS;
|
||||
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
|
||||
|
||||
public class VidStabTabFragment extends Fragment {
|
||||
|
||||
private MainActivity mainActivity;
|
||||
private VideoView videoView;
|
||||
private VideoView stabilizedVideoView;
|
||||
private AlertDialog createProgressDialog;
|
||||
private AlertDialog stabilizeProgressDialog;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_vidstab_tab, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (getView() != null) {
|
||||
View stabilizeVideoButton = getView().findViewById(R.id.stabilizeVideoButton);
|
||||
stabilizeVideoButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
stabilizeVideo();
|
||||
}
|
||||
});
|
||||
|
||||
videoView = getView().findViewById(R.id.videoPlayerFrame);
|
||||
stabilizedVideoView = getView().findViewById(R.id.stabilizedVideoPlayerFrame);
|
||||
}
|
||||
|
||||
createProgressDialog = mainActivity.createProgressDialog("Creating video");
|
||||
stabilizeProgressDialog = mainActivity.createProgressDialog("Stabilizing video");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
setActive();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainActivity(MainActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
||||
public static VidStabTabFragment newInstance(final MainActivity mainActivity) {
|
||||
final VidStabTabFragment fragment = new VidStabTabFragment();
|
||||
fragment.setMainActivity(mainActivity);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void enableLogCallback() {
|
||||
Config.enableLogCallback(new LogCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(LogMessage message) {
|
||||
android.util.Log.d(MainActivity.TAG, message.getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stabilizeVideo() {
|
||||
final File image1File = new File(mainActivity.getCacheDir(), "colosseum.jpg");
|
||||
final File image2File = new File(mainActivity.getCacheDir(), "pyramid.jpg");
|
||||
final File image3File = new File(mainActivity.getCacheDir(), "tajmahal.jpg");
|
||||
final File shakeResultsFile = getShakeResultsFile();
|
||||
final File videoFile = getVideoFile();
|
||||
final File stabilizedVideoFile = getStabilizedVideoFile();
|
||||
|
||||
try {
|
||||
|
||||
// IF VIDEO IS PLAYING STOP PLAYBACK
|
||||
videoView.stopPlayback();
|
||||
stabilizedVideoView.stopPlayback();
|
||||
|
||||
if (shakeResultsFile.exists()) {
|
||||
shakeResultsFile.delete();
|
||||
}
|
||||
if (videoFile.exists()) {
|
||||
videoFile.delete();
|
||||
}
|
||||
if (stabilizedVideoFile.exists()) {
|
||||
stabilizedVideoFile.delete();
|
||||
}
|
||||
|
||||
android.util.Log.d(TAG, "Testing VID.STAB");
|
||||
|
||||
showCreateProgressDialog();
|
||||
|
||||
mainActivity.resourceToFile(R.drawable.colosseum, image1File);
|
||||
mainActivity.resourceToFile(R.drawable.pyramid, image2File);
|
||||
mainActivity.resourceToFile(R.drawable.tajmahal, image3File);
|
||||
|
||||
final String ffmpegCommand = Video.generateShakingVideoScript(image1File.getAbsolutePath(), image2File.getAbsolutePath(), image3File.getAbsolutePath(), videoFile.getAbsolutePath());
|
||||
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final int returnCode) {
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
|
||||
hideCreateProgressDialog();
|
||||
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
|
||||
android.util.Log.d(TAG, "Create completed successfully; stabilizing video.");
|
||||
|
||||
final String analyzeVideoCommand = String.format("-y -i %s -vf vidstabdetect=shakiness=10:accuracy=15:result=%s -f null -", videoFile.getAbsolutePath(), shakeResultsFile.getAbsolutePath());
|
||||
|
||||
showStabilizeProgressDialog();
|
||||
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", analyzeVideoCommand));
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final int returnCode) {
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
final String stabilizeVideoCommand = String.format("-y -i %s -vf vidstabtransform=smoothing=30:input=%s -c:v mpeg4 %s", videoFile.getAbsolutePath(), shakeResultsFile.getAbsolutePath(), stabilizedVideoFile.getAbsolutePath());
|
||||
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", stabilizeVideoCommand));
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final int returnCode) {
|
||||
android.util.Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
|
||||
hideStabilizeProgressDialog();
|
||||
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
android.util.Log.d(TAG, "Stabilize video completed successfully; playing videos.");
|
||||
playVideo();
|
||||
playStabilizedVideo();
|
||||
} else {
|
||||
Popup.show(mainActivity, "Stabilize video failed. Please check log for the details.");
|
||||
android.util.Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, stabilizeVideoCommand);
|
||||
|
||||
} else {
|
||||
hideStabilizeProgressDialog();
|
||||
Popup.show(mainActivity, "Stabilize video failed. Please check log for the details.");
|
||||
android.util.Log.d(TAG, String.format("Stabilize video failed with rc=%d", returnCode));
|
||||
}
|
||||
}
|
||||
}, analyzeVideoCommand);
|
||||
|
||||
} else {
|
||||
Popup.show(mainActivity, "Create video failed. Please check log for the details.");
|
||||
android.util.Log.d(TAG, String.format("Create failed with rc=%d", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, ffmpegCommand);
|
||||
|
||||
} catch (IOException e) {
|
||||
android.util.Log.e(TAG, "Stabilize video failed", e);
|
||||
Popup.show(mainActivity, "Stabilize video failed");
|
||||
}
|
||||
}
|
||||
|
||||
protected void playVideo() {
|
||||
MediaController mediaController = new MediaController(mainActivity);
|
||||
mediaController.setAnchorView(videoView);
|
||||
videoView.setVideoURI(Uri.parse("file://" + getVideoFile().getAbsolutePath()));
|
||||
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.start();
|
||||
}
|
||||
|
||||
protected void playStabilizedVideo() {
|
||||
MediaController mediaController = new MediaController(mainActivity);
|
||||
mediaController.setAnchorView(stabilizedVideoView);
|
||||
stabilizedVideoView.setVideoURI(Uri.parse("file://" + getStabilizedVideoFile().getAbsolutePath()));
|
||||
stabilizedVideoView.setMediaController(mediaController);
|
||||
stabilizedVideoView.requestFocus();
|
||||
stabilizedVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
stabilizedVideoView.setBackgroundColor(0x00000000);
|
||||
}
|
||||
});
|
||||
stabilizedVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
stabilizedVideoView.stopPlayback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
stabilizedVideoView.start();
|
||||
}
|
||||
|
||||
public File getShakeResultsFile() {
|
||||
return new File(mainActivity.getCacheDir(), "transforms.trf");
|
||||
}
|
||||
|
||||
public File getVideoFile() {
|
||||
return new File(mainActivity.getFilesDir(), "video.mp4");
|
||||
}
|
||||
|
||||
public File getStabilizedVideoFile() {
|
||||
return new File(mainActivity.getFilesDir(), "video-stabilized.mp4");
|
||||
}
|
||||
|
||||
public void setActive() {
|
||||
android.util.Log.i(MainActivity.TAG, "VidStab Tab Activated");
|
||||
enableLogCallback();
|
||||
Popup.show(mainActivity, Tooltip.VIDSTAB_TEST_TOOLTIP_TEXT);
|
||||
}
|
||||
|
||||
protected void showCreateProgressDialog() {
|
||||
createProgressDialog.show();
|
||||
}
|
||||
|
||||
protected void hideCreateProgressDialog() {
|
||||
createProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
protected void showStabilizeProgressDialog() {
|
||||
stabilizeProgressDialog.show();
|
||||
}
|
||||
|
||||
protected void hideStabilizeProgressDialog() {
|
||||
stabilizeProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>Generates an FFmpeg command to create a 640x427 video from provided images.
|
||||
*
|
||||
* @author Taner Sener
|
||||
*/
|
||||
public class Video {
|
||||
|
||||
public static String generateEncodeVideoScript(final String image1Path, final String image2Path, final String image3Path, final String videoFilePath, final String videoCodec, final String customOptions) {
|
||||
return
|
||||
"-y -loop 1 -i " + image1Path + " " +
|
||||
"-loop 1 -i " + image2Path + " " +
|
||||
"-loop 1 -i " + image3Path + " " +
|
||||
"-filter_complex " +
|
||||
"[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];" +
|
||||
"[1:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream2out1][stream2out2];" +
|
||||
"[2:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream3out1][stream3out2];" +
|
||||
"[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" +
|
||||
"[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];" +
|
||||
"[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];" +
|
||||
"[stream2out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30),split=2[stream2starting][stream2ending];" +
|
||||
"[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];" +
|
||||
"[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" +
|
||||
"[stream2starting][stream1ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream2blended];" +
|
||||
"[stream3starting][stream2ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream3blended];" +
|
||||
"[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]" +
|
||||
" -map [video] -vsync 2 -async 1 " + customOptions + "-c:v " + videoCodec.toLowerCase() + " -r 30 " + videoFilePath;
|
||||
}
|
||||
|
||||
public static String generateShakingVideoScript(final String image1Path, final String image2Path, final String image3Path, final String videoFilePath) {
|
||||
return
|
||||
"-y -loop 1 -i " + image1Path + " " +
|
||||
"-loop 1 -i " + image2Path + " " +
|
||||
"-loop 1 -i " + image3Path + " " +
|
||||
"-f lavfi -i color=black:s=640x427 " +
|
||||
"-filter_complex " +
|
||||
"[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1[stream1out];" +
|
||||
"[1:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1[stream2out];" +
|
||||
"[2:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1[stream3out];" +
|
||||
"[stream1out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream1overlaid];" +
|
||||
"[stream2out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream2overlaid];" +
|
||||
"[stream3out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream3overlaid];" +
|
||||
"[3:v][stream1overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream1shaking];" +
|
||||
"[3:v][stream2overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream2shaking];" +
|
||||
"[3:v][stream3overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream3shaking];" +
|
||||
"[stream1shaking][stream2shaking][stream3shaking]concat=n=3:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]" +
|
||||
" -map [video] -vsync 2 -async 1 -c:v mpeg4 -r 30 " + videoFilePath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* 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.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.LogCallback;
|
||||
import com.arthenica.mobileffmpeg.LogMessage;
|
||||
import com.arthenica.mobileffmpeg.util.RunCallback;
|
||||
import com.arthenica.mobileffmpeg.Statistics;
|
||||
import com.arthenica.mobileffmpeg.StatisticsCallback;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.arthenica.mobileffmpeg.FFmpeg.RETURN_CODE_SUCCESS;
|
||||
import static com.arthenica.mobileffmpeg.test.MainActivity.TAG;
|
||||
|
||||
public class VideoTabFragment extends Fragment implements AdapterView.OnItemSelectedListener {
|
||||
|
||||
private MainActivity mainActivity;
|
||||
private VideoView videoView;
|
||||
private AlertDialog progressDialog;
|
||||
private String selectedCodec;
|
||||
private Statistics statistics;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_video_tab, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (getView() != null) {
|
||||
Spinner videoCodecSpinner = getView().findViewById(R.id.videoCodecSpinner);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(mainActivity,
|
||||
R.array.video_codec, R.layout.spinner_item);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
videoCodecSpinner.setAdapter(adapter);
|
||||
videoCodecSpinner.setOnItemSelectedListener(this);
|
||||
|
||||
View encodeButton = getView().findViewById(R.id.encodeButton);
|
||||
if (encodeButton != null) {
|
||||
encodeButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encodeVideo();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
videoView = getView().findViewById(R.id.videoPlayerFrame);
|
||||
}
|
||||
|
||||
progressDialog = mainActivity.createProgressDialog("Encoding video");
|
||||
|
||||
selectedCodec = getResources().getStringArray(R.array.video_codec)[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
setActive();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainActivity(MainActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
||||
public static VideoTabFragment newInstance(final MainActivity mainActivity) {
|
||||
final VideoTabFragment fragment = new VideoTabFragment();
|
||||
fragment.setMainActivity(mainActivity);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void enableLogCallback() {
|
||||
Config.enableLogCallback(new LogCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(LogMessage message) {
|
||||
android.util.Log.d(MainActivity.TAG, message.getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void enableStatisticsCallback() {
|
||||
Config.enableStatisticsCallback(new StatisticsCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final Statistics newStatistics) {
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
VideoTabFragment.this.statistics = newStatistics;
|
||||
updateProgressDialog();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
selectedCodec = parent.getItemAtPosition(position).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
// DO NOTHING
|
||||
}
|
||||
|
||||
public void encodeVideo() {
|
||||
final File image1File = new File(mainActivity.getCacheDir(), "colosseum.jpg");
|
||||
final File image2File = new File(mainActivity.getCacheDir(), "pyramid.jpg");
|
||||
final File image3File = new File(mainActivity.getCacheDir(), "tajmahal.jpg");
|
||||
final File videoFile = getVideoFile();
|
||||
|
||||
try {
|
||||
|
||||
// IF VIDEO IS PLAYING STOP PLAYBACK
|
||||
videoView.stopPlayback();
|
||||
|
||||
if (videoFile.exists()) {
|
||||
videoFile.delete();
|
||||
}
|
||||
|
||||
final String videoCodec = selectedCodec;
|
||||
|
||||
Log.d(TAG, String.format("Testing VIDEO encoding with '%s' codec", videoCodec));
|
||||
|
||||
showProgressDialog();
|
||||
|
||||
mainActivity.resourceToFile(R.drawable.colosseum, image1File);
|
||||
mainActivity.resourceToFile(R.drawable.pyramid, image2File);
|
||||
mainActivity.resourceToFile(R.drawable.tajmahal, image3File);
|
||||
|
||||
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));
|
||||
|
||||
MainActivity.executeAsync(new RunCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(final int returnCode) {
|
||||
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
|
||||
hideProgressDialog();
|
||||
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
if (returnCode == RETURN_CODE_SUCCESS) {
|
||||
Log.d(TAG, "Encode completed successfully; playing video.");
|
||||
playVideo();
|
||||
} else {
|
||||
Popup.show(mainActivity, "Encode failed. Please check log for the details.");
|
||||
Log.d(TAG, String.format("Encode failed with rc=%d", returnCode));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, ffmpegCommand);
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Encode video failed", e);
|
||||
Popup.show(mainActivity, "Encode video failed");
|
||||
}
|
||||
}
|
||||
|
||||
protected void playVideo() {
|
||||
MediaController mediaController = new MediaController(mainActivity);
|
||||
mediaController.setAnchorView(videoView);
|
||||
videoView.setVideoURI(Uri.parse("file://" + getVideoFile().getAbsolutePath()));
|
||||
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.start();
|
||||
}
|
||||
|
||||
public String getSelectedVideoCodec() {
|
||||
String videoCodec = selectedCodec;
|
||||
|
||||
// VIDEO CODEC SPINNER HAS BASIC NAMES, FFMPEG NEEDS LONGER AND EXACT CODEC NAMES.
|
||||
// APPLYING NECESSARY TRANSFORMATION HERE
|
||||
switch (videoCodec) {
|
||||
case "x264":
|
||||
videoCodec = "libx264";
|
||||
break;
|
||||
case "x265":
|
||||
videoCodec = "libx265";
|
||||
break;
|
||||
case "xvid":
|
||||
videoCodec = "libxvid";
|
||||
break;
|
||||
case "vp8":
|
||||
videoCodec = "libvpx";
|
||||
break;
|
||||
case "vp9":
|
||||
videoCodec = "libvpx-vp9";
|
||||
break;
|
||||
case "aom":
|
||||
videoCodec = "libaom-av1";
|
||||
break;
|
||||
case "kvazaar":
|
||||
videoCodec = "libkvazaar";
|
||||
break;
|
||||
case "theora":
|
||||
videoCodec = "libtheora";
|
||||
break;
|
||||
}
|
||||
|
||||
return videoCodec;
|
||||
}
|
||||
|
||||
public File getVideoFile() {
|
||||
String videoCodec = selectedCodec;
|
||||
|
||||
final String extension;
|
||||
switch (videoCodec) {
|
||||
case "vp8":
|
||||
case "vp9":
|
||||
extension = "webm";
|
||||
break;
|
||||
case "aom":
|
||||
extension = "mkv";
|
||||
break;
|
||||
case "theora":
|
||||
extension = "ogv";
|
||||
break;
|
||||
case "hap":
|
||||
extension = "mov";
|
||||
break;
|
||||
default:
|
||||
|
||||
// mpeg4, x264, x265, xvid, kvazaar
|
||||
extension = "mp4";
|
||||
break;
|
||||
}
|
||||
|
||||
final String video = "video." + extension;
|
||||
return new File(mainActivity.getFilesDir(), video);
|
||||
}
|
||||
|
||||
public String getCustomOptions() {
|
||||
String videoCodec = selectedCodec;
|
||||
|
||||
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 "kvazaar":
|
||||
return "-preset fast ";
|
||||
case "theora":
|
||||
return "-qscale:v 7 ";
|
||||
case "hap":
|
||||
return "-format hap_q ";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public void setActive() {
|
||||
android.util.Log.i(MainActivity.TAG, "Video Tab Activated");
|
||||
enableLogCallback();
|
||||
enableStatisticsCallback();
|
||||
Popup.show(mainActivity, Tooltip.VIDEO_TEST_TOOLTIP_TEXT);
|
||||
}
|
||||
|
||||
protected void showProgressDialog() {
|
||||
|
||||
// CLEAN STATISTICS
|
||||
statistics = null;
|
||||
Config.resetStatistics();
|
||||
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
protected 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void hideProgressDialog() {
|
||||
progressDialog.dismiss();
|
||||
|
||||
MainActivity.addUIAction(new Callable() {
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
VideoTabFragment.this.progressDialog = mainActivity.createProgressDialog("Encoding video");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<stroke android:width="1dp" android:color="#7f8c8d"/>
|
||||
<solid android:color="#bdc3c7"/>
|
||||
<corners
|
||||
android:bottomLeftRadius="6dp"
|
||||
android:bottomRightRadius="6dp"
|
||||
android:topLeftRadius="6dp"
|
||||
android:topRightRadius="6dp" />
|
||||
</shape>
|
||||
@@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<stroke android:width="1dp" android:color="#27ae60"/>
|
||||
<solid android:color="#2ecc71"/>
|
||||
<corners
|
||||
android:bottomLeftRadius="6dp"
|
||||
android:bottomRightRadius="6dp"
|
||||
android:topLeftRadius="6dp"
|
||||
android:topRightRadius="6dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<stroke android:width="1dp" android:color="#f39c12"/>
|
||||
<solid android:color="#f1c40f"/>
|
||||
<corners
|
||||
android:bottomLeftRadius="6dp"
|
||||
android:bottomRightRadius="6dp"
|
||||
android:topLeftRadius="6dp"
|
||||
android:topRightRadius="6dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<stroke android:width="1dp" android:color="#8e44ad"/>
|
||||
<solid android:color="#9b59b6"/>
|
||||
<corners
|
||||
android:bottomLeftRadius="6dp"
|
||||
android:bottomRightRadius="6dp"
|
||||
android:topLeftRadius="6dp"
|
||||
android:topRightRadius="6dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<stroke android:width="1dp" android:color="#bdc3c7"/>
|
||||
<solid android:color="#ecf0f1"/>
|
||||
<corners
|
||||
android:bottomLeftRadius="2dp"
|
||||
android:bottomRightRadius="2dp"
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="2dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/headerColor"
|
||||
android:gravity="top"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.AppCompatTextView
|
||||
android:id="@+id/header"
|
||||
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold"
|
||||
app:fontFamily="sans-serif" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="46dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:gravity="center|start" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progressDialogText"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center|start"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/dialog_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/dialog_cancel_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="normal" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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=".AudioTabFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/audioCodecSpinnerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/audioCodecSpinner"
|
||||
android:background="@drawable/rounded_spinner"
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:padding="6dp"
|
||||
android:textAlignment="center"
|
||||
android:gravity="center"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/encodeButtonLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/encodeButton"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/audio_encode_button_text"
|
||||
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" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -4,45 +4,66 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
tools:context=".CommandTabFragment">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/commandText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:hint="@string/command_text_hint"
|
||||
android:inputType="text" />
|
||||
android:layout_marginTop="20dp"
|
||||
android:hint="@string/command_text_input_placeholder"
|
||||
android:inputType="text"
|
||||
android:singleLine="true" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/runButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:layout_marginEnd="130dp"
|
||||
android:layout_marginStart="130dp"
|
||||
android:text="@string/run_button_text" />
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/command_run_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/runAsyncButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="100dp"
|
||||
android:layout_marginStart="100dp"
|
||||
android:text="@string/run_async_button_text" />
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/command_run_async_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/logText"
|
||||
android:id="@+id/outputText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:padding="6dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/rounded_output_frame"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="bottom"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
android:scrollbars="vertical"
|
||||
android:gravity="bottom"/>
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp"
|
||||
android:typeface="sans" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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=".HttpsTabFragment">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/urlText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:hint="@string/https_text_input_placeholder"
|
||||
android:inputType="textUri"
|
||||
android:singleLine="true" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/getInfoButton"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/https_get_info_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<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="40dp"
|
||||
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" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,70 +0,0 @@
|
||||
<?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"
|
||||
tools:context=".SlideshowTabFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/slideshowCreateButton"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/button_create_slideshow_text"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/slideshowPlayButton"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/button_play_slideshow_text"
|
||||
android:textAlignment="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/videoCodecText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:layout_marginEnd="50dp"
|
||||
android:layout_marginStart="50dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_weight="1"
|
||||
android:ems="10"
|
||||
android:hint="@string/video_text_hint"
|
||||
android:inputType="text|textNoSuggestions"
|
||||
android:selectAllOnFocus="false"
|
||||
android:singleLine="true"
|
||||
android:text="mpeg4"
|
||||
android:textAlignment="center" />
|
||||
</LinearLayout>
|
||||
|
||||
<VideoView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="262dp"
|
||||
android:layout_margin="30dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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=".SubtitleTabFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/burnSubtitlesButtonLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/burnSubtitlesButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/subtitle_burn_subtitles_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<VideoView
|
||||
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_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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=".VideoTabFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/videoCodecSpinnerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/videoCodecSpinner"
|
||||
android:background="@drawable/rounded_spinner"
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:padding="6dp"
|
||||
android:textAlignment="center"
|
||||
android:gravity="center"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/encodeButtonLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/encodeButton"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/video_encode_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<VideoView
|
||||
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>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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=".VidStabTabFragment">
|
||||
|
||||
<VideoView
|
||||
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="10dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/stabilizeVideoButtonLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/stabilizeVideoButton"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:text="@string/vidstab_stabilize_video_button_text"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<VideoView
|
||||
android:id="@+id/stabilizedVideoPlayerFrame"
|
||||
android:layout_width="wrap_content"
|
||||
android:background="@drawable/rounded_video_frame"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_centerInParent="true">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="46dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:gravity="center|start" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progressDialogText"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center|start"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="18sp" />
|
||||
</TableRow>
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,11 @@
|
||||
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="168dp"
|
||||
android:layout_height="40dp"
|
||||
android:gravity="center"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="16sp" />
|
||||
@@ -0,0 +1,13 @@
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="50dp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/rounded_spinner"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="@android:color/black"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"/>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,46 @@
|
||||
Copyright (c) 2011-2012, Julieta Ulanovsky (julieta.ulanovsky@gmail.com), with Reserved Font Names 'Montserrat'
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,11 @@
|
||||
1
|
||||
00:00:00,021 --> 00:00:04,000
|
||||
Colosseum or Coliseum is an oval amphitheatre in the centre of the city of Rome, Italy.
|
||||
|
||||
2
|
||||
00:00:04,001 --> 00:00:06,927
|
||||
The Great Pyramid of Giza is the oldest and largest of the three pyramids in the Giza pyramid complex bordering what is now El Giza, Egypt.
|
||||
|
||||
3
|
||||
00:00:06,928 --> 00:00:09,000
|
||||
The Taj Mahal is an ivory-white marble mausoleum on the south bank of the Yamuna river in the Indian city of Agra.
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="audio_codec">
|
||||
<item>mp3 (liblame)</item>
|
||||
<item>mp3 (libshine)</item>
|
||||
<item>vorbis</item>
|
||||
<item>opus</item>
|
||||
<item>amr</item>
|
||||
<item>ilbc</item>
|
||||
<item>soxr</item>
|
||||
<item>speex</item>
|
||||
<item>wavpack</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -1,6 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
<color name="colorPrimary">#e74c3c</color>
|
||||
<color name="colorPrimaryDark">#c0392b</color>
|
||||
<color name="colorAccent">#3498db</color>
|
||||
<color name="headerColor">#e74c3c</color>
|
||||
<color name="buttonColor">#2ecc71</color>
|
||||
<color name="editColor">#3498db</color>
|
||||
<color name="comboColor">#9b59b6</color>
|
||||
<color name="playerColor">#ecf0f1</color>
|
||||
<color name="outputColor">#f1c40f</color>
|
||||
<color name="navigationBarColor">#f39c12</color>
|
||||
<color name="navigationColor">#e67e22</color>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
<resources>
|
||||
<string name="app_name">MobileFFmpegTest</string>
|
||||
<string name="command_text_hint">Enter command</string>
|
||||
<string name="run_button_text">Run</string>
|
||||
<string name="run_async_button_text">Run Async</string>
|
||||
<string name="button_create_slideshow_text">Create</string>
|
||||
<string name="button_play_slideshow_text">Play</string>
|
||||
<string name="command_tab">Command</string>
|
||||
<string name="slideshow_tab">Slideshow</string>
|
||||
<string name="video_text_hint">Enter video codec</string>
|
||||
<string name="app_name">MobileFFmpeg Test</string>
|
||||
<string name="command_tab">COMMAND</string>
|
||||
<string name="video_tab">VIDEO</string>
|
||||
<string name="https_tab">HTTPS</string>
|
||||
<string name="audio_tab">AUDIO</string>
|
||||
<string name="subtitle_tab">SUBTITLE</string>
|
||||
<string name="vidstab_tab">VID.STAB</string>
|
||||
<string name="command_text_input_placeholder">Enter command</string>
|
||||
<string name="command_run_async_button_text">RUN ASYNC</string>
|
||||
<string name="command_run_button_text">RUN</string>
|
||||
<string name="video_encode_button_text">ENCODE</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>
|
||||
</resources>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="colorPrimary">@color/headerColor</item>
|
||||
<item name="colorPrimaryDark">@color/headerColor</item>
|
||||
<item name="colorAccent">@color/editColor</item>
|
||||
<item name="android:navigationBarColor">@color/editColor</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||