Compare commits
215 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05dee70dcc | |||
| 9fde72ac48 | |||
| f9912c462c | |||
| fdfef17c51 | |||
| 5f86552238 | |||
| b4287287eb | |||
| 5cc967fb92 | |||
| 56df2b1018 | |||
| 5403567631 | |||
| 82e08a56b9 | |||
| e6ff6f2951 | |||
| 1b855d5762 | |||
| 6db573c9b3 | |||
| 3831dcfeb8 | |||
| 010a11aa78 | |||
| 9c4092acd5 | |||
| aa99132930 | |||
| 9dfae4be30 | |||
| 7f9ebf9988 | |||
| 36f5dd2ece | |||
| 133c0c42eb | |||
| 48645800ec | |||
| 998d3bfa37 | |||
| 60d1ed9f90 | |||
| 209131b92f | |||
| 1a88f77684 | |||
| a4fadfce40 | |||
| a737b02e88 | |||
| 235fe08dc4 | |||
| 893dcf8582 | |||
| 2e20471922 | |||
| baf251e659 | |||
| 1a9ac6dbb0 | |||
| 317d01c3e6 | |||
| cd8a8e2145 | |||
| fa8d119669 | |||
| 9c5cf7d825 | |||
| 554bbe3fbf | |||
| 2f2f779d12 | |||
| fe7ea3a975 | |||
| ed755140b3 | |||
| b03c484fd3 | |||
| b665a4ee34 | |||
| f43eaa7a08 | |||
| 55b28d48e6 | |||
| cdd2d54f4c | |||
| fbe3dd4806 | |||
| 76ec9d59cf | |||
| d2a3df33b6 | |||
| 5bffc5ed9d | |||
| 95f6fdd9e4 | |||
| 1a9a47c65b | |||
| 1d3c0e7f60 | |||
| a307a10c32 | |||
| 78e986bf20 | |||
| e702bef31f | |||
| 006a001fd0 | |||
| 7d29088799 | |||
| dfddf18b52 | |||
| 1336eab42f | |||
| e01989639d | |||
| 1b041324b4 | |||
| df3faba968 | |||
| 1b3b2c3c2f | |||
| d9d31985df | |||
| 322877b3c3 | |||
| 9d414b9790 | |||
| 270fc3f1ad | |||
| d2ef2da94b | |||
| f92c5288b9 | |||
| 248a6cba7a | |||
| 318efe8a5d | |||
| ea9abafaa7 | |||
| 9ede8768f5 | |||
| 1fcf112ed0 | |||
| ace472d20c | |||
| 36fa6b0028 | |||
| 29ca9e37e7 | |||
| f0a65465e9 | |||
| 4808e4c686 | |||
| 41f55f415e | |||
| 3716c4623b | |||
| 7ccf16f130 | |||
| 2cc3a9b0db | |||
| 21c071bebe | |||
| caa4ac1f9b | |||
| 09caf1e997 | |||
| b3109cf276 | |||
| 4bd20725a8 | |||
| 0a127d4370 | |||
| ba7afe784d | |||
| e73224d560 | |||
| 4d544fa7ca | |||
| d500463706 | |||
| a3b95188fb | |||
| 7d27ab7be5 | |||
| 7681389a9c | |||
| e85b08e7c2 | |||
| d298c9ac3b | |||
| 5ffc79e879 | |||
| 2f08be1ff9 | |||
| 6f5262748f | |||
| e2a4766c61 | |||
| 63e87d04ef | |||
| fe73ca4697 | |||
| 68026adb2c | |||
| ede6a05766 | |||
| ba6c218b21 | |||
| 1d5b3f7603 | |||
| b33891eba9 | |||
| f7cbe9c006 | |||
| 84b740d514 | |||
| 703459c2cf | |||
| 04d2709eb4 | |||
| 05d2422bd5 | |||
| f57a3c6c05 | |||
| 5392566ed6 | |||
| 58cff5c8fb | |||
| 2f854d843a | |||
| 6d02e9b0dc | |||
| 156f51cacf | |||
| de7c992bd7 | |||
| 9ca086452c | |||
| b96ac2b4b3 | |||
| 05cc4f9998 | |||
| 870c674e0c | |||
| 166404fecc | |||
| 633238b79f | |||
| b6ac3d143d | |||
| e5581f60ce | |||
| 1ad6a02bf7 | |||
| 47d8c54da3 | |||
| e1870ecde9 | |||
| 761aa55a46 | |||
| 1e67fdb1e9 | |||
| da48d1edb8 | |||
| a7c009704e | |||
| c144aa3058 | |||
| c1584b8080 | |||
| dd6341359d | |||
| 107b5f1efd | |||
| 529893fd84 | |||
| 6aee1343f0 | |||
| c0d231337e | |||
| 77d8e86859 | |||
| a578211c67 | |||
| ef1bbd1c78 | |||
| 502df26547 | |||
| fa2d24e136 | |||
| 0eb3f23dde | |||
| 5b999a40c4 | |||
| 22bddcf207 | |||
| 49e7f5e312 | |||
| ecb97b6b9a | |||
| 043138e413 | |||
| 8cb63decf4 | |||
| c81ee56ab4 | |||
| 0a0f008aca | |||
| 328e37db0f | |||
| 4f36ac31fc | |||
| d0bc612062 | |||
| 701860d5ed | |||
| 172af56b15 | |||
| c4c5d60632 | |||
| 9bea827e39 | |||
| 71e7856102 | |||
| bc05d66b5b | |||
| a39d47f71a | |||
| 10b024082a | |||
| d109a8d3d8 | |||
| 6c49e64b66 | |||
| b076562170 | |||
| 149adaed4a | |||
| 4529ec7ba1 | |||
| 25d576abdb | |||
| e2ff9c62a6 | |||
| 5cdec4ed43 | |||
| c039a72e01 | |||
| 4d544be832 | |||
| 66267ef7f0 | |||
| f1c0224aac | |||
| da6a51af70 | |||
| 4c1cd17ba2 | |||
| c1789def81 | |||
| c9449c894e | |||
| 87d1a546ae | |||
| 4a221b2845 | |||
| 492626cc32 | |||
| 5010b2c8d4 | |||
| 7c8743ba07 | |||
| c829f6660f | |||
| c612d2eb15 | |||
| 0933a77265 | |||
| d96b96d12f | |||
| 3e64017839 | |||
| 6885e0aa3e | |||
| 63062cc425 | |||
| 2912f6e407 | |||
| 358dd529f9 | |||
| 0273d60b79 | |||
| 3572c52296 | |||
| 0d8bf5f33f | |||
| 725433e2d8 | |||
| 6687adae27 | |||
| 8b39af836f | |||
| 8474f2d9a8 | |||
| 0b6f2cb90b | |||
| f49e4ef27d | |||
| 44dd71f7b5 | |||
| c64c639fc9 | |||
| bbe441b2a8 | |||
| d3aca67c05 | |||
| 1d08c088a9 | |||
| 6f14c8db77 | |||
| 3e86f7f5f4 |
@@ -1,93 +1,250 @@
|
||||
# MobileFFmpeg [](https://gitter.im/mobile-ffmpeg/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [  ](https://bintray.com/tanersener/maven/mobile-ffmpeg-full/1.2/link)
|
||||
# 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
|
||||
|
||||
### 1. Features
|
||||
- Use prebuilt binaries available under `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.
|
||||
|
||||
`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
|
||||
|
||||
`x264`, `xvidcore`
|
||||
<img src="https://github.com/tanersener/mobile-ffmpeg/blob/dev-v3.x/docs/assets/mobile-ffmpeg-logo-v7.png" width="320">
|
||||
|
||||
- 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
|
||||
### 1. Features
|
||||
- Use binaries available at `Github`/`JCenter`/`CocoaPods` or build your own version with external libraries you need
|
||||
- Supports
|
||||
- Both Android and IOS
|
||||
- FFmpeg `v3.4.x`, `v4.0.x` and `v4.1-dev-x` (master) releases
|
||||
- 27 external 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`
|
||||
`chromaprint`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libaom`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `twolame`, `wavpack`
|
||||
|
||||
- 4 external libraries with GPL license
|
||||
|
||||
`vid.stab`, `x264`, `x265`, `xvidcore`
|
||||
|
||||
- Exposes both FFmpeg library and MobileFFmpeg wrapper library capabilities
|
||||
- Creates shared libraries (.so for Android, .dylib for IOS)
|
||||
- Includes cross-compile instructions for 43 open-source libraries
|
||||
|
||||
`chromaprint`, `expat`, `ffmpeg`, `fontconfig`, `freetype`, `fribidi`, `giflib`, `gmp`, `gnutls`, `kvazaar`, `lame`, `leptonica`, `libaom`, `libass`, `libiconv`, `libilbc`, `libjpeg`, `libjpeg-turbo`, `libogg`, `libpng`, `libsndfile`, `libtheora`, `libuuid`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `nettle`, `opencore-amr`, `opus`, `sdl`, `shine`, `snappy`, `soxr`, `speex`, `tesseract`, `tiff`, `twolame`, `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
|
||||
- Builds `arm-v7a`, `arm-v7a-neon`, `arm64-v8a`, `x86` and `x86_64` architectures
|
||||
- Supports `zlib` and `MediaCodec` system libraries
|
||||
- Creates Android archive with .aar extension
|
||||
|
||||
#### 1.2 IOS
|
||||
- Supports `armv7`, `armv7s`, `arm64`, `i386` and `x86_64` architectures
|
||||
- Builds `armv7`, `armv7s`, `arm64`, `i386` and `x86_64` architectures
|
||||
- Supports `bzip2`, `zlib` system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` system frameworks
|
||||
- `ARC` enabled library
|
||||
- Built with `-fembed-bitcode` flag
|
||||
- Creates IOS dynamic universal (fat) library
|
||||
- Creates IOS shared/dynamic universal (fat) library
|
||||
- Creates IOS static universal (fat) library
|
||||
- Creates IOS dynamic framework for IOS 8 or later
|
||||
- Supports Xcode 7.3.1 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)
|
||||
Binaries are available at [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.
|
||||
Refer to [Using IOS Universal Binaries](https://github.com/tanersener/mobile-ffmpeg/wiki/Using-IOS-Universal-Binaries) guide to import IOS universal binaries released at [Github](https://github.com/tanersener/mobile-ffmpeg/releases).
|
||||
|
||||
| | 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* |
|
||||
| | [  ](https://bintray.com/tanersener/maven/mobile-ffmpeg-min/1.2/link) | [  ](https://bintray.com/tanersener/maven/mobile-ffmpeg-min-gpl/1.2/link) | [  ](https://bintray.com/tanersener/maven/mobile-ffmpeg-https/1.2/link) | [  ](https://bintray.com/tanersener/maven/mobile-ffmpeg-https-gpl/1.2/link) | [  ](https://bintray.com/tanersener/maven/mobile-ffmpeg-full/1.2/link) | [  ](https://bintray.com/tanersener/maven/mobile-ffmpeg-full-gpl/1.2/link) |
|
||||
There are eight different binary packages. Below you can see which system libraries and external libraries are enabled in each of them.
|
||||
|
||||
\* - Supported since `v1.1`
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="center"></th>
|
||||
<th align="center">min</th>
|
||||
<th align="center">min-gpl</th>
|
||||
<th align="center">https</th>
|
||||
<th align="center">https-gpl</th>
|
||||
<th align="center">audio</th>
|
||||
<th align="center">video</th>
|
||||
<th align="center">full</th>
|
||||
<th align="center">full-gpl</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><sup>external libraries</sup></td>
|
||||
<td align="center">-</td>
|
||||
<td align="center"><sup>vid.stab</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
|
||||
<td align="center"><sup>gmp</sup><br><sup>gnutls</sup></td>
|
||||
<td align="center"><sup>gmp</sup><br><sup>gnutls</sup><br><sup>vid.stab</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
|
||||
<td align="center"><sup>chromaprint</sup><br><sup>lame</sup><br><sup>libilbc</sup><br><sup>libvorbis</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>wavpack</sup></td>
|
||||
<td align="center"><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>kvazaar</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libtheora</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>snappy</sup></td>
|
||||
<td align="center"><sup>chromaprint</sup><br><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>sdl</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>tesseract</sup><br><sup>twolame</sup><br><sup>wavpack</sup></td>
|
||||
<td align="center"><sup>chromaprint</sup><br><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libaom</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>sdl</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>tesseract</sup><br><sup>twolame</sup><br><sup>vid.stab</sup><br><sup>wavpack</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><sup>android system libraries</sup></td>
|
||||
<td align="center" colspan=8><sup>zlib</sup><br><sup>MediaCodec</sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><sup>ios system libraries</sup></td>
|
||||
<td align="center" colspan=8><sup>zlib</sup><br><sup>AudioToolbox</sup><br><sup>AVFoundation</sup><br><sup>CoreImage</sup><br><sup>VideoToolbox</sup><br><sup>bzip2</sup></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
\*\* - Supported since `v2.0`
|
||||
- `libilbc`, `opus`, `snappy`, `x264` and `xvidcore` are supported since `v1.1`
|
||||
|
||||
- `libaom` and `soxr` are supported since `v2.0`
|
||||
|
||||
- `chromaprint`, `vid.stab` and `x265` are supported since `v2.1`
|
||||
|
||||
- `sdl`, `tesseract`, `twolame` external libraries; `zlib`, `MediaCodec` Android system libraries; `bzip2`, `zlib` IOS system libraries and `AudioToolbox`, `CoreImage`, `VideoToolbox`, `AVFoundation` IOS system frameworks are supported since `v3.0`
|
||||
|
||||
#### 2.1 Android
|
||||
1. Add MobileFFmpeg dependency from `jcenter()`
|
||||
```
|
||||
dependencies {`
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:1.2'
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:3.0'
|
||||
}
|
||||
```
|
||||
|
||||
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));
|
||||
FFmpeg.execute("-i file1.mp4 -c:v mpeg4 file2.mp4");
|
||||
```
|
||||
|
||||
3. Check execution output.
|
||||
```
|
||||
int rc = FFmpeg.getLastReturnCode();
|
||||
String output = FFmpeg.getLastCommandOutput();
|
||||
|
||||
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 and output=%s.", rc, output));
|
||||
}
|
||||
```
|
||||
|
||||
4. Stop an ongoing operation.
|
||||
```
|
||||
FFmpeg.cancel();
|
||||
```
|
||||
|
||||
5. Get media information for a file.
|
||||
```
|
||||
MediaInformation info = FFmpeg.getMediaInformation('<file path or uri>');
|
||||
```
|
||||
|
||||
6. List enabled external libraries.
|
||||
```
|
||||
List<String> externalLibraries = Config.getExternalLibraries();
|
||||
```
|
||||
|
||||
7. Enable log callback.
|
||||
```
|
||||
Config.enableLogCallback(new LogCallback() {
|
||||
public void apply(LogMessage message) {
|
||||
Log.d(Config.TAG, message.getText());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
8. 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()));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
9. Set log level.
|
||||
```
|
||||
Config.setLogLevel(Level.AV_LOG_FATAL);
|
||||
```
|
||||
|
||||
10. 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', '~> 1.2'
|
||||
pod 'mobile-ffmpeg-full', '~> 3.0'
|
||||
```
|
||||
|
||||
2. Create and execute commands using the following `Objective-C` example.
|
||||
2. 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 result = mobileffmpeg_execute((int) [commandArray count], arguments);
|
||||
|
||||
NSLog(@"Process exited with rc %d\n", result);
|
||||
[MobileFFmpeg execute: @"-i file1.mp4 -c:v mpeg4 file2.mp4"];
|
||||
```
|
||||
|
||||
free(arguments);
|
||||
3. Check execution output.
|
||||
```
|
||||
int rc = [MobileFFmpeg getLastReturnCode];
|
||||
NSString *output = [MobileFFmpeg getLastCommandOutput];
|
||||
|
||||
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 and output=%@.\n", rc, output);
|
||||
}
|
||||
```
|
||||
|
||||
4. Stop an ongoing operation.
|
||||
```
|
||||
[MobileFFmpeg cancel];
|
||||
```
|
||||
|
||||
5. Get media information for a file.
|
||||
```
|
||||
MediaInformation *mediaInformation = [MobileFFmpeg getMediaInformation:@"<file path or uri>"];
|
||||
```
|
||||
|
||||
6. List enabled external libraries.
|
||||
```
|
||||
NASArray *externalLibraries = [MobileFFmpegConfig getExternalLibraries];
|
||||
```
|
||||
|
||||
7. Enable log callback.
|
||||
```
|
||||
- (void)logCallback: (int)level :(NSString*)message {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSLog(@"%@", message);
|
||||
});
|
||||
}
|
||||
...
|
||||
[MobileFFmpegConfig setLogDelegate:self];
|
||||
```
|
||||
|
||||
8. 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];
|
||||
```
|
||||
|
||||
9. Set log level.
|
||||
```
|
||||
[MobileFFmpegConfig setLogLevel:AV_LOG_FATAL];
|
||||
```
|
||||
|
||||
10. 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, which requires
|
||||
`Xcode 9.x` or later, 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
|
||||
|
||||
@@ -95,12 +252,14 @@ There is an Android test application under the `android/test-app` folder and an
|
||||
|
||||
- `MobileFFmpeg v2.x` is the current stable, includes `FFmpeg v4.0.2`
|
||||
|
||||
- `MobileFFmpeg v3.x` is the next stable, includes `FFmpeg v4.1-dev-1517`
|
||||
|
||||
### 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
|
||||
@@ -108,14 +267,13 @@ Please visit [Android Prerequisites](https://github.com/tanersener/mobile-ffmpeg
|
||||
|
||||
2. Android builds require these additional packages.
|
||||
- **Android SDK 5.0 Lollipop (API Level 21)** or later
|
||||
- **Android NDK r16b** or later with LLDB and CMake
|
||||
- **Android NDK r17c** or later with LLDB and CMake
|
||||
- **gradle 4.4** or later
|
||||
|
||||
3. IOS builds need these extra packages and tools.
|
||||
- **IOS SDK 7.0.x** or later
|
||||
- **Xcode 8.x** or later
|
||||
- **IOS SDK 8.0.x** or later
|
||||
- **Xcode 7.3.1** or later
|
||||
- **Command Line Tools**
|
||||
- **lipo** utility
|
||||
|
||||
#### 4.2 Build Scripts
|
||||
Use `android.sh` and `ios.sh` to build MobileFFmpeg for each platform.
|
||||
@@ -129,17 +287,25 @@ 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
|
||||
`build` directory includes build scripts for external libraries. There are two scripts for each library, one for Android and one for IOS. They include all options/flags used to cross-compile the libraries. `ASM` is enabled by most of them, exceptions are listed under the [ASM Support](https://github.com/tanersener/mobile-ffmpeg/wiki/ASM-Support) page.
|
||||
`build` directory includes build scripts for external libraries. There are two scripts for each library, one for Android
|
||||
and one for IOS. They include all options/flags used to cross-compile the libraries. `ASM` is enabled by most of them,
|
||||
exceptions are listed under the [ASM Support](https://github.com/tanersener/mobile-ffmpeg/wiki/ASM-Support) page.
|
||||
|
||||
### 5. Documentation
|
||||
|
||||
@@ -152,9 +318,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.
|
||||
|
||||
@@ -165,5 +331,7 @@ 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,25 +28,35 @@ LIBRARY_WAVPACK=16
|
||||
LIBRARY_KVAZAAR=17
|
||||
LIBRARY_X264=18
|
||||
LIBRARY_XVIDCORE=19
|
||||
LIBRARY_LIBILBC=20
|
||||
LIBRARY_OPUS=21
|
||||
LIBRARY_SNAPPY=22
|
||||
LIBRARY_GIFLIB=23
|
||||
LIBRARY_JPEG=24
|
||||
LIBRARY_LIBOGG=25
|
||||
LIBRARY_LIBPNG=26
|
||||
LIBRARY_LIBUUID=27
|
||||
LIBRARY_NETTLE=28
|
||||
LIBRARY_TIFF=29
|
||||
LIBRARY_EXPAT=30
|
||||
LIBRARY_ZLIB=31
|
||||
LIBRARY_MEDIA_CODEC=32
|
||||
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_TWOLAME=28
|
||||
LIBRARY_SDL=29
|
||||
LIBRARY_TESSERACT=30
|
||||
LIBRARY_GIFLIB=31
|
||||
LIBRARY_JPEG=32
|
||||
LIBRARY_LIBOGG=33
|
||||
LIBRARY_LIBPNG=34
|
||||
LIBRARY_LIBUUID=35
|
||||
LIBRARY_NETTLE=36
|
||||
LIBRARY_TIFF=37
|
||||
LIBRARY_EXPAT=38
|
||||
LIBRARY_SNDFILE=39
|
||||
LIBRARY_LEPTONICA=40
|
||||
LIBRARY_ZLIB=41
|
||||
LIBRARY_MEDIA_CODEC=42
|
||||
|
||||
# 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)
|
||||
ENABLED_LIBRARIES=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
|
||||
|
||||
export BASEDIR=$(pwd)
|
||||
|
||||
@@ -76,7 +86,9 @@ 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"
|
||||
echo -e " -S, --speed\t\t\toptimize for speed instead of size\n"
|
||||
|
||||
echo -e "Licensing options:"
|
||||
|
||||
@@ -93,8 +105,9 @@ When compilation ends an Android Archive (AAR) file is created with enabled plat
|
||||
echo -e "Libraries:"
|
||||
|
||||
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-android-media-codec\tbuild with built-in Android MediaCodec support[no]"
|
||||
echo -e " --enable-android-zlib\t\tbuild with built-in zlib support[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]"
|
||||
@@ -102,6 +115,7 @@ When compilation ends an Android Archive (AAR) file is created with enabled plat
|
||||
echo -e " --enable-gnutls\t\tbuild with gnutls [no]"
|
||||
echo -e " --enable-kvazaar\t\tbuild with kvazaar [no]"
|
||||
echo -e " --enable-lame\t\t\tbuild with lame [no]"
|
||||
echo -e " --enable-libaom\t\tbuild with libaom [no]"
|
||||
echo -e " --enable-libass\t\tbuild with libass [no]"
|
||||
echo -e " --enable-libiconv\t\tbuild with libiconv [no]"
|
||||
echo -e " --enable-libilbc\t\tbuild with libilbc [no]"
|
||||
@@ -112,14 +126,20 @@ When compilation ends an Android Archive (AAR) file is created with enabled plat
|
||||
echo -e " --enable-libxml2\t\tbuild with libxml2 [no]"
|
||||
echo -e " --enable-opencore-amr\t\tbuild with opencore-amr [no]"
|
||||
echo -e " --enable-opus\t\t\tbuild with opus [no]"
|
||||
echo -e " --enable-sdl\t\t\tbuild with sdl [no]"
|
||||
echo -e " --enable-shine\t\tbuild with shine [no]"
|
||||
echo -e " --enable-snappy\t\tbuild with snappy [no]"
|
||||
echo -e " --enable-soxr\t\t\tbuild with soxr [no]"
|
||||
echo -e " --enable-speex\t\tbuild with speex [no]"
|
||||
echo -e " --enable-tesseract\t\tbuild with tesseract [no]"
|
||||
echo -e " --enable-twolame\t\tbuild with twolame [no]"
|
||||
echo -e " --enable-wavpack\t\tbuild with wavpack [no]\n"
|
||||
|
||||
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:"
|
||||
@@ -141,6 +161,20 @@ 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"
|
||||
}
|
||||
|
||||
optimize_for_speed() {
|
||||
export MOBILE_FFMPEG_OPTIMIZED_FOR_SPEED="1"
|
||||
}
|
||||
|
||||
reconf_library() {
|
||||
RECONF_VARIABLE=$(echo "RECONF_$1" | sed "s/\-/\_/g")
|
||||
|
||||
@@ -165,6 +199,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
|
||||
@@ -185,8 +222,8 @@ set_library() {
|
||||
;;
|
||||
gnutls)
|
||||
ENABLED_LIBRARIES[LIBRARY_GNUTLS]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_NETTLE]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_ZLIB]=$2
|
||||
set_library "nettle" $2
|
||||
set_library "gmp" $2
|
||||
set_library "libiconv" $2
|
||||
;;
|
||||
@@ -197,6 +234,9 @@ set_library() {
|
||||
ENABLED_LIBRARIES[LIBRARY_LAME]=$2
|
||||
set_library "libiconv" $2
|
||||
;;
|
||||
libaom)
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBAOM]=$2
|
||||
;;
|
||||
libass)
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBASS]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBUUID]=$2
|
||||
@@ -221,6 +261,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
|
||||
@@ -232,7 +275,7 @@ set_library() {
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBWEBP]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_GIFLIB]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_JPEG]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_TIFF]=$2
|
||||
set_library "tiff" $2
|
||||
set_library "libpng" $2
|
||||
;;
|
||||
libxml2)
|
||||
@@ -245,6 +288,9 @@ set_library() {
|
||||
opus)
|
||||
ENABLED_LIBRARIES[LIBRARY_OPUS]=$2
|
||||
;;
|
||||
sdl)
|
||||
ENABLED_LIBRARIES[LIBRARY_SDL]=$2
|
||||
;;
|
||||
shine)
|
||||
ENABLED_LIBRARIES[LIBRARY_SHINE]=$2
|
||||
;;
|
||||
@@ -252,21 +298,49 @@ set_library() {
|
||||
ENABLED_LIBRARIES[LIBRARY_SNAPPY]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_ZLIB]=$2
|
||||
;;
|
||||
soxr)
|
||||
ENABLED_LIBRARIES[LIBRARY_SOXR]=$2
|
||||
;;
|
||||
speex)
|
||||
ENABLED_LIBRARIES[LIBRARY_SPEEX]=$2
|
||||
;;
|
||||
tesseract)
|
||||
ENABLED_LIBRARIES[LIBRARY_TESSERACT]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_LEPTONICA]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_LIBWEBP]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_GIFLIB]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_JPEG]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_ZLIB]=$2
|
||||
set_library "tiff" $2
|
||||
set_library "libpng" $2
|
||||
;;
|
||||
twolame)
|
||||
ENABLED_LIBRARIES[LIBRARY_TWOLAME]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_SNDFILE]=$2
|
||||
;;
|
||||
wavpack)
|
||||
ENABLED_LIBRARIES[LIBRARY_WAVPACK]=$2
|
||||
;;
|
||||
x264)
|
||||
ENABLED_LIBRARIES[LIBRARY_X264]=$2
|
||||
;;
|
||||
x265)
|
||||
ENABLED_LIBRARIES[LIBRARY_X265]=$2
|
||||
;;
|
||||
xvidcore)
|
||||
ENABLED_LIBRARIES[LIBRARY_XVIDCORE]=$2
|
||||
;;
|
||||
giflib | jpeg | libogg | libpng | libuuid | nettle | tiff | expat)
|
||||
expat | giflib | jpeg | leptonica | libogg | libpng | libsndfile | libuuid)
|
||||
# THESE LIBRARIES ARE NOT ENABLED DIRECTLY
|
||||
;;
|
||||
nettle)
|
||||
ENABLED_LIBRARIES[LIBRARY_NETTLE]=$2
|
||||
set_library "gmp" $2
|
||||
;;
|
||||
tiff)
|
||||
ENABLED_LIBRARIES[LIBRARY_TIFF]=$2
|
||||
ENABLED_LIBRARIES[LIBRARY_JPEG]=$2
|
||||
;;
|
||||
*)
|
||||
print_unknown_library $1
|
||||
;;
|
||||
@@ -343,7 +417,7 @@ print_enabled_libraries() {
|
||||
let enabled=0;
|
||||
|
||||
# FIRST BUILT-IN LIBRARIES
|
||||
for library in {31..32}
|
||||
for library in {41..42}
|
||||
do
|
||||
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
|
||||
if [[ ${enabled} -ge 1 ]]; then
|
||||
@@ -355,7 +429,7 @@ print_enabled_libraries() {
|
||||
done
|
||||
|
||||
# THEN EXTERNAL LIBRARIES
|
||||
for library in {0..22}
|
||||
for library in {0..30}
|
||||
do
|
||||
if [[ ${ENABLED_LIBRARIES[$library]} -eq 1 ]]; then
|
||||
if [[ ${enabled} -ge 1 ]]; then
|
||||
@@ -407,6 +481,17 @@ 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
|
||||
;;
|
||||
-S | --speed)
|
||||
optimize_for_speed
|
||||
;;
|
||||
--reconf-*)
|
||||
CONF_LIBRARY=`echo $1 | sed -e 's/^--[A-Za-z]*-//g'`
|
||||
|
||||
@@ -418,9 +503,9 @@ do
|
||||
rebuild_library ${BUILD_LIBRARY}
|
||||
;;
|
||||
--full)
|
||||
for library in {0..32}
|
||||
for library in {0..42}
|
||||
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
|
||||
@@ -451,66 +536,80 @@ 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
|
||||
export ARCH=$(get_arch_name $run_arch)
|
||||
export TOOLCHAIN=$(get_toolchain)
|
||||
export TOOLCHAIN_ARCH=$(get_toolchain_arch)
|
||||
export ANDROID_ARCH=$(get_android_arch $run_arch)
|
||||
|
||||
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..33}
|
||||
for library in {1..43}
|
||||
do
|
||||
library_name=$(get_library_name $((library - 1)))
|
||||
unset $(echo "OK_${library_name}" | sed "s/\-/\_/g")
|
||||
unset $(echo "DEPENDENCY_REBUILT_${library_name}" | sed "s/\-/\_/g")
|
||||
done
|
||||
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
|
||||
@@ -535,13 +634,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"
|
||||
@@ -552,16 +651,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 = 1.2
|
||||
PROJECT_NUMBER = 3.0
|
||||
|
||||
# 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 12
|
||||
versionName "1.2"
|
||||
versionCode 30
|
||||
versionName "3.0"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
project.archivesBaseName = "mobile-ffmpeg"
|
||||
}
|
||||
@@ -23,6 +23,10 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.returnDefaultValues = true
|
||||
}
|
||||
|
||||
libraryVariants.all { variant ->
|
||||
variant.outputs.all { output ->
|
||||
if (outputFile != null && outputFileName.endsWith('.aar')) {
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -1,91 +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 "cpu-features.h"
|
||||
#include "abidetect.h"
|
||||
|
||||
/** Full name of the Java class that owns native functions in this file. */
|
||||
const char *abiDetectClassName = "com/arthenica/mobileffmpeg/AbiDetect";
|
||||
|
||||
/** Prototypes of native functions defined by this file. */
|
||||
JNINativeMethod abiDetectMethods[] = {
|
||||
{"getAbi", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_AbiDetect_getAbi}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when 'abidetect' native library is loaded.
|
||||
*
|
||||
* \param vm pointer to the running virtual machine
|
||||
* \param reserved reserved
|
||||
* \return JNI version needed by 'abidetect' 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.", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
jclass abiDetectClass = (*env)->FindClass(env, abiDetectClassName);
|
||||
if (abiDetectClass == NULL) {
|
||||
LOGE("OnLoad failed to FindClass %s.", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if ((*env)->RegisterNatives(env, abiDetectClass, abiDetectMethods, 1) < 0) {
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns running ABI name.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the class on which this method is invoked
|
||||
* \return running ABI name as UTF string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_getAbi(JNIEnv *env, jclass object) {
|
||||
AndroidCpuFamily family = android_getCpuFamily();
|
||||
|
||||
if (family == ANDROID_CPU_FAMILY_ARM) {
|
||||
uint64_t features = android_getCpuFeatures();
|
||||
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) {
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_NEON) {
|
||||
return (*env)->NewStringUTF(env, ABI_ARMV7A_NEON);
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_ARMV7A);
|
||||
}
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_ARM);
|
||||
}
|
||||
|
||||
} else if (family == ANDROID_CPU_FAMILY_ARM64) {
|
||||
return (*env)->NewStringUTF(env, ABI_ARM64_V8A);
|
||||
} else if (family == ANDROID_CPU_FAMILY_X86) {
|
||||
return (*env)->NewStringUTF(env, ABI_X86);
|
||||
} else if (family == ANDROID_CPU_FAMILY_X86_64) {
|
||||
return (*env)->NewStringUTF(env, ABI_X86_64);
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_UNKNOWN);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +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_ABIDETECT_H
|
||||
#define MOBILEFFMPEG_ABIDETECT_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "log.h"
|
||||
|
||||
/** Represents armeabi-v7a ABI with NEON support. */
|
||||
#define ABI_ARMV7A_NEON "armeabi-v7a-neon"
|
||||
|
||||
/** Represents armeabi-v7a ABI. */
|
||||
#define ABI_ARMV7A "armeabi-v7a"
|
||||
|
||||
/** Represents armeabi ABI. */
|
||||
#define ABI_ARM "armeabi"
|
||||
|
||||
/** Represents x86 ABI. */
|
||||
#define ABI_X86 "x86"
|
||||
|
||||
/** Represents x86_64 ABI. */
|
||||
#define ABI_X86_64 "x86_64"
|
||||
|
||||
/** Represents arm64-v8a ABI. */
|
||||
#define ABI_ARM64_V8A "arm64-v8a"
|
||||
|
||||
/** Represents not supported ABIs. */
|
||||
#define ABI_UNKNOWN "unknown"
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_AbiDetect
|
||||
* Method: getAbi
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_getAbi(JNIEnv *, jclass);
|
||||
|
||||
#endif /* MOBILEFFMPEG_ABIDETECT_H */
|
||||
@@ -1,670 +0,0 @@
|
||||
/*
|
||||
* Various utilities for command line tools
|
||||
* copyright (c) 2003 Fabrice Bellard
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* CHANGES 03.2018 Taner Sener
|
||||
* --------------------------------------------------------
|
||||
* - Include guards renamed
|
||||
* - log.h included
|
||||
* - Unused headers removed
|
||||
*/
|
||||
|
||||
#ifndef MOBILEFFMPEG_CMDUTILS_H
|
||||
#define MOBILEFFMPEG_CMDUTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#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() */
|
||||
#endif
|
||||
|
||||
/**
|
||||
* program name, defined by the program for show_version().
|
||||
*/
|
||||
extern const char program_name[];
|
||||
|
||||
/**
|
||||
* program birth year, defined by the program for show_banner()
|
||||
*/
|
||||
extern const int program_birth_year;
|
||||
|
||||
extern AVCodecContext *avcodec_opts[AVMEDIA_TYPE_NB];
|
||||
extern AVFormatContext *avformat_opts;
|
||||
extern AVDictionary *sws_dict;
|
||||
extern AVDictionary *swr_opts;
|
||||
extern AVDictionary *format_opts, *codec_opts, *resample_opts;
|
||||
extern int hide_banner;
|
||||
|
||||
/**
|
||||
* Register a program-specific cleanup routine.
|
||||
*/
|
||||
void register_exit(void (*cb)(int ret));
|
||||
|
||||
/**
|
||||
* Wraps exit with a program-specific cleanup routine.
|
||||
*/
|
||||
void exit_program(int ret) av_noreturn;
|
||||
|
||||
/**
|
||||
* Initialize dynamic library loading
|
||||
*/
|
||||
void init_dynload(void);
|
||||
|
||||
/**
|
||||
* Initialize the cmdutils option system, in particular
|
||||
* allocate the *_opts contexts.
|
||||
*/
|
||||
void init_opts(void);
|
||||
/**
|
||||
* Uninitialize the cmdutils option system, in particular
|
||||
* free the *_opts contexts and their contents.
|
||||
*/
|
||||
void uninit_opts(void);
|
||||
|
||||
/**
|
||||
* Trivial log callback.
|
||||
* Only suitable for opt_help and similar since it lacks prefix handling.
|
||||
*/
|
||||
void log_callback_help(void* ptr, int level, const char* fmt, va_list vl);
|
||||
|
||||
/**
|
||||
* Override the cpuflags.
|
||||
*/
|
||||
int opt_cpuflags(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Fallback for options that are not explicitly handled, these will be
|
||||
* parsed through AVOptions.
|
||||
*/
|
||||
int opt_default(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Set the libav* libraries log level.
|
||||
*/
|
||||
int opt_loglevel(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
int opt_report(const char *opt);
|
||||
|
||||
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.
|
||||
*/
|
||||
int opt_timelimit(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Parse a string and return its corresponding value as a double.
|
||||
* Exit from the application if the string cannot be correctly
|
||||
* parsed or the corresponding value is invalid.
|
||||
*
|
||||
* @param context the context of the value to be set (e.g. the
|
||||
* corresponding command line option name)
|
||||
* @param numstr the string to be parsed
|
||||
* @param type the type (OPT_INT64 or OPT_FLOAT) as which the
|
||||
* string should be parsed
|
||||
* @param min the minimum valid accepted value
|
||||
* @param max the maximum valid accepted value
|
||||
*/
|
||||
double parse_number_or_die(const char *context, const char *numstr, int type,
|
||||
double min, double max);
|
||||
|
||||
/**
|
||||
* Parse a string specifying a time and return its corresponding
|
||||
* value as a number of microseconds. Exit from the application if
|
||||
* the string cannot be correctly parsed.
|
||||
*
|
||||
* @param context the context of the value to be set (e.g. the
|
||||
* corresponding command line option name)
|
||||
* @param timestr the string to be parsed
|
||||
* @param is_duration a flag which tells how to interpret timestr, if
|
||||
* not zero timestr is interpreted as a duration, otherwise as a
|
||||
* date
|
||||
*
|
||||
* @see av_parse_time()
|
||||
*/
|
||||
int64_t parse_time_or_die(const char *context, const char *timestr,
|
||||
int is_duration);
|
||||
|
||||
typedef struct SpecifierOpt {
|
||||
char *specifier; /**< stream/chapter/program/... specifier */
|
||||
union {
|
||||
uint8_t *str;
|
||||
int i;
|
||||
int64_t i64;
|
||||
float f;
|
||||
double dbl;
|
||||
} u;
|
||||
} SpecifierOpt;
|
||||
|
||||
typedef struct OptionDef {
|
||||
const char *name;
|
||||
int flags;
|
||||
#define HAS_ARG 0x0001
|
||||
#define OPT_BOOL 0x0002
|
||||
#define OPT_EXPERT 0x0004
|
||||
#define OPT_STRING 0x0008
|
||||
#define OPT_VIDEO 0x0010
|
||||
#define OPT_AUDIO 0x0020
|
||||
#define OPT_INT 0x0080
|
||||
#define OPT_FLOAT 0x0100
|
||||
#define OPT_SUBTITLE 0x0200
|
||||
#define OPT_INT64 0x0400
|
||||
#define OPT_EXIT 0x0800
|
||||
#define OPT_DATA 0x1000
|
||||
#define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only).
|
||||
implied by OPT_OFFSET or OPT_SPEC */
|
||||
#define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */
|
||||
#define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt.
|
||||
Implies OPT_OFFSET. Next element after the offset is
|
||||
an int containing element count in the array. */
|
||||
#define OPT_TIME 0x10000
|
||||
#define OPT_DOUBLE 0x20000
|
||||
#define OPT_INPUT 0x40000
|
||||
#define OPT_OUTPUT 0x80000
|
||||
union {
|
||||
void *dst_ptr;
|
||||
int (*func_arg)(void *, const char *, const char *);
|
||||
size_t off;
|
||||
} u;
|
||||
const char *help;
|
||||
const char *argname;
|
||||
} OptionDef;
|
||||
|
||||
/**
|
||||
* Print help for all options matching specified flags.
|
||||
*
|
||||
* @param options a list of options
|
||||
* @param msg title of this group. Only printed if at least one option matches.
|
||||
* @param req_flags print only options which have all those flags set.
|
||||
* @param rej_flags don't print options which have any of those flags set.
|
||||
* @param alt_flags print only options that have at least one of those flags set
|
||||
*/
|
||||
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 }, \
|
||||
"list sources of the input device", "device" }, \
|
||||
{ "sinks" , OPT_EXIT | HAS_ARG, { .func_arg = show_sinks }, \
|
||||
"list sinks of the output device", "device" }, \
|
||||
|
||||
#else
|
||||
#define CMDUTILS_COMMON_OPTIONS_AVDEVICE
|
||||
#endif
|
||||
|
||||
#define CMDUTILS_COMMON_OPTIONS \
|
||||
{ "L", OPT_EXIT, { .func_arg = show_license }, "show license" }, \
|
||||
{ "h", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "?", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "-help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "version", OPT_EXIT, { .func_arg = show_version }, "show version" }, \
|
||||
{ "buildconf", OPT_EXIT, { .func_arg = show_buildconf }, "show build configuration" }, \
|
||||
{ "formats", OPT_EXIT, { .func_arg = show_formats }, "show available formats" }, \
|
||||
{ "muxers", OPT_EXIT, { .func_arg = show_muxers }, "show available muxers" }, \
|
||||
{ "demuxers", OPT_EXIT, { .func_arg = show_demuxers }, "show available demuxers" }, \
|
||||
{ "devices", OPT_EXIT, { .func_arg = show_devices }, "show available devices" }, \
|
||||
{ "codecs", OPT_EXIT, { .func_arg = show_codecs }, "show available codecs" }, \
|
||||
{ "decoders", OPT_EXIT, { .func_arg = show_decoders }, "show available decoders" }, \
|
||||
{ "encoders", OPT_EXIT, { .func_arg = show_encoders }, "show available encoders" }, \
|
||||
{ "bsfs", OPT_EXIT, { .func_arg = show_bsfs }, "show available bit stream filters" }, \
|
||||
{ "protocols", OPT_EXIT, { .func_arg = show_protocols }, "show available protocols" }, \
|
||||
{ "filters", OPT_EXIT, { .func_arg = show_filters }, "show available filters" }, \
|
||||
{ "pix_fmts", OPT_EXIT, { .func_arg = show_pix_fmts }, "show available pixel formats" }, \
|
||||
{ "layouts", OPT_EXIT, { .func_arg = show_layouts }, "show standard channel layouts" }, \
|
||||
{ "sample_fmts", OPT_EXIT, { .func_arg = show_sample_fmts }, "show available audio sample formats" }, \
|
||||
{ "colors", OPT_EXIT, { .func_arg = show_colors }, "show available color names" }, \
|
||||
{ "loglevel", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \
|
||||
{ "v", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \
|
||||
{ "report", 0, { (void*)opt_report }, "generate a report" }, \
|
||||
{ "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 \
|
||||
|
||||
/**
|
||||
* Show help for all options with given flags in class and all its
|
||||
* children.
|
||||
*/
|
||||
void show_help_children(const AVClass *class, int flags);
|
||||
|
||||
/**
|
||||
* Per-fftool specific help handler. Implemented in each
|
||||
* fftool, called by show_help().
|
||||
*/
|
||||
void show_help_default(const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Generic -h handler common to all fftools.
|
||||
*/
|
||||
int show_help(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Parse the command line arguments.
|
||||
*
|
||||
* @param optctx an opaque options context
|
||||
* @param argc number of command line arguments
|
||||
* @param argv values of command line arguments
|
||||
* @param options Array with the definitions required to interpret every
|
||||
* option of the form: -option_name [argument]
|
||||
* @param parse_arg_function Name of the function called to process every
|
||||
* argument without a leading option name flag. NULL if such arguments do
|
||||
* not have to be processed.
|
||||
*/
|
||||
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
|
||||
void (* parse_arg_function)(void *optctx, const char*));
|
||||
|
||||
/**
|
||||
* Parse one given option.
|
||||
*
|
||||
* @return on success 1 if arg was consumed, 0 otherwise; negative number on error
|
||||
*/
|
||||
int parse_option(void *optctx, const char *opt, const char *arg,
|
||||
const OptionDef *options);
|
||||
|
||||
/**
|
||||
* An option extracted from the commandline.
|
||||
* Cannot use AVDictionary because of options like -map which can be
|
||||
* used multiple times.
|
||||
*/
|
||||
typedef struct Option {
|
||||
const OptionDef *opt;
|
||||
const char *key;
|
||||
const char *val;
|
||||
} Option;
|
||||
|
||||
typedef struct OptionGroupDef {
|
||||
/**< group name */
|
||||
const char *name;
|
||||
/**
|
||||
* Option to be used as group separator. Can be NULL for groups which
|
||||
* are terminated by a non-option argument (e.g. ffmpeg output files)
|
||||
*/
|
||||
const char *sep;
|
||||
/**
|
||||
* Option flags that must be set on each option that is
|
||||
* applied to this group
|
||||
*/
|
||||
int flags;
|
||||
} OptionGroupDef;
|
||||
|
||||
typedef struct OptionGroup {
|
||||
const OptionGroupDef *group_def;
|
||||
const char *arg;
|
||||
|
||||
Option *opts;
|
||||
int nb_opts;
|
||||
|
||||
AVDictionary *codec_opts;
|
||||
AVDictionary *format_opts;
|
||||
AVDictionary *resample_opts;
|
||||
AVDictionary *sws_dict;
|
||||
AVDictionary *swr_opts;
|
||||
} OptionGroup;
|
||||
|
||||
/**
|
||||
* A list of option groups that all have the same group type
|
||||
* (e.g. input files or output files)
|
||||
*/
|
||||
typedef struct OptionGroupList {
|
||||
const OptionGroupDef *group_def;
|
||||
|
||||
OptionGroup *groups;
|
||||
int nb_groups;
|
||||
} OptionGroupList;
|
||||
|
||||
typedef struct OptionParseContext {
|
||||
OptionGroup global_opts;
|
||||
|
||||
OptionGroupList *groups;
|
||||
int nb_groups;
|
||||
|
||||
/* parsing state */
|
||||
OptionGroup cur_group;
|
||||
} OptionParseContext;
|
||||
|
||||
/**
|
||||
* Parse an options group and write results into optctx.
|
||||
*
|
||||
* @param optctx an app-specific options context. NULL for global options group
|
||||
*/
|
||||
int parse_optgroup(void *optctx, OptionGroup *g);
|
||||
|
||||
/**
|
||||
* Split the commandline into an intermediate form convenient for further
|
||||
* processing.
|
||||
*
|
||||
* The commandline is assumed to be composed of options which either belong to a
|
||||
* group (those with OPT_SPEC, OPT_OFFSET or OPT_PERFILE) or are global
|
||||
* (everything else).
|
||||
*
|
||||
* A group (defined by an OptionGroupDef struct) is a sequence of options
|
||||
* terminated by either a group separator option (e.g. -i) or a parameter that
|
||||
* is not an option (doesn't start with -). A group without a separator option
|
||||
* must always be first in the supplied groups list.
|
||||
*
|
||||
* All options within the same group are stored in one OptionGroup struct in an
|
||||
* OptionGroupList, all groups with the same group definition are stored in one
|
||||
* OptionGroupList in OptionParseContext.groups. The order of group lists is the
|
||||
* same as the order of group definitions.
|
||||
*/
|
||||
int split_commandline(OptionParseContext *octx, int argc, char *argv[],
|
||||
const OptionDef *options,
|
||||
const OptionGroupDef *groups, int nb_groups);
|
||||
|
||||
/**
|
||||
* Free all allocated memory in an OptionParseContext.
|
||||
*/
|
||||
void uninit_parse_context(OptionParseContext *octx);
|
||||
|
||||
/**
|
||||
* Find the '-loglevel' option in the command line args and apply it.
|
||||
*/
|
||||
void parse_loglevel(int argc, char **argv, const OptionDef *options);
|
||||
|
||||
/**
|
||||
* Return index of option opt in argv or 0 if not found.
|
||||
*/
|
||||
int locate_option(int argc, char **argv, const OptionDef *options,
|
||||
const char *optname);
|
||||
|
||||
/**
|
||||
* Check if the given stream matches a stream specifier.
|
||||
*
|
||||
* @param s Corresponding format context.
|
||||
* @param st Stream from s to be checked.
|
||||
* @param spec A stream specifier of the [v|a|s|d]:[\<stream index\>] form.
|
||||
*
|
||||
* @return 1 if the stream matches, 0 if it doesn't, <0 on error
|
||||
*/
|
||||
int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec);
|
||||
|
||||
/**
|
||||
* Filter out options for given codec.
|
||||
*
|
||||
* Create a new options dictionary containing only the options from
|
||||
* opts which apply to the codec with ID codec_id.
|
||||
*
|
||||
* @param opts dictionary to place options in
|
||||
* @param codec_id ID of the codec that should be filtered for
|
||||
* @param s Corresponding format context.
|
||||
* @param st A stream from s for which the options should be filtered.
|
||||
* @param codec The particular codec for which the options should be filtered.
|
||||
* If null, the default one is looked up according to the codec id.
|
||||
* @return a pointer to the created dictionary
|
||||
*/
|
||||
AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id,
|
||||
AVFormatContext *s, AVStream *st, AVCodec *codec);
|
||||
|
||||
/**
|
||||
* Setup AVCodecContext options for avformat_find_stream_info().
|
||||
*
|
||||
* Create an array of dictionaries, one dictionary for each stream
|
||||
* contained in s.
|
||||
* Each dictionary will contain the options from codec_opts which can
|
||||
* be applied to the corresponding stream codec context.
|
||||
*
|
||||
* @return pointer to the created array of dictionaries, NULL if it
|
||||
* cannot be created
|
||||
*/
|
||||
AVDictionary **setup_find_stream_info_opts(AVFormatContext *s,
|
||||
AVDictionary *codec_opts);
|
||||
|
||||
/**
|
||||
* Print an error message to stderr, indicating filename and a human
|
||||
* readable description of the error code err.
|
||||
*
|
||||
* If strerror_r() is not available the use of this function in a
|
||||
* multithreaded application may be unsafe.
|
||||
*
|
||||
* @see av_strerror()
|
||||
*/
|
||||
void print_error(const char *filename, int err);
|
||||
|
||||
/**
|
||||
* Print the program banner to stderr. The banner contents depend on the
|
||||
* current version of the repository and of the libav* libraries used by
|
||||
* the program.
|
||||
*/
|
||||
void show_banner(int argc, char **argv, const OptionDef *options);
|
||||
|
||||
/**
|
||||
* Print the version of the program to stdout. The version message
|
||||
* depends on the current versions of the repository and of the libav*
|
||||
* libraries.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_version(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print the build configuration of the program to stdout. The contents
|
||||
* depend on the definition of FFMPEG_CONFIGURATION.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_buildconf(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print the license of the program to stdout. The license depends on
|
||||
* the license of the libraries compiled into the program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_license(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the formats supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_formats(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the muxers supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_muxers(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the demuxer supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_demuxers(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the devices supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_devices(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
#if CONFIG_AVDEVICE
|
||||
/**
|
||||
* Print a listing containing autodetected sinks of the output device.
|
||||
* Device name with options may be passed as an argument to limit results.
|
||||
*/
|
||||
int show_sinks(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing autodetected sources of the input device.
|
||||
* Device name with options may be passed as an argument to limit results.
|
||||
*/
|
||||
int show_sources(void *optctx, const char *opt, const char *arg);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Print a listing containing all the codecs supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_codecs(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the decoders supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_decoders(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the encoders supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_encoders(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the filters supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_filters(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the bit stream filters supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_bsfs(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the protocols supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_protocols(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the pixel formats supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_pix_fmts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the standard channel layouts supported by
|
||||
* the program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_layouts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the sample formats supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_sample_fmts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the color names and values recognized
|
||||
* by the program.
|
||||
*/
|
||||
int show_colors(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Return a positive value if a line read from standard input
|
||||
* starts with [yY], otherwise return 0.
|
||||
*/
|
||||
int read_yesno(void);
|
||||
|
||||
/**
|
||||
* Get a file corresponding to a preset file.
|
||||
*
|
||||
* If is_path is non-zero, look for the file in the path preset_name.
|
||||
* Otherwise search for a file named arg.ffpreset in the directories
|
||||
* $FFMPEG_DATADIR (if set), $HOME/.ffmpeg, and in the datadir defined
|
||||
* at configuration time or in a "ffpresets" folder along the executable
|
||||
* on win32, in that order. If no such file is found and
|
||||
* codec_name is defined, then search for a file named
|
||||
* codec_name-preset_name.avpreset in the above-mentioned directories.
|
||||
*
|
||||
* @param filename buffer where the name of the found filename is written
|
||||
* @param filename_size size in bytes of the filename buffer
|
||||
* @param preset_name name of the preset to search
|
||||
* @param is_path tell if preset_name is a filename path
|
||||
* @param codec_name name of the codec for which to look for the
|
||||
* preset, may be NULL
|
||||
*/
|
||||
FILE *get_preset_file(char *filename, size_t filename_size,
|
||||
const char *preset_name, int is_path, const char *codec_name);
|
||||
|
||||
/**
|
||||
* Realloc array to hold new_size elements of elem_size.
|
||||
* Calls exit() on failure.
|
||||
*
|
||||
* @param array array to reallocate
|
||||
* @param elem_size size in bytes of each element
|
||||
* @param size new element count will be written here
|
||||
* @param new_size number of elements to place in reallocated array
|
||||
* @return reallocated array
|
||||
*/
|
||||
void *grow_array(void *array, int elem_size, int *size, int new_size);
|
||||
|
||||
#define media_type_string av_get_media_type_string
|
||||
|
||||
#define GROW_ARRAY(array, nb_elems)\
|
||||
array = grow_array(array, sizeof(*array), &nb_elems, nb_elems + 1)
|
||||
|
||||
#define GET_PIX_FMT_NAME(pix_fmt)\
|
||||
const char *name = av_get_pix_fmt_name(pix_fmt);
|
||||
|
||||
#define GET_SAMPLE_FMT_NAME(sample_fmt)\
|
||||
const char *name = av_get_sample_fmt_name(sample_fmt)
|
||||
|
||||
#define GET_SAMPLE_RATE_NAME(rate)\
|
||||
char name[16];\
|
||||
snprintf(name, sizeof(name), "%d", rate);
|
||||
|
||||
#define GET_CH_LAYOUT_NAME(ch_layout)\
|
||||
char name[16];\
|
||||
snprintf(name, sizeof(name), "0x%"PRIx64, ch_layout);
|
||||
|
||||
#define GET_CH_LAYOUT_DESC(ch_layout)\
|
||||
char name[128];\
|
||||
av_get_channel_layout_string(name, sizeof(name), 0, ch_layout);
|
||||
|
||||
double get_rotation(AVStream *st);
|
||||
|
||||
#endif /* MOBILEFFMPEG_CMDUTILS_H */
|
||||
@@ -1,29 +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_EXCEPTION_H
|
||||
#define MOBILEFFMPEG_EXCEPTION_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
/** Holds information to implement exception handling. */
|
||||
jmp_buf ex_buf__;
|
||||
|
||||
#endif // MOBILEFFMPEG_EXCEPTION_H
|
||||
@@ -1,719 +0,0 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* CHANGES 03.2018 Taner Sener
|
||||
* --------------------------------------------------------
|
||||
* - 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
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#if HAVE_PTHREADS
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include "cmdutils.h"
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavformat/avio.h"
|
||||
|
||||
#include "libavcodec/avcodec.h"
|
||||
|
||||
#include "libavfilter/avfilter.h"
|
||||
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/hwcontext.h"
|
||||
#include "libavutil/pixfmt.h"
|
||||
#include "libavutil/rational.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
|
||||
#define VSYNC_VFR 2
|
||||
#define VSYNC_VSCFR 0xfe
|
||||
#define VSYNC_DROP 0xff
|
||||
|
||||
#define MAX_STREAMS 1024 /* arbitrary sanity check value */
|
||||
|
||||
enum HWAccelID {
|
||||
HWACCEL_NONE = 0,
|
||||
HWACCEL_AUTO,
|
||||
HWACCEL_VDPAU,
|
||||
HWACCEL_DXVA2,
|
||||
HWACCEL_VDA,
|
||||
HWACCEL_VIDEOTOOLBOX,
|
||||
HWACCEL_QSV,
|
||||
HWACCEL_VAAPI,
|
||||
HWACCEL_CUVID,
|
||||
HWACCEL_D3D11VA,
|
||||
};
|
||||
|
||||
typedef struct HWAccel {
|
||||
const char *name;
|
||||
int (*init)(AVCodecContext *s);
|
||||
enum HWAccelID id;
|
||||
enum AVPixelFormat pix_fmt;
|
||||
enum AVHWDeviceType device_type;
|
||||
} HWAccel;
|
||||
|
||||
typedef struct HWDevice {
|
||||
char *name;
|
||||
enum AVHWDeviceType type;
|
||||
AVBufferRef *device_ref;
|
||||
} HWDevice;
|
||||
|
||||
/* select an input stream for an output stream */
|
||||
typedef struct StreamMap {
|
||||
int disabled; /* 1 is this mapping is disabled by a negative map */
|
||||
int file_index;
|
||||
int stream_index;
|
||||
int sync_file_index;
|
||||
int sync_stream_index;
|
||||
char *linklabel; /* name of an output link, for mapping lavfi outputs */
|
||||
} StreamMap;
|
||||
|
||||
typedef struct {
|
||||
int file_idx, stream_idx, channel_idx; // input
|
||||
int ofile_idx, ostream_idx; // output
|
||||
} AudioChannelMap;
|
||||
|
||||
typedef struct OptionsContext {
|
||||
OptionGroup *g;
|
||||
|
||||
/* input/output options */
|
||||
int64_t start_time;
|
||||
int64_t start_time_eof;
|
||||
int seek_timestamp;
|
||||
const char *format;
|
||||
|
||||
SpecifierOpt *codec_names;
|
||||
int nb_codec_names;
|
||||
SpecifierOpt *audio_channels;
|
||||
int nb_audio_channels;
|
||||
SpecifierOpt *audio_sample_rate;
|
||||
int nb_audio_sample_rate;
|
||||
SpecifierOpt *frame_rates;
|
||||
int nb_frame_rates;
|
||||
SpecifierOpt *frame_sizes;
|
||||
int nb_frame_sizes;
|
||||
SpecifierOpt *frame_pix_fmts;
|
||||
int nb_frame_pix_fmts;
|
||||
|
||||
/* input options */
|
||||
int64_t input_ts_offset;
|
||||
int loop;
|
||||
int rate_emu;
|
||||
int accurate_seek;
|
||||
int thread_queue_size;
|
||||
|
||||
SpecifierOpt *ts_scale;
|
||||
int nb_ts_scale;
|
||||
SpecifierOpt *dump_attachment;
|
||||
int nb_dump_attachment;
|
||||
SpecifierOpt *hwaccels;
|
||||
int nb_hwaccels;
|
||||
SpecifierOpt *hwaccel_devices;
|
||||
int nb_hwaccel_devices;
|
||||
SpecifierOpt *hwaccel_output_formats;
|
||||
int nb_hwaccel_output_formats;
|
||||
SpecifierOpt *autorotate;
|
||||
int nb_autorotate;
|
||||
|
||||
/* output options */
|
||||
StreamMap *stream_maps;
|
||||
int nb_stream_maps;
|
||||
AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */
|
||||
int nb_audio_channel_maps; /* number of (valid) -map_channel settings */
|
||||
int metadata_global_manual;
|
||||
int metadata_streams_manual;
|
||||
int metadata_chapters_manual;
|
||||
const char **attachments;
|
||||
int nb_attachments;
|
||||
|
||||
int chapters_input_file;
|
||||
|
||||
int64_t recording_time;
|
||||
int64_t stop_time;
|
||||
uint64_t limit_filesize;
|
||||
float mux_preload;
|
||||
float mux_max_delay;
|
||||
int shortest;
|
||||
|
||||
int video_disable;
|
||||
int audio_disable;
|
||||
int subtitle_disable;
|
||||
int data_disable;
|
||||
|
||||
/* indexed by output file stream index */
|
||||
int *streamid_map;
|
||||
int nb_streamid_map;
|
||||
|
||||
SpecifierOpt *metadata;
|
||||
int nb_metadata;
|
||||
SpecifierOpt *max_frames;
|
||||
int nb_max_frames;
|
||||
SpecifierOpt *bitstream_filters;
|
||||
int nb_bitstream_filters;
|
||||
SpecifierOpt *codec_tags;
|
||||
int nb_codec_tags;
|
||||
SpecifierOpt *sample_fmts;
|
||||
int nb_sample_fmts;
|
||||
SpecifierOpt *qscale;
|
||||
int nb_qscale;
|
||||
SpecifierOpt *forced_key_frames;
|
||||
int nb_forced_key_frames;
|
||||
SpecifierOpt *force_fps;
|
||||
int nb_force_fps;
|
||||
SpecifierOpt *frame_aspect_ratios;
|
||||
int nb_frame_aspect_ratios;
|
||||
SpecifierOpt *rc_overrides;
|
||||
int nb_rc_overrides;
|
||||
SpecifierOpt *intra_matrices;
|
||||
int nb_intra_matrices;
|
||||
SpecifierOpt *inter_matrices;
|
||||
int nb_inter_matrices;
|
||||
SpecifierOpt *chroma_intra_matrices;
|
||||
int nb_chroma_intra_matrices;
|
||||
SpecifierOpt *top_field_first;
|
||||
int nb_top_field_first;
|
||||
SpecifierOpt *metadata_map;
|
||||
int nb_metadata_map;
|
||||
SpecifierOpt *presets;
|
||||
int nb_presets;
|
||||
SpecifierOpt *copy_initial_nonkeyframes;
|
||||
int nb_copy_initial_nonkeyframes;
|
||||
SpecifierOpt *copy_prior_start;
|
||||
int nb_copy_prior_start;
|
||||
SpecifierOpt *filters;
|
||||
int nb_filters;
|
||||
SpecifierOpt *filter_scripts;
|
||||
int nb_filter_scripts;
|
||||
SpecifierOpt *reinit_filters;
|
||||
int nb_reinit_filters;
|
||||
SpecifierOpt *fix_sub_duration;
|
||||
int nb_fix_sub_duration;
|
||||
SpecifierOpt *canvas_sizes;
|
||||
int nb_canvas_sizes;
|
||||
SpecifierOpt *pass;
|
||||
int nb_pass;
|
||||
SpecifierOpt *passlogfiles;
|
||||
int nb_passlogfiles;
|
||||
SpecifierOpt *max_muxing_queue_size;
|
||||
int nb_max_muxing_queue_size;
|
||||
SpecifierOpt *guess_layout_max;
|
||||
int nb_guess_layout_max;
|
||||
SpecifierOpt *apad;
|
||||
int nb_apad;
|
||||
SpecifierOpt *discard;
|
||||
int nb_discard;
|
||||
SpecifierOpt *disposition;
|
||||
int nb_disposition;
|
||||
SpecifierOpt *program;
|
||||
int nb_program;
|
||||
SpecifierOpt *time_bases;
|
||||
int nb_time_bases;
|
||||
SpecifierOpt *enc_time_bases;
|
||||
int nb_enc_time_bases;
|
||||
} OptionsContext;
|
||||
|
||||
typedef struct InputFilter {
|
||||
AVFilterContext *filter;
|
||||
struct InputStream *ist;
|
||||
struct FilterGraph *graph;
|
||||
uint8_t *name;
|
||||
enum AVMediaType type; // AVMEDIA_TYPE_SUBTITLE for sub2video
|
||||
|
||||
AVFifoBuffer *frame_queue;
|
||||
|
||||
// parameters configured for this input
|
||||
int format;
|
||||
|
||||
int width, height;
|
||||
AVRational sample_aspect_ratio;
|
||||
|
||||
int sample_rate;
|
||||
int channels;
|
||||
uint64_t channel_layout;
|
||||
|
||||
AVBufferRef *hw_frames_ctx;
|
||||
|
||||
int eof;
|
||||
} InputFilter;
|
||||
|
||||
typedef struct OutputFilter {
|
||||
AVFilterContext *filter;
|
||||
struct OutputStream *ost;
|
||||
struct FilterGraph *graph;
|
||||
uint8_t *name;
|
||||
|
||||
/* temporary storage until stream maps are processed */
|
||||
AVFilterInOut *out_tmp;
|
||||
enum AVMediaType type;
|
||||
|
||||
/* desired output stream properties */
|
||||
int width, height;
|
||||
AVRational frame_rate;
|
||||
int format;
|
||||
int sample_rate;
|
||||
uint64_t channel_layout;
|
||||
|
||||
// those are only set if no format is specified and the encoder gives us multiple options
|
||||
int *formats;
|
||||
uint64_t *channel_layouts;
|
||||
int *sample_rates;
|
||||
} OutputFilter;
|
||||
|
||||
typedef struct FilterGraph {
|
||||
int index;
|
||||
const char *graph_desc;
|
||||
|
||||
AVFilterGraph *graph;
|
||||
int reconfiguration;
|
||||
|
||||
InputFilter **inputs;
|
||||
int nb_inputs;
|
||||
OutputFilter **outputs;
|
||||
int nb_outputs;
|
||||
} FilterGraph;
|
||||
|
||||
typedef struct InputStream {
|
||||
int file_index;
|
||||
AVStream *st;
|
||||
int discard; /* true if stream data should be discarded */
|
||||
int user_set_discard;
|
||||
int decoding_needed; /* non zero if the packets must be decoded in 'raw_fifo', see DECODING_FOR_* */
|
||||
#define DECODING_FOR_OST 1
|
||||
#define DECODING_FOR_FILTER 2
|
||||
|
||||
AVCodecContext *dec_ctx;
|
||||
AVCodec *dec;
|
||||
AVFrame *decoded_frame;
|
||||
AVFrame *filter_frame; /* a ref of decoded_frame, to be sent to filters */
|
||||
|
||||
int64_t start; /* time when read started */
|
||||
/* predicted dts of the next packet read for this stream or (when there are
|
||||
* several frames in a packet) of the next frame in current packet (in AV_TIME_BASE units) */
|
||||
int64_t next_dts;
|
||||
int64_t dts; ///< dts of the last packet read for this stream (in AV_TIME_BASE units)
|
||||
|
||||
int64_t next_pts; ///< synthetic pts for the next decode frame (in AV_TIME_BASE units)
|
||||
int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units)
|
||||
int wrap_correction_done;
|
||||
|
||||
int64_t filter_in_rescale_delta_last;
|
||||
|
||||
int64_t min_pts; /* pts with the smallest value in a current stream */
|
||||
int64_t max_pts; /* pts with the higher value in a current stream */
|
||||
|
||||
// when forcing constant input framerate through -r,
|
||||
// this contains the pts that will be given to the next decoded frame
|
||||
int64_t cfr_next_pts;
|
||||
|
||||
int64_t nb_samples; /* number of samples in the last decoded audio frame before looping */
|
||||
|
||||
double ts_scale;
|
||||
int saw_first_ts;
|
||||
AVDictionary *decoder_opts;
|
||||
AVRational framerate; /* framerate forced with -r */
|
||||
int top_field_first;
|
||||
int guess_layout_max;
|
||||
|
||||
int autorotate;
|
||||
|
||||
int fix_sub_duration;
|
||||
struct { /* previous decoded subtitle and related variables */
|
||||
int got_output;
|
||||
int ret;
|
||||
AVSubtitle subtitle;
|
||||
} prev_sub;
|
||||
|
||||
struct sub2video {
|
||||
int64_t last_pts;
|
||||
int64_t end_pts;
|
||||
AVFifoBuffer *sub_queue; ///< queue of AVSubtitle* before filter init
|
||||
AVFrame *frame;
|
||||
int w, h;
|
||||
} sub2video;
|
||||
|
||||
int dr1;
|
||||
|
||||
/* decoded data from this stream goes into all those filters
|
||||
* currently video and audio only */
|
||||
InputFilter **filters;
|
||||
int nb_filters;
|
||||
|
||||
int reinit_filters;
|
||||
|
||||
/* hwaccel options */
|
||||
enum HWAccelID hwaccel_id;
|
||||
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);
|
||||
int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame);
|
||||
enum AVPixelFormat hwaccel_pix_fmt;
|
||||
enum AVPixelFormat hwaccel_retrieved_pix_fmt;
|
||||
AVBufferRef *hw_frames_ctx;
|
||||
|
||||
/* stats */
|
||||
// combined size of all the packets read
|
||||
uint64_t data_size;
|
||||
/* number of packets successfully read for this stream */
|
||||
uint64_t nb_packets;
|
||||
// number of frames/samples retrieved from the decoder
|
||||
uint64_t frames_decoded;
|
||||
uint64_t samples_decoded;
|
||||
|
||||
int64_t *dts_buffer;
|
||||
int nb_dts_buffer;
|
||||
|
||||
int got_output;
|
||||
} InputStream;
|
||||
|
||||
typedef struct InputFile {
|
||||
AVFormatContext *ctx;
|
||||
int eof_reached; /* true if eof reached */
|
||||
int eagain; /* true if last read attempt returned EAGAIN */
|
||||
int ist_index; /* index of first stream in input_streams */
|
||||
int loop; /* set number of times input stream should be looped */
|
||||
int64_t duration; /* actual duration of the longest stream in a file
|
||||
at the moment when looping happens */
|
||||
AVRational time_base; /* time base of the duration */
|
||||
int64_t input_ts_offset;
|
||||
|
||||
int64_t ts_offset;
|
||||
int64_t last_ts;
|
||||
int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */
|
||||
int seek_timestamp;
|
||||
int64_t recording_time;
|
||||
int nb_streams; /* number of stream that ffmpeg is aware of; may be different
|
||||
from ctx.nb_streams if new streams appear during av_read_frame() */
|
||||
int nb_streams_warn; /* number of streams that the user was warned of */
|
||||
int rate_emu;
|
||||
int accurate_seek;
|
||||
|
||||
#if HAVE_PTHREADS
|
||||
AVThreadMessageQueue *in_thread_queue;
|
||||
pthread_t thread; /* thread reading from this file */
|
||||
int non_blocking; /* reading packets from the thread should not block */
|
||||
int joined; /* the thread has been joined */
|
||||
int thread_queue_size; /* maximum number of queued packets */
|
||||
#endif
|
||||
} InputFile;
|
||||
|
||||
enum forced_keyframes_const {
|
||||
FKF_N,
|
||||
FKF_N_FORCED,
|
||||
FKF_PREV_FORCED_N,
|
||||
FKF_PREV_FORCED_T,
|
||||
FKF_T,
|
||||
FKF_NB
|
||||
};
|
||||
|
||||
#define ABORT_ON_FLAG_EMPTY_OUTPUT (1 << 0)
|
||||
|
||||
extern const char *const forced_keyframes_const_names[];
|
||||
|
||||
typedef enum {
|
||||
ENCODER_FINISHED = 1,
|
||||
MUXER_FINISHED = 2,
|
||||
} OSTFinished ;
|
||||
|
||||
typedef struct OutputStream {
|
||||
int file_index; /* file index */
|
||||
int index; /* stream index in the output file */
|
||||
int source_index; /* InputStream index */
|
||||
AVStream *st; /* stream in the output file */
|
||||
int encoding_needed; /* true if encoding needed for this stream */
|
||||
int frame_number;
|
||||
/* input pts and corresponding output pts
|
||||
for A/V sync */
|
||||
struct InputStream *sync_ist; /* input stream to sync against */
|
||||
int64_t sync_opts; /* output frame counter, could be changed to some true timestamp */ // FIXME look at frame_number
|
||||
/* pts of the first frame encoded for this stream, used for limiting
|
||||
* recording time */
|
||||
int64_t first_pts;
|
||||
/* dts of the last packet sent to the muxer */
|
||||
int64_t last_mux_dts;
|
||||
// the timebase of the packets sent to the muxer
|
||||
AVRational mux_timebase;
|
||||
AVRational enc_timebase;
|
||||
|
||||
int nb_bitstream_filters;
|
||||
AVBSFContext **bsf_ctx;
|
||||
|
||||
AVCodecContext *enc_ctx;
|
||||
AVCodecParameters *ref_par; /* associated input codec parameters with encoders options applied */
|
||||
AVCodec *enc;
|
||||
int64_t max_frames;
|
||||
AVFrame *filtered_frame;
|
||||
AVFrame *last_frame;
|
||||
int last_dropped;
|
||||
int last_nb0_frames[3];
|
||||
|
||||
void *hwaccel_ctx;
|
||||
|
||||
/* video only */
|
||||
AVRational frame_rate;
|
||||
int is_cfr;
|
||||
int force_fps;
|
||||
int top_field_first;
|
||||
int rotate_overridden;
|
||||
double rotate_override_value;
|
||||
|
||||
AVRational frame_aspect_ratio;
|
||||
|
||||
/* forced key frames */
|
||||
int64_t *forced_kf_pts;
|
||||
int forced_kf_count;
|
||||
int forced_kf_index;
|
||||
char *forced_keyframes;
|
||||
AVExpr *forced_keyframes_pexpr;
|
||||
double forced_keyframes_expr_const_values[FKF_NB];
|
||||
|
||||
/* audio only */
|
||||
int *audio_channels_map; /* list of the channels id to pick from the source stream */
|
||||
int audio_channels_mapped; /* number of channels in audio_channels_map */
|
||||
|
||||
char *logfile_prefix;
|
||||
FILE *logfile;
|
||||
|
||||
OutputFilter *filter;
|
||||
char *avfilter;
|
||||
char *filters; ///< filtergraph associated to the -filter option
|
||||
char *filters_script; ///< filtergraph script associated to the -filter_script option
|
||||
|
||||
AVDictionary *encoder_opts;
|
||||
AVDictionary *sws_dict;
|
||||
AVDictionary *swr_opts;
|
||||
AVDictionary *resample_opts;
|
||||
char *apad;
|
||||
OSTFinished finished; /* no more packets should be written for this stream */
|
||||
int unavailable; /* true if the steram is unavailable (possibly temporarily) */
|
||||
int stream_copy;
|
||||
|
||||
// init_output_stream() has been called for this stream
|
||||
// The encoder and the bitstream filters have been initialized and the stream
|
||||
// parameters are set in the AVStream.
|
||||
int initialized;
|
||||
|
||||
int inputs_done;
|
||||
|
||||
const char *attachment_filename;
|
||||
int copy_initial_nonkeyframes;
|
||||
int copy_prior_start;
|
||||
char *disposition;
|
||||
|
||||
int keep_pix_fmt;
|
||||
|
||||
AVCodecParserContext *parser;
|
||||
AVCodecContext *parser_avctx;
|
||||
|
||||
/* stats */
|
||||
// combined size of all the packets written
|
||||
uint64_t data_size;
|
||||
// number of packets send to the muxer
|
||||
uint64_t packets_written;
|
||||
// number of frames/samples sent to the encoder
|
||||
uint64_t frames_encoded;
|
||||
uint64_t samples_encoded;
|
||||
|
||||
/* packet quality factor */
|
||||
int quality;
|
||||
|
||||
int max_muxing_queue_size;
|
||||
|
||||
/* the packets are buffered here until the muxer is ready to be initialized */
|
||||
AVFifoBuffer *muxing_queue;
|
||||
|
||||
/* packet picture type */
|
||||
int pict_type;
|
||||
|
||||
/* frame encode sum of squared error values */
|
||||
int64_t error[4];
|
||||
} OutputStream;
|
||||
|
||||
typedef struct OutputFile {
|
||||
AVFormatContext *ctx;
|
||||
AVDictionary *opts;
|
||||
int ost_index; /* index of the first stream in output_streams */
|
||||
int64_t recording_time; ///< desired length of the resulting file in microseconds == AV_TIME_BASE units
|
||||
int64_t start_time; ///< start time in microseconds == AV_TIME_BASE units
|
||||
uint64_t limit_filesize; /* filesize limit expressed in bytes */
|
||||
|
||||
int shortest;
|
||||
|
||||
int header_written;
|
||||
} OutputFile;
|
||||
|
||||
extern InputStream **input_streams;
|
||||
extern int nb_input_streams;
|
||||
extern InputFile **input_files;
|
||||
extern int nb_input_files;
|
||||
|
||||
extern OutputStream **output_streams;
|
||||
extern int nb_output_streams;
|
||||
extern OutputFile **output_files;
|
||||
extern int nb_output_files;
|
||||
|
||||
extern FilterGraph **filtergraphs;
|
||||
extern int nb_filtergraphs;
|
||||
|
||||
extern char *vstats_filename;
|
||||
extern char *sdp_filename;
|
||||
|
||||
extern float audio_drift_threshold;
|
||||
extern float dts_delta_threshold;
|
||||
extern float dts_error_threshold;
|
||||
|
||||
extern int audio_volume;
|
||||
extern int audio_sync_method;
|
||||
extern int video_sync_method;
|
||||
extern float frame_drop_threshold;
|
||||
extern int do_benchmark;
|
||||
extern int do_benchmark_all;
|
||||
extern int do_deinterlace;
|
||||
extern int do_hex_dump;
|
||||
extern int do_pkt_dump;
|
||||
extern int copy_ts;
|
||||
extern int start_at_zero;
|
||||
extern int copy_tb;
|
||||
extern int debug_ts;
|
||||
extern int exit_on_error;
|
||||
extern int abort_on_flags;
|
||||
extern int print_stats;
|
||||
extern int qp_hist;
|
||||
extern int stdin_interaction;
|
||||
extern int frame_bits_per_raw_sample;
|
||||
extern AVIOContext *progress_avio;
|
||||
extern float max_error_rate;
|
||||
extern char *videotoolbox_pixfmt;
|
||||
|
||||
extern int filter_nbthreads;
|
||||
extern int filter_complex_nbthreads;
|
||||
extern int vstats_version;
|
||||
|
||||
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;
|
||||
#endif
|
||||
extern HWDevice *filter_hw_device;
|
||||
|
||||
|
||||
void term_init(void);
|
||||
void term_exit(void);
|
||||
|
||||
void reset_options(OptionsContext *o, int is_input);
|
||||
void show_usage(void);
|
||||
|
||||
void opt_output_file(void *optctx, const char *filename);
|
||||
|
||||
void remove_avoptions(AVDictionary **a, AVDictionary *b);
|
||||
void assert_avoptions(AVDictionary *m);
|
||||
|
||||
int guess_input_channel_layout(InputStream *ist);
|
||||
|
||||
enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *avctx, AVCodec *codec, enum AVPixelFormat target);
|
||||
void choose_sample_fmt(AVStream *st, AVCodec *codec);
|
||||
|
||||
int configure_filtergraph(FilterGraph *fg);
|
||||
int configure_output_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out);
|
||||
void check_filter_outputs(void);
|
||||
int ist_in_filtergraph(FilterGraph *fg, InputStream *ist);
|
||||
int filtergraph_is_simple(FilterGraph *fg);
|
||||
int init_simple_filtergraph(InputStream *ist, OutputStream *ost);
|
||||
int init_complex_filtergraph(FilterGraph *fg);
|
||||
|
||||
void sub2video_update(InputStream *ist, AVSubtitle *sub);
|
||||
|
||||
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);
|
||||
|
||||
HWDevice *hw_device_get_by_name(const char *name);
|
||||
int hw_device_init_from_string(const char *arg, HWDevice **dev);
|
||||
void hw_device_free_all(void);
|
||||
|
||||
int hw_device_setup_for_decode(InputStream *ist);
|
||||
int hw_device_setup_for_encode(OutputStream *ost);
|
||||
|
||||
int hwaccel_decode_init(AVCodecContext *avctx);
|
||||
|
||||
#endif /* MOBILEFFMPEG_FFMPEG_H */
|
||||
@@ -1,385 +0,0 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
|
||||
#include "ffmpeg.h"
|
||||
|
||||
static int nb_hw_devices;
|
||||
static HWDevice **hw_devices;
|
||||
|
||||
static HWDevice *hw_device_get_by_type(enum AVHWDeviceType type)
|
||||
{
|
||||
HWDevice *found = NULL;
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
if (hw_devices[i]->type == type) {
|
||||
if (found)
|
||||
return NULL;
|
||||
found = hw_devices[i];
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
HWDevice *hw_device_get_by_name(const char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
if (!strcmp(hw_devices[i]->name, name))
|
||||
return hw_devices[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static HWDevice *hw_device_add(void)
|
||||
{
|
||||
int err;
|
||||
err = av_reallocp_array(&hw_devices, nb_hw_devices + 1,
|
||||
sizeof(*hw_devices));
|
||||
if (err) {
|
||||
nb_hw_devices = 0;
|
||||
return NULL;
|
||||
}
|
||||
hw_devices[nb_hw_devices] = av_mallocz(sizeof(HWDevice));
|
||||
if (!hw_devices[nb_hw_devices])
|
||||
return NULL;
|
||||
return hw_devices[nb_hw_devices++];
|
||||
}
|
||||
|
||||
int hw_device_init_from_string(const char *arg, HWDevice **dev_out)
|
||||
{
|
||||
// "type=name:device,key=value,key2=value2"
|
||||
// "type:device,key=value,key2=value2"
|
||||
// -> av_hwdevice_ctx_create()
|
||||
// "type=name@name"
|
||||
// "type@name"
|
||||
// -> av_hwdevice_ctx_create_derived()
|
||||
|
||||
AVDictionary *options = NULL;
|
||||
char *type_name = NULL, *name = NULL, *device = NULL;
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev, *src;
|
||||
AVBufferRef *device_ref = NULL;
|
||||
int err;
|
||||
const char *errmsg, *p, *q;
|
||||
size_t k;
|
||||
|
||||
k = strcspn(arg, ":=@");
|
||||
p = arg + k;
|
||||
|
||||
type_name = av_strndup(arg, k);
|
||||
if (!type_name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
type = av_hwdevice_find_type_by_name(type_name);
|
||||
if (type == AV_HWDEVICE_TYPE_NONE) {
|
||||
errmsg = "unknown device type";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
if (*p == '=') {
|
||||
k = strcspn(p + 1, ":@");
|
||||
|
||||
name = av_strndup(p + 1, k);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
if (hw_device_get_by_name(name)) {
|
||||
errmsg = "named device already exists";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
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);
|
||||
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) {
|
||||
// New device with no parameters.
|
||||
err = av_hwdevice_ctx_create(&device_ref, type,
|
||||
NULL, NULL, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (*p == ':') {
|
||||
// New device with some parameters.
|
||||
++p;
|
||||
q = strchr(p, ',');
|
||||
if (q) {
|
||||
device = av_strndup(p, q - p);
|
||||
if (!device) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
err = av_dict_parse_string(&options, q + 1, "=", ",", 0);
|
||||
if (err < 0) {
|
||||
errmsg = "failed to parse options";
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create(&device_ref, type,
|
||||
device ? device : p, options, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (*p == '@') {
|
||||
// Derive from existing device.
|
||||
|
||||
src = hw_device_get_by_name(p + 1);
|
||||
if (!src) {
|
||||
errmsg = "invalid source device name";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create_derived(&device_ref, type,
|
||||
src->device_ref, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
} else {
|
||||
errmsg = "parse error";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
name = NULL;
|
||||
err = 0;
|
||||
done:
|
||||
av_freep(&type_name);
|
||||
av_freep(&name);
|
||||
av_freep(&device);
|
||||
av_dict_free(&options);
|
||||
return err;
|
||||
invalid:
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Invalid device specification \"%s\": %s\n", arg, errmsg);
|
||||
err = AVERROR(EINVAL);
|
||||
goto done;
|
||||
fail:
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Device creation failed: %d.\n", err);
|
||||
av_buffer_unref(&device_ref);
|
||||
goto done;
|
||||
}
|
||||
|
||||
void hw_device_free_all(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
av_freep(&hw_devices[i]->name);
|
||||
av_buffer_unref(&hw_devices[i]->device_ref);
|
||||
av_freep(&hw_devices[i]);
|
||||
}
|
||||
av_freep(&hw_devices);
|
||||
nb_hw_devices = 0;
|
||||
}
|
||||
|
||||
static enum AVHWDeviceType hw_device_match_type_by_hwaccel(enum HWAccelID hwaccel_id)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev;
|
||||
int err;
|
||||
|
||||
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.
|
||||
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_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) {
|
||||
dev = hw_device_get_by_type(type);
|
||||
if (!dev) {
|
||||
hw_device_init_from_string(av_hwdevice_get_type_name(type),
|
||||
&dev);
|
||||
}
|
||||
} else {
|
||||
// No device required.
|
||||
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_hwdevice_get_type_name(type), ist->dec->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ist->dec_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
||||
if (!ist->dec_ctx->hw_device_ctx)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int hwaccel_retrieve_data(AVCodecContext *avctx, AVFrame *input)
|
||||
{
|
||||
InputStream *ist = avctx->opaque;
|
||||
AVFrame *output = NULL;
|
||||
enum AVPixelFormat output_format = ist->hwaccel_output_format;
|
||||
int err;
|
||||
|
||||
if (input->format == output_format) {
|
||||
// Nothing to do.
|
||||
return 0;
|
||||
}
|
||||
|
||||
output = av_frame_alloc();
|
||||
if (!output)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
output->format = output_format;
|
||||
|
||||
err = av_hwframe_transfer_data(output, input, 0);
|
||||
if (err < 0) {
|
||||
av_log(avctx, AV_LOG_ERROR, "Failed to transfer data to "
|
||||
"output frame: %d.\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = av_frame_copy_props(output, input);
|
||||
if (err < 0) {
|
||||
av_frame_unref(output);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
av_frame_unref(input);
|
||||
av_frame_move_ref(input, output);
|
||||
av_frame_free(&output);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_frame_free(&output);
|
||||
return err;
|
||||
}
|
||||
|
||||
int hwaccel_decode_init(AVCodecContext *avctx)
|
||||
{
|
||||
InputStream *ist = avctx->opaque;
|
||||
|
||||
ist->hwaccel_retrieve_data = &hwaccel_retrieve_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
* Various utilities for command line tools
|
||||
* copyright (c) 2003 Fabrice Bellard
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and include guards
|
||||
*
|
||||
* CHANGES 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - Include guards renamed
|
||||
* - Unused headers removed
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_CMDUTILS_H
|
||||
#define FFTOOLS_CMDUTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libswscale/swscale.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef main /* We don't want SDL to override our main() */
|
||||
#endif
|
||||
|
||||
/**
|
||||
* program name, defined by the program for show_version().
|
||||
*/
|
||||
extern const char program_name[];
|
||||
|
||||
/**
|
||||
* program birth year, defined by the program for show_banner()
|
||||
*/
|
||||
extern const int program_birth_year;
|
||||
|
||||
extern AVCodecContext *avcodec_opts[AVMEDIA_TYPE_NB];
|
||||
extern AVFormatContext *avformat_opts;
|
||||
extern AVDictionary *sws_dict;
|
||||
extern AVDictionary *swr_opts;
|
||||
extern AVDictionary *format_opts, *codec_opts, *resample_opts;
|
||||
extern int hide_banner;
|
||||
|
||||
/**
|
||||
* Register a program-specific cleanup routine.
|
||||
*/
|
||||
void register_exit(void (*cb)(int ret));
|
||||
|
||||
/**
|
||||
* Wraps exit with a program-specific cleanup routine.
|
||||
*/
|
||||
void exit_program(int ret) av_noreturn;
|
||||
|
||||
/**
|
||||
* Initialize dynamic library loading
|
||||
*/
|
||||
void init_dynload(void);
|
||||
|
||||
/**
|
||||
* Initialize the cmdutils option system, in particular
|
||||
* allocate the *_opts contexts.
|
||||
*/
|
||||
void init_opts(void);
|
||||
/**
|
||||
* Uninitialize the cmdutils option system, in particular
|
||||
* free the *_opts contexts and their contents.
|
||||
*/
|
||||
void uninit_opts(void);
|
||||
|
||||
/**
|
||||
* Trivial log callback.
|
||||
* Only suitable for opt_help and similar since it lacks prefix handling.
|
||||
*/
|
||||
void log_callback_help(void* ptr, int level, const char* fmt, va_list vl);
|
||||
|
||||
/**
|
||||
* Override the cpuflags.
|
||||
*/
|
||||
int opt_cpuflags(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Fallback for options that are not explicitly handled, these will be
|
||||
* parsed through AVOptions.
|
||||
*/
|
||||
int opt_default(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Set the libav* libraries log level.
|
||||
*/
|
||||
int opt_loglevel(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
int opt_report(const char *opt);
|
||||
|
||||
int opt_max_alloc(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
int opt_codec_debug(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Limit the execution time.
|
||||
*/
|
||||
int opt_timelimit(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Parse a string and return its corresponding value as a double.
|
||||
* Exit from the application if the string cannot be correctly
|
||||
* parsed or the corresponding value is invalid.
|
||||
*
|
||||
* @param context the context of the value to be set (e.g. the
|
||||
* corresponding command line option name)
|
||||
* @param numstr the string to be parsed
|
||||
* @param type the type (OPT_INT64 or OPT_FLOAT) as which the
|
||||
* string should be parsed
|
||||
* @param min the minimum valid accepted value
|
||||
* @param max the maximum valid accepted value
|
||||
*/
|
||||
double parse_number_or_die(const char *context, const char *numstr, int type,
|
||||
double min, double max);
|
||||
|
||||
/**
|
||||
* Parse a string specifying a time and return its corresponding
|
||||
* value as a number of microseconds. Exit from the application if
|
||||
* the string cannot be correctly parsed.
|
||||
*
|
||||
* @param context the context of the value to be set (e.g. the
|
||||
* corresponding command line option name)
|
||||
* @param timestr the string to be parsed
|
||||
* @param is_duration a flag which tells how to interpret timestr, if
|
||||
* not zero timestr is interpreted as a duration, otherwise as a
|
||||
* date
|
||||
*
|
||||
* @see av_parse_time()
|
||||
*/
|
||||
int64_t parse_time_or_die(const char *context, const char *timestr,
|
||||
int is_duration);
|
||||
|
||||
typedef struct SpecifierOpt {
|
||||
char *specifier; /**< stream/chapter/program/... specifier */
|
||||
union {
|
||||
uint8_t *str;
|
||||
int i;
|
||||
int64_t i64;
|
||||
uint64_t ui64;
|
||||
float f;
|
||||
double dbl;
|
||||
} u;
|
||||
} SpecifierOpt;
|
||||
|
||||
typedef struct OptionDef {
|
||||
const char *name;
|
||||
int flags;
|
||||
#define HAS_ARG 0x0001
|
||||
#define OPT_BOOL 0x0002
|
||||
#define OPT_EXPERT 0x0004
|
||||
#define OPT_STRING 0x0008
|
||||
#define OPT_VIDEO 0x0010
|
||||
#define OPT_AUDIO 0x0020
|
||||
#define OPT_INT 0x0080
|
||||
#define OPT_FLOAT 0x0100
|
||||
#define OPT_SUBTITLE 0x0200
|
||||
#define OPT_INT64 0x0400
|
||||
#define OPT_EXIT 0x0800
|
||||
#define OPT_DATA 0x1000
|
||||
#define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only).
|
||||
implied by OPT_OFFSET or OPT_SPEC */
|
||||
#define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */
|
||||
#define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt.
|
||||
Implies OPT_OFFSET. Next element after the offset is
|
||||
an int containing element count in the array. */
|
||||
#define OPT_TIME 0x10000
|
||||
#define OPT_DOUBLE 0x20000
|
||||
#define OPT_INPUT 0x40000
|
||||
#define OPT_OUTPUT 0x80000
|
||||
union {
|
||||
void *dst_ptr;
|
||||
int (*func_arg)(void *, const char *, const char *);
|
||||
size_t off;
|
||||
} u;
|
||||
const char *help;
|
||||
const char *argname;
|
||||
} OptionDef;
|
||||
|
||||
/**
|
||||
* Print help for all options matching specified flags.
|
||||
*
|
||||
* @param options a list of options
|
||||
* @param msg title of this group. Only printed if at least one option matches.
|
||||
* @param req_flags print only options which have all those flags set.
|
||||
* @param rej_flags don't print options which have any of those flags set.
|
||||
* @param alt_flags print only options that have at least one of those flags set
|
||||
*/
|
||||
void show_help_options(const OptionDef *options, const char *msg, int req_flags,
|
||||
int rej_flags, int alt_flags);
|
||||
|
||||
#if CONFIG_AVDEVICE
|
||||
#define CMDUTILS_COMMON_OPTIONS_AVDEVICE \
|
||||
{ "sources" , OPT_EXIT | HAS_ARG, { .func_arg = show_sources }, \
|
||||
"list sources of the input device", "device" }, \
|
||||
{ "sinks" , OPT_EXIT | HAS_ARG, { .func_arg = show_sinks }, \
|
||||
"list sinks of the output device", "device" }, \
|
||||
|
||||
#else
|
||||
#define CMDUTILS_COMMON_OPTIONS_AVDEVICE
|
||||
#endif
|
||||
|
||||
#define CMDUTILS_COMMON_OPTIONS \
|
||||
{ "L", OPT_EXIT, { .func_arg = show_license }, "show license" }, \
|
||||
{ "h", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "?", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "-help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
|
||||
{ "version", OPT_EXIT, { .func_arg = show_version }, "show version" }, \
|
||||
{ "buildconf", OPT_EXIT, { .func_arg = show_buildconf }, "show build configuration" }, \
|
||||
{ "formats", OPT_EXIT, { .func_arg = show_formats }, "show available formats" }, \
|
||||
{ "muxers", OPT_EXIT, { .func_arg = show_muxers }, "show available muxers" }, \
|
||||
{ "demuxers", OPT_EXIT, { .func_arg = show_demuxers }, "show available demuxers" }, \
|
||||
{ "devices", OPT_EXIT, { .func_arg = show_devices }, "show available devices" }, \
|
||||
{ "codecs", OPT_EXIT, { .func_arg = show_codecs }, "show available codecs" }, \
|
||||
{ "decoders", OPT_EXIT, { .func_arg = show_decoders }, "show available decoders" }, \
|
||||
{ "encoders", OPT_EXIT, { .func_arg = show_encoders }, "show available encoders" }, \
|
||||
{ "bsfs", OPT_EXIT, { .func_arg = show_bsfs }, "show available bit stream filters" }, \
|
||||
{ "protocols", OPT_EXIT, { .func_arg = show_protocols }, "show available protocols" }, \
|
||||
{ "filters", OPT_EXIT, { .func_arg = show_filters }, "show available filters" }, \
|
||||
{ "pix_fmts", OPT_EXIT, { .func_arg = show_pix_fmts }, "show available pixel formats" }, \
|
||||
{ "layouts", OPT_EXIT, { .func_arg = show_layouts }, "show standard channel layouts" }, \
|
||||
{ "sample_fmts", OPT_EXIT, { .func_arg = show_sample_fmts }, "show available audio sample formats" }, \
|
||||
{ "colors", OPT_EXIT, { .func_arg = show_colors }, "show available color names" }, \
|
||||
{ "loglevel", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \
|
||||
{ "v", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \
|
||||
{ "report", 0, { (void*)opt_report }, "generate a report" }, \
|
||||
{ "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_AVDEVICE \
|
||||
|
||||
/**
|
||||
* Show help for all options with given flags in class and all its
|
||||
* children.
|
||||
*/
|
||||
void show_help_children(const AVClass *class, int flags);
|
||||
|
||||
/**
|
||||
* Per-fftool specific help handler. Implemented in each
|
||||
* fftool, called by show_help().
|
||||
*/
|
||||
void show_help_default(const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Generic -h handler common to all fftools.
|
||||
*/
|
||||
int show_help(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Parse the command line arguments.
|
||||
*
|
||||
* @param optctx an opaque options context
|
||||
* @param argc number of command line arguments
|
||||
* @param argv values of command line arguments
|
||||
* @param options Array with the definitions required to interpret every
|
||||
* option of the form: -option_name [argument]
|
||||
* @param parse_arg_function Name of the function called to process every
|
||||
* argument without a leading option name flag. NULL if such arguments do
|
||||
* not have to be processed.
|
||||
*/
|
||||
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
|
||||
void (* parse_arg_function)(void *optctx, const char*));
|
||||
|
||||
/**
|
||||
* Parse one given option.
|
||||
*
|
||||
* @return on success 1 if arg was consumed, 0 otherwise; negative number on error
|
||||
*/
|
||||
int parse_option(void *optctx, const char *opt, const char *arg,
|
||||
const OptionDef *options);
|
||||
|
||||
/**
|
||||
* An option extracted from the commandline.
|
||||
* Cannot use AVDictionary because of options like -map which can be
|
||||
* used multiple times.
|
||||
*/
|
||||
typedef struct Option {
|
||||
const OptionDef *opt;
|
||||
const char *key;
|
||||
const char *val;
|
||||
} Option;
|
||||
|
||||
typedef struct OptionGroupDef {
|
||||
/**< group name */
|
||||
const char *name;
|
||||
/**
|
||||
* Option to be used as group separator. Can be NULL for groups which
|
||||
* are terminated by a non-option argument (e.g. ffmpeg output files)
|
||||
*/
|
||||
const char *sep;
|
||||
/**
|
||||
* Option flags that must be set on each option that is
|
||||
* applied to this group
|
||||
*/
|
||||
int flags;
|
||||
} OptionGroupDef;
|
||||
|
||||
typedef struct OptionGroup {
|
||||
const OptionGroupDef *group_def;
|
||||
const char *arg;
|
||||
|
||||
Option *opts;
|
||||
int nb_opts;
|
||||
|
||||
AVDictionary *codec_opts;
|
||||
AVDictionary *format_opts;
|
||||
AVDictionary *resample_opts;
|
||||
AVDictionary *sws_dict;
|
||||
AVDictionary *swr_opts;
|
||||
} OptionGroup;
|
||||
|
||||
/**
|
||||
* A list of option groups that all have the same group type
|
||||
* (e.g. input files or output files)
|
||||
*/
|
||||
typedef struct OptionGroupList {
|
||||
const OptionGroupDef *group_def;
|
||||
|
||||
OptionGroup *groups;
|
||||
int nb_groups;
|
||||
} OptionGroupList;
|
||||
|
||||
typedef struct OptionParseContext {
|
||||
OptionGroup global_opts;
|
||||
|
||||
OptionGroupList *groups;
|
||||
int nb_groups;
|
||||
|
||||
/* parsing state */
|
||||
OptionGroup cur_group;
|
||||
} OptionParseContext;
|
||||
|
||||
/**
|
||||
* Parse an options group and write results into optctx.
|
||||
*
|
||||
* @param optctx an app-specific options context. NULL for global options group
|
||||
*/
|
||||
int parse_optgroup(void *optctx, OptionGroup *g);
|
||||
|
||||
/**
|
||||
* Split the commandline into an intermediate form convenient for further
|
||||
* processing.
|
||||
*
|
||||
* The commandline is assumed to be composed of options which either belong to a
|
||||
* group (those with OPT_SPEC, OPT_OFFSET or OPT_PERFILE) or are global
|
||||
* (everything else).
|
||||
*
|
||||
* A group (defined by an OptionGroupDef struct) is a sequence of options
|
||||
* terminated by either a group separator option (e.g. -i) or a parameter that
|
||||
* is not an option (doesn't start with -). A group without a separator option
|
||||
* must always be first in the supplied groups list.
|
||||
*
|
||||
* All options within the same group are stored in one OptionGroup struct in an
|
||||
* OptionGroupList, all groups with the same group definition are stored in one
|
||||
* OptionGroupList in OptionParseContext.groups. The order of group lists is the
|
||||
* same as the order of group definitions.
|
||||
*/
|
||||
int split_commandline(OptionParseContext *octx, int argc, char *argv[],
|
||||
const OptionDef *options,
|
||||
const OptionGroupDef *groups, int nb_groups);
|
||||
|
||||
/**
|
||||
* Free all allocated memory in an OptionParseContext.
|
||||
*/
|
||||
void uninit_parse_context(OptionParseContext *octx);
|
||||
|
||||
/**
|
||||
* Find the '-loglevel' option in the command line args and apply it.
|
||||
*/
|
||||
void parse_loglevel(int argc, char **argv, const OptionDef *options);
|
||||
|
||||
/**
|
||||
* Return index of option opt in argv or 0 if not found.
|
||||
*/
|
||||
int locate_option(int argc, char **argv, const OptionDef *options,
|
||||
const char *optname);
|
||||
|
||||
/**
|
||||
* Check if the given stream matches a stream specifier.
|
||||
*
|
||||
* @param s Corresponding format context.
|
||||
* @param st Stream from s to be checked.
|
||||
* @param spec A stream specifier of the [v|a|s|d]:[\<stream index\>] form.
|
||||
*
|
||||
* @return 1 if the stream matches, 0 if it doesn't, <0 on error
|
||||
*/
|
||||
int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec);
|
||||
|
||||
/**
|
||||
* Filter out options for given codec.
|
||||
*
|
||||
* Create a new options dictionary containing only the options from
|
||||
* opts which apply to the codec with ID codec_id.
|
||||
*
|
||||
* @param opts dictionary to place options in
|
||||
* @param codec_id ID of the codec that should be filtered for
|
||||
* @param s Corresponding format context.
|
||||
* @param st A stream from s for which the options should be filtered.
|
||||
* @param codec The particular codec for which the options should be filtered.
|
||||
* If null, the default one is looked up according to the codec id.
|
||||
* @return a pointer to the created dictionary
|
||||
*/
|
||||
AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id,
|
||||
AVFormatContext *s, AVStream *st, AVCodec *codec);
|
||||
|
||||
/**
|
||||
* Setup AVCodecContext options for avformat_find_stream_info().
|
||||
*
|
||||
* Create an array of dictionaries, one dictionary for each stream
|
||||
* contained in s.
|
||||
* Each dictionary will contain the options from codec_opts which can
|
||||
* be applied to the corresponding stream codec context.
|
||||
*
|
||||
* @return pointer to the created array of dictionaries, NULL if it
|
||||
* cannot be created
|
||||
*/
|
||||
AVDictionary **setup_find_stream_info_opts(AVFormatContext *s,
|
||||
AVDictionary *codec_opts);
|
||||
|
||||
/**
|
||||
* Print an error message to stderr, indicating filename and a human
|
||||
* readable description of the error code err.
|
||||
*
|
||||
* If strerror_r() is not available the use of this function in a
|
||||
* multithreaded application may be unsafe.
|
||||
*
|
||||
* @see av_strerror()
|
||||
*/
|
||||
void print_error(const char *filename, int err);
|
||||
|
||||
/**
|
||||
* Print the program banner to stderr. The banner contents depend on the
|
||||
* current version of the repository and of the libav* libraries used by
|
||||
* the program.
|
||||
*/
|
||||
void show_banner(int argc, char **argv, const OptionDef *options);
|
||||
|
||||
/**
|
||||
* Print the version of the program to stdout. The version message
|
||||
* depends on the current versions of the repository and of the libav*
|
||||
* libraries.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_version(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print the build configuration of the program to stdout. The contents
|
||||
* depend on the definition of FFMPEG_CONFIGURATION.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_buildconf(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print the license of the program to stdout. The license depends on
|
||||
* the license of the libraries compiled into the program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_license(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the formats supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_formats(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the muxers supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_muxers(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the demuxer supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_demuxers(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the devices supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_devices(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
#if CONFIG_AVDEVICE
|
||||
/**
|
||||
* Print a listing containing autodetected sinks of the output device.
|
||||
* Device name with options may be passed as an argument to limit results.
|
||||
*/
|
||||
int show_sinks(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing autodetected sources of the input device.
|
||||
* Device name with options may be passed as an argument to limit results.
|
||||
*/
|
||||
int show_sources(void *optctx, const char *opt, const char *arg);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Print a listing containing all the codecs supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_codecs(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the decoders supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_decoders(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the encoders supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_encoders(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the filters supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_filters(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the bit stream filters supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_bsfs(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the protocols supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_protocols(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the pixel formats supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_pix_fmts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the standard channel layouts supported by
|
||||
* the program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_layouts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the sample formats supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_sample_fmts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the color names and values recognized
|
||||
* by the program.
|
||||
*/
|
||||
int show_colors(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Return a positive value if a line read from standard input
|
||||
* starts with [yY], otherwise return 0.
|
||||
*/
|
||||
int read_yesno(void);
|
||||
|
||||
/**
|
||||
* Get a file corresponding to a preset file.
|
||||
*
|
||||
* If is_path is non-zero, look for the file in the path preset_name.
|
||||
* Otherwise search for a file named arg.ffpreset in the directories
|
||||
* $FFMPEG_DATADIR (if set), $HOME/.ffmpeg, and in the datadir defined
|
||||
* at configuration time or in a "ffpresets" folder along the executable
|
||||
* on win32, in that order. If no such file is found and
|
||||
* codec_name is defined, then search for a file named
|
||||
* codec_name-preset_name.avpreset in the above-mentioned directories.
|
||||
*
|
||||
* @param filename buffer where the name of the found filename is written
|
||||
* @param filename_size size in bytes of the filename buffer
|
||||
* @param preset_name name of the preset to search
|
||||
* @param is_path tell if preset_name is a filename path
|
||||
* @param codec_name name of the codec for which to look for the
|
||||
* preset, may be NULL
|
||||
*/
|
||||
FILE *get_preset_file(char *filename, size_t filename_size,
|
||||
const char *preset_name, int is_path, const char *codec_name);
|
||||
|
||||
/**
|
||||
* Realloc array to hold new_size elements of elem_size.
|
||||
* Calls exit() on failure.
|
||||
*
|
||||
* @param array array to reallocate
|
||||
* @param elem_size size in bytes of each element
|
||||
* @param size new element count will be written here
|
||||
* @param new_size number of elements to place in reallocated array
|
||||
* @return reallocated array
|
||||
*/
|
||||
void *grow_array(void *array, int elem_size, int *size, int new_size);
|
||||
|
||||
#define media_type_string av_get_media_type_string
|
||||
|
||||
#define GROW_ARRAY(array, nb_elems)\
|
||||
array = grow_array(array, sizeof(*array), &nb_elems, nb_elems + 1)
|
||||
|
||||
#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)
|
||||
|
||||
#define GET_SAMPLE_RATE_NAME(rate)\
|
||||
char name[16];\
|
||||
snprintf(name, sizeof(name), "%d", rate);
|
||||
|
||||
#define GET_CH_LAYOUT_NAME(ch_layout)\
|
||||
char name[16];\
|
||||
snprintf(name, sizeof(name), "0x%"PRIx64, ch_layout);
|
||||
|
||||
#define GET_CH_LAYOUT_DESC(ch_layout)\
|
||||
char name[128];\
|
||||
av_get_channel_layout_string(name, sizeof(name), 0, ch_layout);
|
||||
|
||||
double get_rotation(AVStream *st);
|
||||
|
||||
#endif /* FFTOOLS_CMDUTILS_H */
|
||||
@@ -0,0 +1,684 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_FFMPEG_H
|
||||
#define FFTOOLS_FFMPEG_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "fftools_cmdutils.h"
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavformat/avio.h"
|
||||
|
||||
#include "libavcodec/avcodec.h"
|
||||
|
||||
#include "libavfilter/avfilter.h"
|
||||
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/hwcontext.h"
|
||||
#include "libavutil/pixfmt.h"
|
||||
#include "libavutil/rational.h"
|
||||
#include "libavutil/thread.h"
|
||||
#include "libavutil/threadmessage.h"
|
||||
|
||||
#include "libswresample/swresample.h"
|
||||
|
||||
#define VSYNC_AUTO -1
|
||||
#define VSYNC_PASSTHROUGH 0
|
||||
#define VSYNC_CFR 1
|
||||
#define VSYNC_VFR 2
|
||||
#define VSYNC_VSCFR 0xfe
|
||||
#define VSYNC_DROP 0xff
|
||||
|
||||
#define MAX_STREAMS 1024 /* arbitrary sanity check value */
|
||||
|
||||
enum HWAccelID {
|
||||
HWACCEL_NONE = 0,
|
||||
HWACCEL_AUTO,
|
||||
HWACCEL_GENERIC,
|
||||
HWACCEL_VIDEOTOOLBOX,
|
||||
HWACCEL_QSV,
|
||||
HWACCEL_CUVID,
|
||||
};
|
||||
|
||||
typedef struct HWAccel {
|
||||
const char *name;
|
||||
int (*init)(AVCodecContext *s);
|
||||
enum HWAccelID id;
|
||||
enum AVPixelFormat pix_fmt;
|
||||
} HWAccel;
|
||||
|
||||
typedef struct HWDevice {
|
||||
char *name;
|
||||
enum AVHWDeviceType type;
|
||||
AVBufferRef *device_ref;
|
||||
} HWDevice;
|
||||
|
||||
/* select an input stream for an output stream */
|
||||
typedef struct StreamMap {
|
||||
int disabled; /* 1 is this mapping is disabled by a negative map */
|
||||
int file_index;
|
||||
int stream_index;
|
||||
int sync_file_index;
|
||||
int sync_stream_index;
|
||||
char *linklabel; /* name of an output link, for mapping lavfi outputs */
|
||||
} StreamMap;
|
||||
|
||||
typedef struct {
|
||||
int file_idx, stream_idx, channel_idx; // input
|
||||
int ofile_idx, ostream_idx; // output
|
||||
} AudioChannelMap;
|
||||
|
||||
typedef struct OptionsContext {
|
||||
OptionGroup *g;
|
||||
|
||||
/* input/output options */
|
||||
int64_t start_time;
|
||||
int64_t start_time_eof;
|
||||
int seek_timestamp;
|
||||
const char *format;
|
||||
|
||||
SpecifierOpt *codec_names;
|
||||
int nb_codec_names;
|
||||
SpecifierOpt *audio_channels;
|
||||
int nb_audio_channels;
|
||||
SpecifierOpt *audio_sample_rate;
|
||||
int nb_audio_sample_rate;
|
||||
SpecifierOpt *frame_rates;
|
||||
int nb_frame_rates;
|
||||
SpecifierOpt *frame_sizes;
|
||||
int nb_frame_sizes;
|
||||
SpecifierOpt *frame_pix_fmts;
|
||||
int nb_frame_pix_fmts;
|
||||
|
||||
/* input options */
|
||||
int64_t input_ts_offset;
|
||||
int loop;
|
||||
int rate_emu;
|
||||
int accurate_seek;
|
||||
int thread_queue_size;
|
||||
|
||||
SpecifierOpt *ts_scale;
|
||||
int nb_ts_scale;
|
||||
SpecifierOpt *dump_attachment;
|
||||
int nb_dump_attachment;
|
||||
SpecifierOpt *hwaccels;
|
||||
int nb_hwaccels;
|
||||
SpecifierOpt *hwaccel_devices;
|
||||
int nb_hwaccel_devices;
|
||||
SpecifierOpt *hwaccel_output_formats;
|
||||
int nb_hwaccel_output_formats;
|
||||
SpecifierOpt *autorotate;
|
||||
int nb_autorotate;
|
||||
|
||||
/* output options */
|
||||
StreamMap *stream_maps;
|
||||
int nb_stream_maps;
|
||||
AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */
|
||||
int nb_audio_channel_maps; /* number of (valid) -map_channel settings */
|
||||
int metadata_global_manual;
|
||||
int metadata_streams_manual;
|
||||
int metadata_chapters_manual;
|
||||
const char **attachments;
|
||||
int nb_attachments;
|
||||
|
||||
int chapters_input_file;
|
||||
|
||||
int64_t recording_time;
|
||||
int64_t stop_time;
|
||||
uint64_t limit_filesize;
|
||||
float mux_preload;
|
||||
float mux_max_delay;
|
||||
int shortest;
|
||||
int bitexact;
|
||||
|
||||
int video_disable;
|
||||
int audio_disable;
|
||||
int subtitle_disable;
|
||||
int data_disable;
|
||||
|
||||
/* indexed by output file stream index */
|
||||
int *streamid_map;
|
||||
int nb_streamid_map;
|
||||
|
||||
SpecifierOpt *metadata;
|
||||
int nb_metadata;
|
||||
SpecifierOpt *max_frames;
|
||||
int nb_max_frames;
|
||||
SpecifierOpt *bitstream_filters;
|
||||
int nb_bitstream_filters;
|
||||
SpecifierOpt *codec_tags;
|
||||
int nb_codec_tags;
|
||||
SpecifierOpt *sample_fmts;
|
||||
int nb_sample_fmts;
|
||||
SpecifierOpt *qscale;
|
||||
int nb_qscale;
|
||||
SpecifierOpt *forced_key_frames;
|
||||
int nb_forced_key_frames;
|
||||
SpecifierOpt *force_fps;
|
||||
int nb_force_fps;
|
||||
SpecifierOpt *frame_aspect_ratios;
|
||||
int nb_frame_aspect_ratios;
|
||||
SpecifierOpt *rc_overrides;
|
||||
int nb_rc_overrides;
|
||||
SpecifierOpt *intra_matrices;
|
||||
int nb_intra_matrices;
|
||||
SpecifierOpt *inter_matrices;
|
||||
int nb_inter_matrices;
|
||||
SpecifierOpt *chroma_intra_matrices;
|
||||
int nb_chroma_intra_matrices;
|
||||
SpecifierOpt *top_field_first;
|
||||
int nb_top_field_first;
|
||||
SpecifierOpt *metadata_map;
|
||||
int nb_metadata_map;
|
||||
SpecifierOpt *presets;
|
||||
int nb_presets;
|
||||
SpecifierOpt *copy_initial_nonkeyframes;
|
||||
int nb_copy_initial_nonkeyframes;
|
||||
SpecifierOpt *copy_prior_start;
|
||||
int nb_copy_prior_start;
|
||||
SpecifierOpt *filters;
|
||||
int nb_filters;
|
||||
SpecifierOpt *filter_scripts;
|
||||
int nb_filter_scripts;
|
||||
SpecifierOpt *reinit_filters;
|
||||
int nb_reinit_filters;
|
||||
SpecifierOpt *fix_sub_duration;
|
||||
int nb_fix_sub_duration;
|
||||
SpecifierOpt *canvas_sizes;
|
||||
int nb_canvas_sizes;
|
||||
SpecifierOpt *pass;
|
||||
int nb_pass;
|
||||
SpecifierOpt *passlogfiles;
|
||||
int nb_passlogfiles;
|
||||
SpecifierOpt *max_muxing_queue_size;
|
||||
int nb_max_muxing_queue_size;
|
||||
SpecifierOpt *guess_layout_max;
|
||||
int nb_guess_layout_max;
|
||||
SpecifierOpt *apad;
|
||||
int nb_apad;
|
||||
SpecifierOpt *discard;
|
||||
int nb_discard;
|
||||
SpecifierOpt *disposition;
|
||||
int nb_disposition;
|
||||
SpecifierOpt *program;
|
||||
int nb_program;
|
||||
SpecifierOpt *time_bases;
|
||||
int nb_time_bases;
|
||||
SpecifierOpt *enc_time_bases;
|
||||
int nb_enc_time_bases;
|
||||
} OptionsContext;
|
||||
|
||||
typedef struct InputFilter {
|
||||
AVFilterContext *filter;
|
||||
struct InputStream *ist;
|
||||
struct FilterGraph *graph;
|
||||
uint8_t *name;
|
||||
enum AVMediaType type; // AVMEDIA_TYPE_SUBTITLE for sub2video
|
||||
|
||||
AVFifoBuffer *frame_queue;
|
||||
|
||||
// parameters configured for this input
|
||||
int format;
|
||||
|
||||
int width, height;
|
||||
AVRational sample_aspect_ratio;
|
||||
|
||||
int sample_rate;
|
||||
int channels;
|
||||
uint64_t channel_layout;
|
||||
|
||||
AVBufferRef *hw_frames_ctx;
|
||||
|
||||
int eof;
|
||||
} InputFilter;
|
||||
|
||||
typedef struct OutputFilter {
|
||||
AVFilterContext *filter;
|
||||
struct OutputStream *ost;
|
||||
struct FilterGraph *graph;
|
||||
uint8_t *name;
|
||||
|
||||
/* temporary storage until stream maps are processed */
|
||||
AVFilterInOut *out_tmp;
|
||||
enum AVMediaType type;
|
||||
|
||||
/* desired output stream properties */
|
||||
int width, height;
|
||||
AVRational frame_rate;
|
||||
int format;
|
||||
int sample_rate;
|
||||
uint64_t channel_layout;
|
||||
|
||||
// those are only set if no format is specified and the encoder gives us multiple options
|
||||
int *formats;
|
||||
uint64_t *channel_layouts;
|
||||
int *sample_rates;
|
||||
} OutputFilter;
|
||||
|
||||
typedef struct FilterGraph {
|
||||
int index;
|
||||
const char *graph_desc;
|
||||
|
||||
AVFilterGraph *graph;
|
||||
int reconfiguration;
|
||||
|
||||
InputFilter **inputs;
|
||||
int nb_inputs;
|
||||
OutputFilter **outputs;
|
||||
int nb_outputs;
|
||||
} FilterGraph;
|
||||
|
||||
typedef struct InputStream {
|
||||
int file_index;
|
||||
AVStream *st;
|
||||
int discard; /* true if stream data should be discarded */
|
||||
int user_set_discard;
|
||||
int decoding_needed; /* non zero if the packets must be decoded in 'raw_fifo', see DECODING_FOR_* */
|
||||
#define DECODING_FOR_OST 1
|
||||
#define DECODING_FOR_FILTER 2
|
||||
|
||||
AVCodecContext *dec_ctx;
|
||||
AVCodec *dec;
|
||||
AVFrame *decoded_frame;
|
||||
AVFrame *filter_frame; /* a ref of decoded_frame, to be sent to filters */
|
||||
|
||||
int64_t start; /* time when read started */
|
||||
/* predicted dts of the next packet read for this stream or (when there are
|
||||
* several frames in a packet) of the next frame in current packet (in AV_TIME_BASE units) */
|
||||
int64_t next_dts;
|
||||
int64_t dts; ///< dts of the last packet read for this stream (in AV_TIME_BASE units)
|
||||
|
||||
int64_t next_pts; ///< synthetic pts for the next decode frame (in AV_TIME_BASE units)
|
||||
int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units)
|
||||
int wrap_correction_done;
|
||||
|
||||
int64_t filter_in_rescale_delta_last;
|
||||
|
||||
int64_t min_pts; /* pts with the smallest value in a current stream */
|
||||
int64_t max_pts; /* pts with the higher value in a current stream */
|
||||
|
||||
// when forcing constant input framerate through -r,
|
||||
// this contains the pts that will be given to the next decoded frame
|
||||
int64_t cfr_next_pts;
|
||||
|
||||
int64_t nb_samples; /* number of samples in the last decoded audio frame before looping */
|
||||
|
||||
double ts_scale;
|
||||
int saw_first_ts;
|
||||
AVDictionary *decoder_opts;
|
||||
AVRational framerate; /* framerate forced with -r */
|
||||
int top_field_first;
|
||||
int guess_layout_max;
|
||||
|
||||
int autorotate;
|
||||
|
||||
int fix_sub_duration;
|
||||
struct { /* previous decoded subtitle and related variables */
|
||||
int got_output;
|
||||
int ret;
|
||||
AVSubtitle subtitle;
|
||||
} prev_sub;
|
||||
|
||||
struct sub2video {
|
||||
int64_t last_pts;
|
||||
int64_t end_pts;
|
||||
AVFifoBuffer *sub_queue; ///< queue of AVSubtitle* before filter init
|
||||
AVFrame *frame;
|
||||
int w, h;
|
||||
} sub2video;
|
||||
|
||||
int dr1;
|
||||
|
||||
/* decoded data from this stream goes into all those filters
|
||||
* currently video and audio only */
|
||||
InputFilter **filters;
|
||||
int nb_filters;
|
||||
|
||||
int reinit_filters;
|
||||
|
||||
/* hwaccel options */
|
||||
enum HWAccelID hwaccel_id;
|
||||
enum AVHWDeviceType hwaccel_device_type;
|
||||
char *hwaccel_device;
|
||||
enum AVPixelFormat hwaccel_output_format;
|
||||
|
||||
/* hwaccel context */
|
||||
void *hwaccel_ctx;
|
||||
void (*hwaccel_uninit)(AVCodecContext *s);
|
||||
int (*hwaccel_get_buffer)(AVCodecContext *s, AVFrame *frame, int flags);
|
||||
int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame);
|
||||
enum AVPixelFormat hwaccel_pix_fmt;
|
||||
enum AVPixelFormat hwaccel_retrieved_pix_fmt;
|
||||
AVBufferRef *hw_frames_ctx;
|
||||
|
||||
/* stats */
|
||||
// combined size of all the packets read
|
||||
uint64_t data_size;
|
||||
/* number of packets successfully read for this stream */
|
||||
uint64_t nb_packets;
|
||||
// number of frames/samples retrieved from the decoder
|
||||
uint64_t frames_decoded;
|
||||
uint64_t samples_decoded;
|
||||
|
||||
int64_t *dts_buffer;
|
||||
int nb_dts_buffer;
|
||||
|
||||
int got_output;
|
||||
} InputStream;
|
||||
|
||||
typedef struct InputFile {
|
||||
AVFormatContext *ctx;
|
||||
int eof_reached; /* true if eof reached */
|
||||
int eagain; /* true if last read attempt returned EAGAIN */
|
||||
int ist_index; /* index of first stream in input_streams */
|
||||
int loop; /* set number of times input stream should be looped */
|
||||
int64_t duration; /* actual duration of the longest stream in a file
|
||||
at the moment when looping happens */
|
||||
AVRational time_base; /* time base of the duration */
|
||||
int64_t input_ts_offset;
|
||||
|
||||
int64_t ts_offset;
|
||||
int64_t last_ts;
|
||||
int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */
|
||||
int seek_timestamp;
|
||||
int64_t recording_time;
|
||||
int nb_streams; /* number of stream that ffmpeg is aware of; may be different
|
||||
from ctx.nb_streams if new streams appear during av_read_frame() */
|
||||
int nb_streams_warn; /* number of streams that the user was warned of */
|
||||
int rate_emu;
|
||||
int accurate_seek;
|
||||
|
||||
#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 */
|
||||
int joined; /* the thread has been joined */
|
||||
int thread_queue_size; /* maximum number of queued packets */
|
||||
#endif
|
||||
} InputFile;
|
||||
|
||||
enum forced_keyframes_const {
|
||||
FKF_N,
|
||||
FKF_N_FORCED,
|
||||
FKF_PREV_FORCED_N,
|
||||
FKF_PREV_FORCED_T,
|
||||
FKF_T,
|
||||
FKF_NB
|
||||
};
|
||||
|
||||
#define ABORT_ON_FLAG_EMPTY_OUTPUT (1 << 0)
|
||||
|
||||
extern const char *const forced_keyframes_const_names[];
|
||||
|
||||
typedef enum {
|
||||
ENCODER_FINISHED = 1,
|
||||
MUXER_FINISHED = 2,
|
||||
} OSTFinished ;
|
||||
|
||||
typedef struct OutputStream {
|
||||
int file_index; /* file index */
|
||||
int index; /* stream index in the output file */
|
||||
int source_index; /* InputStream index */
|
||||
AVStream *st; /* stream in the output file */
|
||||
int encoding_needed; /* true if encoding needed for this stream */
|
||||
int frame_number;
|
||||
/* input pts and corresponding output pts
|
||||
for A/V sync */
|
||||
struct InputStream *sync_ist; /* input stream to sync against */
|
||||
int64_t sync_opts; /* output frame counter, could be changed to some true timestamp */ // FIXME look at frame_number
|
||||
/* pts of the first frame encoded for this stream, used for limiting
|
||||
* recording time */
|
||||
int64_t first_pts;
|
||||
/* dts of the last packet sent to the muxer */
|
||||
int64_t last_mux_dts;
|
||||
// the timebase of the packets sent to the muxer
|
||||
AVRational mux_timebase;
|
||||
AVRational enc_timebase;
|
||||
|
||||
int nb_bitstream_filters;
|
||||
AVBSFContext **bsf_ctx;
|
||||
|
||||
AVCodecContext *enc_ctx;
|
||||
AVCodecParameters *ref_par; /* associated input codec parameters with encoders options applied */
|
||||
AVCodec *enc;
|
||||
int64_t max_frames;
|
||||
AVFrame *filtered_frame;
|
||||
AVFrame *last_frame;
|
||||
int last_dropped;
|
||||
int last_nb0_frames[3];
|
||||
|
||||
void *hwaccel_ctx;
|
||||
|
||||
/* video only */
|
||||
AVRational frame_rate;
|
||||
int is_cfr;
|
||||
int force_fps;
|
||||
int top_field_first;
|
||||
int rotate_overridden;
|
||||
double rotate_override_value;
|
||||
|
||||
AVRational frame_aspect_ratio;
|
||||
|
||||
/* forced key frames */
|
||||
int64_t forced_kf_ref_pts;
|
||||
int64_t *forced_kf_pts;
|
||||
int forced_kf_count;
|
||||
int forced_kf_index;
|
||||
char *forced_keyframes;
|
||||
AVExpr *forced_keyframes_pexpr;
|
||||
double forced_keyframes_expr_const_values[FKF_NB];
|
||||
|
||||
/* audio only */
|
||||
int *audio_channels_map; /* list of the channels id to pick from the source stream */
|
||||
int audio_channels_mapped; /* number of channels in audio_channels_map */
|
||||
|
||||
char *logfile_prefix;
|
||||
FILE *logfile;
|
||||
|
||||
OutputFilter *filter;
|
||||
char *avfilter;
|
||||
char *filters; ///< filtergraph associated to the -filter option
|
||||
char *filters_script; ///< filtergraph script associated to the -filter_script option
|
||||
|
||||
AVDictionary *encoder_opts;
|
||||
AVDictionary *sws_dict;
|
||||
AVDictionary *swr_opts;
|
||||
AVDictionary *resample_opts;
|
||||
char *apad;
|
||||
OSTFinished finished; /* no more packets should be written for this stream */
|
||||
int unavailable; /* true if the steram is unavailable (possibly temporarily) */
|
||||
int stream_copy;
|
||||
|
||||
// init_output_stream() has been called for this stream
|
||||
// The encoder and the bitstream filters have been initialized and the stream
|
||||
// parameters are set in the AVStream.
|
||||
int initialized;
|
||||
|
||||
int inputs_done;
|
||||
|
||||
const char *attachment_filename;
|
||||
int copy_initial_nonkeyframes;
|
||||
int copy_prior_start;
|
||||
char *disposition;
|
||||
|
||||
int keep_pix_fmt;
|
||||
|
||||
/* stats */
|
||||
// combined size of all the packets written
|
||||
uint64_t data_size;
|
||||
// number of packets send to the muxer
|
||||
uint64_t packets_written;
|
||||
// number of frames/samples sent to the encoder
|
||||
uint64_t frames_encoded;
|
||||
uint64_t samples_encoded;
|
||||
|
||||
/* packet quality factor */
|
||||
int quality;
|
||||
|
||||
int max_muxing_queue_size;
|
||||
|
||||
/* the packets are buffered here until the muxer is ready to be initialized */
|
||||
AVFifoBuffer *muxing_queue;
|
||||
|
||||
/* packet picture type */
|
||||
int pict_type;
|
||||
|
||||
/* frame encode sum of squared error values */
|
||||
int64_t error[4];
|
||||
} OutputStream;
|
||||
|
||||
typedef struct OutputFile {
|
||||
AVFormatContext *ctx;
|
||||
AVDictionary *opts;
|
||||
int ost_index; /* index of the first stream in output_streams */
|
||||
int64_t recording_time; ///< desired length of the resulting file in microseconds == AV_TIME_BASE units
|
||||
int64_t start_time; ///< start time in microseconds == AV_TIME_BASE units
|
||||
uint64_t limit_filesize; /* filesize limit expressed in bytes */
|
||||
|
||||
int shortest;
|
||||
|
||||
int header_written;
|
||||
} OutputFile;
|
||||
|
||||
extern InputStream **input_streams;
|
||||
extern int nb_input_streams;
|
||||
extern InputFile **input_files;
|
||||
extern int nb_input_files;
|
||||
|
||||
extern OutputStream **output_streams;
|
||||
extern int nb_output_streams;
|
||||
extern OutputFile **output_files;
|
||||
extern int nb_output_files;
|
||||
|
||||
extern FilterGraph **filtergraphs;
|
||||
extern int nb_filtergraphs;
|
||||
|
||||
extern char *vstats_filename;
|
||||
extern char *sdp_filename;
|
||||
|
||||
extern float audio_drift_threshold;
|
||||
extern float dts_delta_threshold;
|
||||
extern float dts_error_threshold;
|
||||
|
||||
extern int audio_volume;
|
||||
extern int audio_sync_method;
|
||||
extern int video_sync_method;
|
||||
extern float frame_drop_threshold;
|
||||
extern int do_benchmark;
|
||||
extern int do_benchmark_all;
|
||||
extern int do_deinterlace;
|
||||
extern int do_hex_dump;
|
||||
extern int do_pkt_dump;
|
||||
extern int copy_ts;
|
||||
extern int start_at_zero;
|
||||
extern int copy_tb;
|
||||
extern int debug_ts;
|
||||
extern int exit_on_error;
|
||||
extern int abort_on_flags;
|
||||
extern int print_stats;
|
||||
extern int qp_hist;
|
||||
extern int stdin_interaction;
|
||||
extern int frame_bits_per_raw_sample;
|
||||
extern AVIOContext *progress_avio;
|
||||
extern float max_error_rate;
|
||||
extern char *videotoolbox_pixfmt;
|
||||
|
||||
extern int filter_nbthreads;
|
||||
extern int filter_complex_nbthreads;
|
||||
extern int vstats_version;
|
||||
|
||||
extern const AVIOInterruptCB int_cb;
|
||||
|
||||
extern const OptionDef options[];
|
||||
extern const HWAccel hwaccels[];
|
||||
extern AVBufferRef *hw_device_ctx;
|
||||
#if CONFIG_QSV
|
||||
extern char *qsv_device;
|
||||
#endif
|
||||
extern HWDevice *filter_hw_device;
|
||||
|
||||
|
||||
void term_init(void);
|
||||
void term_exit(void);
|
||||
|
||||
void reset_options(OptionsContext *o, int is_input);
|
||||
void show_usage(void);
|
||||
|
||||
void opt_output_file(void *optctx, const char *filename);
|
||||
|
||||
void remove_avoptions(AVDictionary **a, AVDictionary *b);
|
||||
void assert_avoptions(AVDictionary *m);
|
||||
|
||||
int guess_input_channel_layout(InputStream *ist);
|
||||
|
||||
enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *avctx, AVCodec *codec, enum AVPixelFormat target);
|
||||
void choose_sample_fmt(AVStream *st, AVCodec *codec);
|
||||
|
||||
int configure_filtergraph(FilterGraph *fg);
|
||||
int configure_output_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out);
|
||||
void check_filter_outputs(void);
|
||||
int ist_in_filtergraph(FilterGraph *fg, InputStream *ist);
|
||||
int filtergraph_is_simple(FilterGraph *fg);
|
||||
int init_simple_filtergraph(InputStream *ist, OutputStream *ost);
|
||||
int init_complex_filtergraph(FilterGraph *fg);
|
||||
|
||||
void sub2video_update(InputStream *ist, AVSubtitle *sub);
|
||||
|
||||
int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame);
|
||||
|
||||
int ffmpeg_parse_options(int argc, char **argv);
|
||||
|
||||
int videotoolbox_init(AVCodecContext *s);
|
||||
int qsv_init(AVCodecContext *s);
|
||||
int cuvid_init(AVCodecContext *s);
|
||||
|
||||
HWDevice *hw_device_get_by_name(const char *name);
|
||||
int hw_device_init_from_string(const char *arg, HWDevice **dev);
|
||||
void hw_device_free_all(void);
|
||||
|
||||
int hw_device_setup_for_decode(InputStream *ist);
|
||||
int hw_device_setup_for_encode(OutputStream *ost);
|
||||
|
||||
int hwaccel_decode_init(AVCodecContext *avctx);
|
||||
|
||||
void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double));
|
||||
|
||||
void cancel_operation();
|
||||
|
||||
#endif /* FFTOOLS_FFMPEG_H */
|
||||
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* 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 "fftools_ffmpeg.h"
|
||||
|
||||
static int nb_hw_devices;
|
||||
static HWDevice **hw_devices;
|
||||
|
||||
static HWDevice *hw_device_get_by_type(enum AVHWDeviceType type)
|
||||
{
|
||||
HWDevice *found = NULL;
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
if (hw_devices[i]->type == type) {
|
||||
if (found)
|
||||
return NULL;
|
||||
found = hw_devices[i];
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
HWDevice *hw_device_get_by_name(const char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
if (!strcmp(hw_devices[i]->name, name))
|
||||
return hw_devices[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static HWDevice *hw_device_add(void)
|
||||
{
|
||||
int err;
|
||||
err = av_reallocp_array(&hw_devices, nb_hw_devices + 1,
|
||||
sizeof(*hw_devices));
|
||||
if (err) {
|
||||
nb_hw_devices = 0;
|
||||
return NULL;
|
||||
}
|
||||
hw_devices[nb_hw_devices] = av_mallocz(sizeof(HWDevice));
|
||||
if (!hw_devices[nb_hw_devices])
|
||||
return NULL;
|
||||
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"
|
||||
// "type:device,key=value,key2=value2"
|
||||
// -> av_hwdevice_ctx_create()
|
||||
// "type=name@name"
|
||||
// "type@name"
|
||||
// -> av_hwdevice_ctx_create_derived()
|
||||
|
||||
AVDictionary *options = NULL;
|
||||
char *type_name = NULL, *name = NULL, *device = NULL;
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev, *src;
|
||||
AVBufferRef *device_ref = NULL;
|
||||
int err;
|
||||
const char *errmsg, *p, *q;
|
||||
size_t k;
|
||||
|
||||
k = strcspn(arg, ":=@");
|
||||
p = arg + k;
|
||||
|
||||
type_name = av_strndup(arg, k);
|
||||
if (!type_name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
type = av_hwdevice_find_type_by_name(type_name);
|
||||
if (type == AV_HWDEVICE_TYPE_NONE) {
|
||||
errmsg = "unknown device type";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
if (*p == '=') {
|
||||
k = strcspn(p + 1, ":@");
|
||||
|
||||
name = av_strndup(p + 1, k);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
if (hw_device_get_by_name(name)) {
|
||||
errmsg = "named device already exists";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
p += 1 + k;
|
||||
} else {
|
||||
name = hw_device_default_name(type);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!*p) {
|
||||
// New device with no parameters.
|
||||
err = av_hwdevice_ctx_create(&device_ref, type,
|
||||
NULL, NULL, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (*p == ':') {
|
||||
// New device with some parameters.
|
||||
++p;
|
||||
q = strchr(p, ',');
|
||||
if (q) {
|
||||
device = av_strndup(p, q - p);
|
||||
if (!device) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
err = av_dict_parse_string(&options, q + 1, "=", ",", 0);
|
||||
if (err < 0) {
|
||||
errmsg = "failed to parse options";
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create(&device_ref, type,
|
||||
device ? device : p, options, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (*p == '@') {
|
||||
// Derive from existing device.
|
||||
|
||||
src = hw_device_get_by_name(p + 1);
|
||||
if (!src) {
|
||||
errmsg = "invalid source device name";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create_derived(&device_ref, type,
|
||||
src->device_ref, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
} else {
|
||||
errmsg = "parse error";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
name = NULL;
|
||||
err = 0;
|
||||
done:
|
||||
av_freep(&type_name);
|
||||
av_freep(&name);
|
||||
av_freep(&device);
|
||||
av_dict_free(&options);
|
||||
return err;
|
||||
invalid:
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Invalid device specification \"%s\": %s\n", arg, errmsg);
|
||||
err = AVERROR(EINVAL);
|
||||
goto done;
|
||||
fail:
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Device creation failed: %d.\n", err);
|
||||
av_buffer_unref(&device_ref);
|
||||
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;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
av_freep(&hw_devices[i]->name);
|
||||
av_buffer_unref(&hw_devices[i]->device_ref);
|
||||
av_freep(&hw_devices[i]);
|
||||
}
|
||||
av_freep(&hw_devices);
|
||||
nb_hw_devices = 0;
|
||||
}
|
||||
|
||||
static HWDevice *hw_device_match_by_codec(const AVCodec *codec)
|
||||
{
|
||||
const AVCodecHWConfig *config;
|
||||
HWDevice *dev;
|
||||
int i;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int hw_device_setup_for_decode(InputStream *ist)
|
||||
{
|
||||
const AVCodecHWConfig *config;
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev = NULL;
|
||||
int err, auto_device = 0;
|
||||
|
||||
if (ist->hwaccel_device) {
|
||||
dev = hw_device_get_by_name(ist->hwaccel_device);
|
||||
if (!dev) {
|
||||
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;
|
||||
}
|
||||
} 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_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)
|
||||
err = hw_device_init_from_type(type, NULL, &dev);
|
||||
} else {
|
||||
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_ERROR, "No device available "
|
||||
"for decoder: device type %s needed for codec %s.\n",
|
||||
av_hwdevice_get_type_name(type), ist->dec->name);
|
||||
return err;
|
||||
}
|
||||
|
||||
ist->dec_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
||||
if (!ist->dec_ctx->hw_device_ctx)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hw_device_setup_for_encode(OutputStream *ost)
|
||||
{
|
||||
HWDevice *dev;
|
||||
|
||||
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, or no device available.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int hwaccel_retrieve_data(AVCodecContext *avctx, AVFrame *input)
|
||||
{
|
||||
InputStream *ist = avctx->opaque;
|
||||
AVFrame *output = NULL;
|
||||
enum AVPixelFormat output_format = ist->hwaccel_output_format;
|
||||
int err;
|
||||
|
||||
if (input->format == output_format) {
|
||||
// Nothing to do.
|
||||
return 0;
|
||||
}
|
||||
|
||||
output = av_frame_alloc();
|
||||
if (!output)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
output->format = output_format;
|
||||
|
||||
err = av_hwframe_transfer_data(output, input, 0);
|
||||
if (err < 0) {
|
||||
av_log(avctx, AV_LOG_ERROR, "Failed to transfer data to "
|
||||
"output frame: %d.\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = av_frame_copy_props(output, input);
|
||||
if (err < 0) {
|
||||
av_frame_unref(output);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
av_frame_unref(input);
|
||||
av_frame_move_ref(input, output);
|
||||
av_frame_free(&output);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_frame_free(&output);
|
||||
return err;
|
||||
}
|
||||
|
||||
int hwaccel_decode_init(AVCodecContext *avctx)
|
||||
{
|
||||
InputStream *ist = avctx->opaque;
|
||||
|
||||
ist->hwaccel_retrieve_data = &hwaccel_retrieve_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -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,443 @@
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mobileffmpeg.h"
|
||||
/*
|
||||
* CHANGES 10.2018
|
||||
* --------------------------------------------------------
|
||||
* - getBuildConf method added
|
||||
*
|
||||
* CHANGES 09.2018
|
||||
* --------------------------------------------------------
|
||||
* - Merged with mobileffmpeg_config
|
||||
*
|
||||
* CHANGES 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - Copied methods with avutil_log_ prefix from libavutil/log.c
|
||||
*/
|
||||
|
||||
/** Forward declaration for function defined in ffmpeg.c */
|
||||
#include <pthread.h>
|
||||
|
||||
#include "config.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},
|
||||
{"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},
|
||||
{"getNativeBuildConf", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeBuildConf}
|
||||
};
|
||||
|
||||
/** 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 +465,153 @@ 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, configMethods, 9) < 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 +623,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 +635,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 +657,24 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns build configuration for FFmpeg.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the class on which this method is invoked
|
||||
* \return build configuration string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeBuildConf(JNIEnv *env, jclass object) {
|
||||
return (*env)->NewStringUTF(env, FFMPEG_CONFIGURATION);
|
||||
}
|
||||
|
||||
@@ -17,38 +17,97 @@
|
||||
* 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 "1.2"
|
||||
#define MOBILE_FFMPEG_VERSION "3.0"
|
||||
|
||||
/** 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);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_Config
|
||||
* Method: getNativeBuildConf
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeBuildConf(JNIEnv *, jclass);
|
||||
|
||||
#endif /* MOBILE_FFMPEG_H */
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 "cpu-features.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";
|
||||
|
||||
/** Prototypes of native functions defined by this file. */
|
||||
JNINativeMethod abiDetectMethods[] = {
|
||||
{"getAbi", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_AbiDetect_getAbi}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when 'abidetect' native library is loaded.
|
||||
*
|
||||
* \param vm pointer to the running virtual machine
|
||||
* \param reserved reserved
|
||||
* \return JNI version needed by 'abidetect' 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.\n", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
jclass abiDetectClass = (*env)->FindClass(env, abiDetectClassName);
|
||||
if (abiDetectClass == NULL) {
|
||||
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.\n", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns running ABI name.
|
||||
*
|
||||
* \param env pointer to native method interface
|
||||
* \param object reference to the class on which this method is invoked
|
||||
* \return running ABI name as UTF string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_getAbi(JNIEnv *env, jclass object) {
|
||||
AndroidCpuFamily family = android_getCpuFamily();
|
||||
|
||||
if (family == ANDROID_CPU_FAMILY_ARM) {
|
||||
uint64_t features = android_getCpuFeatures();
|
||||
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) {
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_NEON) {
|
||||
return (*env)->NewStringUTF(env, ABI_ARMV7A_NEON);
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_ARMV7A);
|
||||
}
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_ARM);
|
||||
}
|
||||
|
||||
} else if (family == ANDROID_CPU_FAMILY_ARM64) {
|
||||
return (*env)->NewStringUTF(env, ABI_ARM64_V8A);
|
||||
} else if (family == ANDROID_CPU_FAMILY_X86) {
|
||||
return (*env)->NewStringUTF(env, ABI_X86);
|
||||
} else if (family == ANDROID_CPU_FAMILY_X86_64) {
|
||||
return (*env)->NewStringUTF(env, ABI_X86_64);
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_UNKNOWN);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 MOBILE_FFMPEG_ABIDETECT_H
|
||||
#define MOBILE_FFMPEG_ABIDETECT_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "mobileffmpeg.h"
|
||||
|
||||
/** Represents armeabi-v7a ABI with NEON support. */
|
||||
#define ABI_ARMV7A_NEON "armeabi-v7a-neon"
|
||||
|
||||
/** Represents armeabi-v7a ABI. */
|
||||
#define ABI_ARMV7A "armeabi-v7a"
|
||||
|
||||
/** Represents armeabi ABI. */
|
||||
#define ABI_ARM "armeabi"
|
||||
|
||||
/** Represents x86 ABI. */
|
||||
#define ABI_X86 "x86"
|
||||
|
||||
/** Represents x86_64 ABI. */
|
||||
#define ABI_X86_64 "x86_64"
|
||||
|
||||
/** Represents arm64-v8a ABI. */
|
||||
#define ABI_ARM64_V8A "arm64-v8a"
|
||||
|
||||
/** Represents not supported ABIs. */
|
||||
#define ABI_UNKNOWN "unknown"
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_mobileffmpeg_AbiDetect
|
||||
* Method: getAbi
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_AbiDetect_getAbi(JNIEnv *, jclass);
|
||||
|
||||
#endif /* MOBILE_FFMPEG_ABIDETECT_H */
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 MOBILE_FFMPEG_EXCEPTION_H
|
||||
#define MOBILE_FFMPEG_EXCEPTION_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
/** Holds information to implement exception handling. */
|
||||
jmp_buf ex_buf__;
|
||||
|
||||
#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,519 @@
|
||||
/*
|
||||
* 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.List;
|
||||
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;
|
||||
|
||||
private static final AtomicReference<StringBuffer> systemCommandOutputReference;
|
||||
|
||||
private static boolean runningSystemCommand;
|
||||
|
||||
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-%s.", getPackageName(), abi.getName(), getVersion()));
|
||||
|
||||
/* NATIVE LOG LEVEL IS RECEIVED ONLY ON STARTUP */
|
||||
activeLogLevel = Level.from(getNativeLogLevel());
|
||||
|
||||
lastReceivedStatistics = new Statistics();
|
||||
|
||||
Config.enableRedirection();
|
||||
|
||||
systemCommandOutputReference = new AtomicReference<>();
|
||||
systemCommandOutputReference.set(new StringBuffer());
|
||||
|
||||
runningSystemCommand = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (runningSystemCommand) {
|
||||
|
||||
// REDIRECT SYSTEM OUTPUT
|
||||
if (activeLogLevel != Level.AV_LOG_QUIET && levelValue <= activeLogLevel.getValue()) {
|
||||
systemCommandOutputReference.get().append(text);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeLogLevel == Level.AV_LOG_QUIET || levelValue > activeLogLevel.getValue()) {
|
||||
// LOG NEITHER PRINTED NOR FORWARDED
|
||||
return;
|
||||
}
|
||||
|
||||
// ALWAYS REDIRECT COMMAND OUTPUT
|
||||
FFmpeg.appendCommandOutput(text);
|
||||
|
||||
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 to use 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>Returns package name.
|
||||
*
|
||||
* @return guessed package name according to supported external libraries
|
||||
* @since 3.0
|
||||
*/
|
||||
public static String getPackageName() {
|
||||
return Packages.getPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns supported external libraries.
|
||||
*
|
||||
* @return list of supported external libraries
|
||||
* @since 3.0
|
||||
*/
|
||||
public static List<String> getExternalLibraries() {
|
||||
return Packages.getExternalLibraries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes system command. System command is not logged to output.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param commandOutputEndPatternList list of patterns which will indicate that operation has ended
|
||||
* @param timeout execution timeout
|
||||
* @return return code
|
||||
*/
|
||||
static int systemExecute(final String[] arguments, final List<String> commandOutputEndPatternList, final long timeout) {
|
||||
systemCommandOutputReference.set(new StringBuffer());
|
||||
runningSystemCommand = true;
|
||||
|
||||
int rc = Config.nativeExecute(arguments);
|
||||
|
||||
long totalWaitTime = 0;
|
||||
|
||||
try {
|
||||
while (!systemCommandOutputContainsPattern(commandOutputEndPatternList) && (totalWaitTime < timeout)) {
|
||||
synchronized (systemCommandOutputReference) {
|
||||
systemCommandOutputReference.wait(20);
|
||||
}
|
||||
totalWaitTime += 20;
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
Log.w(TAG, "systemExecute operation interrupted.", e);
|
||||
}
|
||||
|
||||
runningSystemCommand = false;
|
||||
|
||||
nativeCancel();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
private static boolean systemCommandOutputContainsPattern(final List<String> patternList) {
|
||||
String string = systemCommandOutputReference.get().toString();
|
||||
for (String pattern : patternList) {
|
||||
if (string.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns output of last executed system command.
|
||||
*
|
||||
* @return output of last executed system command
|
||||
*/
|
||||
static String getSystemCommandOutput() {
|
||||
return systemCommandOutputReference.get().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <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. This function does not wait for termination to
|
||||
* complete and returns immediately.
|
||||
*/
|
||||
native static void nativeCancel();
|
||||
|
||||
/**
|
||||
* <p>Returns build configuration for <code>FFmpeg</code>.
|
||||
*
|
||||
* @return build configuration string
|
||||
*/
|
||||
native static String getNativeBuildConf();
|
||||
|
||||
}
|
||||
@@ -19,49 +19,34 @@
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <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;
|
||||
|
||||
private static int lastReturnCode = 0;
|
||||
|
||||
private static StringBuffer lastCommandOutput = new StringBuffer();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,41 +55,128 @@ public class FFmpeg {
|
||||
private FFmpeg() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Appends given log output to the last command output.
|
||||
*
|
||||
* @param output log output
|
||||
*/
|
||||
static void appendCommandOutput(final String output) {
|
||||
lastCommandOutput.append(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns FFmpeg version bundled within the library.
|
||||
*
|
||||
* @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);
|
||||
public static int execute(final String[] arguments) {
|
||||
lastCommandOutput = new StringBuffer();
|
||||
|
||||
/**
|
||||
* <p>Shuts down library capabilities.
|
||||
*/
|
||||
public static void shutdown() {
|
||||
Log.disableCollectingStdOutErr();
|
||||
lastReturnCode = Config.nativeExecute(arguments);
|
||||
|
||||
return lastReturnCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Overrides default {@link Object#finalize()} method.
|
||||
* <p>Synchronously executes FFmpeg command provided. Command is split into arguments using
|
||||
* provided delimiter.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param delimiter delimiter used between arguments
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
* @since 3.0
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() {
|
||||
shutdown();
|
||||
public static int execute(final String command, final String delimiter) {
|
||||
return execute((command == null) ? new String[]{""} : command.split(delimiter));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFmpeg command provided. Space character is used to split command
|
||||
* into arguments.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @return zero on successful execution, 255 on user cancel and non-zero on error
|
||||
*/
|
||||
public static int execute(final String command) {
|
||||
return execute(command, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Cancels an ongoing operation. This function does not wait for termination to complete and
|
||||
* returns immediately.
|
||||
*/
|
||||
public static void cancel() {
|
||||
Config.nativeCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns return code of last executed command.
|
||||
*
|
||||
* @return return code of last executed command
|
||||
* @since 3.0
|
||||
*/
|
||||
public static int getLastReturnCode() {
|
||||
return lastReturnCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns log output of last executed command. Please note that disabling redirection using
|
||||
* {@link Config#disableRedirection()} method also disables this functionality.
|
||||
*
|
||||
* @return output of last executed command
|
||||
* @since 3.0
|
||||
*/
|
||||
public static String getLastCommandOutput() {
|
||||
return lastCommandOutput.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns media information for given file.
|
||||
*
|
||||
* @param path path or uri of media file
|
||||
* @return media information
|
||||
* @since 3.0
|
||||
*/
|
||||
public static MediaInformation getMediaInformation(final String path) {
|
||||
return getMediaInformation(path, 10000L);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns media information for given file.
|
||||
*
|
||||
* @param path path or uri of media file
|
||||
* @param timeout complete timeout
|
||||
* @return media information
|
||||
* @since 3.0
|
||||
*/
|
||||
public static MediaInformation getMediaInformation(final String path, final Long timeout) {
|
||||
int rc = Config.systemExecute(new String[]{"-v", "info", "-hide_banner", "-i", path, "-f", "null", "-"}, Arrays.asList("Press [q] to stop, [?] for help", "No such file or directory", "Input/output error", "Conversion failed"), timeout);
|
||||
|
||||
if (rc == 0) {
|
||||
return MediaInformationParser.from(Config.getSystemCommandOutput());
|
||||
} else {
|
||||
Log.i(Config.TAG, Config.getSystemCommandOutput());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,225 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Media information class.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class MediaInformation {
|
||||
|
||||
/**
|
||||
* Format
|
||||
*/
|
||||
private String format;
|
||||
|
||||
/**
|
||||
* Path
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* Duration, in milliseconds
|
||||
*/
|
||||
private Long duration;
|
||||
|
||||
/**
|
||||
* Start time, in milliseconds
|
||||
*/
|
||||
private Long startTime;
|
||||
|
||||
/**
|
||||
* Bitrate, kb/s
|
||||
*/
|
||||
private Long bitrate;
|
||||
|
||||
/**
|
||||
* Metadata map
|
||||
*/
|
||||
private Map<String, String> metadata;
|
||||
|
||||
/**
|
||||
* List of streams
|
||||
*/
|
||||
private List<StreamInformation> streams;
|
||||
|
||||
/**
|
||||
* Raw unparsed media information
|
||||
*/
|
||||
private String rawInformation;
|
||||
|
||||
public MediaInformation() {
|
||||
this.metadata = new HashMap<>();
|
||||
this.streams = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns format.
|
||||
*
|
||||
* @return media format
|
||||
*/
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets media format.
|
||||
*
|
||||
* @param format media format
|
||||
*/
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path.
|
||||
*
|
||||
* @return media path
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets path.
|
||||
*
|
||||
* @param path media path
|
||||
*/
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns duration.
|
||||
*
|
||||
* @return media duration in milliseconds
|
||||
*/
|
||||
public Long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets duration.
|
||||
*
|
||||
* @param duration media duration in milliseconds
|
||||
*/
|
||||
public void setDuration(Long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns start time.
|
||||
*
|
||||
* @return media start time in milliseconds
|
||||
*/
|
||||
public Long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets start time.
|
||||
*
|
||||
* @param startTime media start time in milliseconds
|
||||
*/
|
||||
public void setStartTime(Long startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitrate.
|
||||
*
|
||||
* @return media bitrate in kb/s
|
||||
*/
|
||||
public Long getBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bitrate.
|
||||
*
|
||||
* @param bitrate media bitrate in kb/s
|
||||
*/
|
||||
public void setBitrate(Long bitrate) {
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unparsed media information.
|
||||
*
|
||||
* @return unparsed media information data
|
||||
*/
|
||||
public String getRawInformation() {
|
||||
return rawInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets unparsed media information.
|
||||
*
|
||||
* @param rawInformation unparsed media information data
|
||||
*/
|
||||
public void setRawInformation(String rawInformation) {
|
||||
this.rawInformation = rawInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds metadata.
|
||||
*
|
||||
* @param key metadata key
|
||||
* @param value metadata value
|
||||
*/
|
||||
public void addMetadata(String key, String value) {
|
||||
this.metadata.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all metadata entries.
|
||||
*
|
||||
* @return set of metadata entries
|
||||
*/
|
||||
public Set<Map.Entry<String, String>> getMetadataEntries() {
|
||||
return this.metadata.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new stream.
|
||||
*
|
||||
* @param stream new stream information
|
||||
*/
|
||||
public void addStream(StreamInformation stream) {
|
||||
this.streams.add(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all streams.
|
||||
*
|
||||
* @return list of streams
|
||||
*/
|
||||
public List<StreamInformation> getStreams() {
|
||||
return streams;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
* 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.util.Log;
|
||||
|
||||
import com.arthenica.mobileffmpeg.util.Pair;
|
||||
import com.arthenica.mobileffmpeg.util.Trio;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Helper class for {@link MediaInformation}.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class MediaInformationParser {
|
||||
|
||||
public static SimpleDateFormat DURATION_FORMAT;
|
||||
|
||||
public static Date REFERENCE_DURATION;
|
||||
|
||||
static {
|
||||
try {
|
||||
DURATION_FORMAT = new SimpleDateFormat("kk:mm:ss", Locale.getDefault());
|
||||
REFERENCE_DURATION = DURATION_FORMAT.parse("00:00:00");
|
||||
} catch (final ParseException e) {
|
||||
Log.i(Config.TAG, "Preparing duration reference failed.", e);
|
||||
DURATION_FORMAT = null;
|
||||
REFERENCE_DURATION = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses media information command output and builds a {@link MediaInformation} instance.
|
||||
*
|
||||
* @param rawCommandOutput media information command output
|
||||
* @return parsed instance of null if a parsing error occurs
|
||||
*/
|
||||
public static MediaInformation from(final String rawCommandOutput) {
|
||||
final MediaInformation mediaInformation = new MediaInformation();
|
||||
|
||||
if (rawCommandOutput != null) {
|
||||
final String[] split = rawCommandOutput.split("\n");
|
||||
boolean metadata = false;
|
||||
StreamInformation lastCreatedStream = null;
|
||||
final StringBuilder rawInformation = new StringBuilder();
|
||||
|
||||
for (final String outputLine : split) {
|
||||
if (outputLine.startsWith("[")) {
|
||||
metadata = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
final String trimmedLine = outputLine.trim();
|
||||
|
||||
if (trimmedLine.startsWith("Input")) {
|
||||
metadata = false;
|
||||
lastCreatedStream = null;
|
||||
Pair<String, String> pair = parseInputBlock(trimmedLine);
|
||||
mediaInformation.setFormat(pair.getFirst());
|
||||
mediaInformation.setPath(pair.getSecond());
|
||||
} else if (trimmedLine.startsWith("Duration")) {
|
||||
metadata = false;
|
||||
lastCreatedStream = null;
|
||||
Trio<Long, Long, Long> trio = parseDurationBlock(trimmedLine);
|
||||
mediaInformation.setDuration(trio.getFirst());
|
||||
mediaInformation.setStartTime(trio.getSecond());
|
||||
mediaInformation.setBitrate(trio.getThird());
|
||||
} else if (trimmedLine.startsWith("Metadata")) {
|
||||
metadata = true;
|
||||
} else if (trimmedLine.startsWith("Stream mapping") || trimmedLine.startsWith("Press [q] to stop") || trimmedLine.startsWith("Output")) {
|
||||
break;
|
||||
} else if (trimmedLine.startsWith("Stream")) {
|
||||
metadata = false;
|
||||
lastCreatedStream = MediaInformationParser.parseStreamBlock(trimmedLine);
|
||||
mediaInformation.addStream(lastCreatedStream);
|
||||
} else if (metadata) {
|
||||
Pair<String, String> pair = parseMetadataBlock(trimmedLine);
|
||||
|
||||
if (pair.getFirst() != null && pair.getSecond() != null) {
|
||||
if (lastCreatedStream != null) {
|
||||
lastCreatedStream.addMetadata(pair.getFirst(), pair.getSecond());
|
||||
} else {
|
||||
mediaInformation.addMetadata(pair.getFirst(), pair.getSecond());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rawInformation.append(outputLine);
|
||||
rawInformation.append("\n");
|
||||
}
|
||||
|
||||
mediaInformation.setRawInformation(rawInformation.toString());
|
||||
}
|
||||
|
||||
return mediaInformation;
|
||||
}
|
||||
|
||||
static Pair<String, String> parseInputBlock(final String input) {
|
||||
String format = substring(input, ",", ", from", Collections.<String>emptyList());
|
||||
String path = substring(input, "\'", "\'", Collections.<String>emptyList());
|
||||
|
||||
return new Pair<>(format, path);
|
||||
}
|
||||
|
||||
static Trio<Long, Long, Long> parseDurationBlock(final String line) {
|
||||
Long duration = parseDuration(substring(line, "Duration:", ",", Collections.singletonList("uration:")));
|
||||
Long start = parseStartTime(substring(line, "start:", ",", Collections.singletonList("tart:")));
|
||||
Long bitrate = toLongObject(substring(line, "bitrate:", Arrays.asList("itrate:", "kb/s")));
|
||||
|
||||
return new Trio<>(duration, start, bitrate);
|
||||
}
|
||||
|
||||
static Pair<String, String> parseMetadataBlock(final String metadata) {
|
||||
String key = null;
|
||||
String value = null;
|
||||
|
||||
if (metadata != null) {
|
||||
int index = metadata.indexOf(':');
|
||||
if (index > -1) {
|
||||
key = metadata.substring(0, index).trim();
|
||||
value = metadata.substring(index + 1).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return new Pair<>(key, value);
|
||||
}
|
||||
|
||||
static StreamInformation parseStreamBlock(final String input) {
|
||||
final StreamInformation streamInformation = new StreamInformation();
|
||||
|
||||
if (input != null) {
|
||||
streamInformation.setIndex(parseStreamIndex(input));
|
||||
|
||||
int typeBlockStartIndex = index(input, ":", 0, 2);
|
||||
if (typeBlockStartIndex > -1 && (typeBlockStartIndex < input.length())) {
|
||||
String[] parts = input.substring(typeBlockStartIndex + 1).split(",");
|
||||
String typePart = safeGet(parts, 0);
|
||||
|
||||
final String type = parseStreamType(typePart);
|
||||
streamInformation.setType(type);
|
||||
streamInformation.setCodec(parseStreamCodec(typePart));
|
||||
streamInformation.setFullCodec(parseStreamFullCodec(typePart));
|
||||
|
||||
String part2 = safeGet(parts, 1);
|
||||
String part3 = safeGet(parts, 2);
|
||||
String part4 = safeGet(parts, 3);
|
||||
String part5 = safeGet(parts, 4);
|
||||
if ("video".equals(type)) {
|
||||
int lastUsedPart = 1;
|
||||
|
||||
if (part2 != null) {
|
||||
int pStart = count(part2, "(");
|
||||
int pEnd = count(part2, ")");
|
||||
|
||||
while (pStart != pEnd) {
|
||||
lastUsedPart++;
|
||||
String newPart = safeGet(parts, lastUsedPart);
|
||||
if (newPart == null) {
|
||||
break;
|
||||
}
|
||||
part2 = String.format("%s,%s", part2, newPart);
|
||||
pStart = count(part2, "(");
|
||||
pEnd = count(part2, ")");
|
||||
}
|
||||
|
||||
streamInformation.setFullFormat(part2.toLowerCase().trim());
|
||||
streamInformation.setFormat(part2.replaceAll("\\(.*\\)", "").toLowerCase().trim());
|
||||
}
|
||||
|
||||
lastUsedPart++;
|
||||
String videoDimensionPart = safeGet(parts, lastUsedPart);
|
||||
if (videoDimensionPart != null) {
|
||||
String videoLayout = videoDimensionPart.toLowerCase().trim();
|
||||
Pair<Long, Long> dimensions = parseVideoDimensions(videoLayout);
|
||||
streamInformation.setWidth(dimensions.getFirst());
|
||||
streamInformation.setHeight(dimensions.getSecond());
|
||||
streamInformation.setSampleAspectRatio(parseVideoStreamSampleAspectRatio(videoLayout));
|
||||
streamInformation.setDisplayAspectRatio(parseVideoStreamDisplayAspectRatio(videoLayout));
|
||||
}
|
||||
|
||||
for (int i = lastUsedPart + 1; i < parts.length; i++) {
|
||||
String part = parts[i].replaceAll("\\(.*\\)", "").toLowerCase();
|
||||
|
||||
if (part.contains("kb/s")) {
|
||||
streamInformation.setBitrate(toLongObject(part.replaceAll("kb/s", "").trim()));
|
||||
} else if (part.contains("fps")) {
|
||||
streamInformation.setAverageFrameRate(part.replaceAll("fps", "").trim());
|
||||
} else if (part.contains("tbr")) {
|
||||
streamInformation.setRealFrameRate(part.replaceAll("tbr", "").trim());
|
||||
} else if (part.contains("tbn")) {
|
||||
streamInformation.setTimeBase(part.replaceAll("tbn", "").trim());
|
||||
} else if (part.contains("tbc")) {
|
||||
streamInformation.setCodecTimeBase(part.replaceAll("tbc", "").trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("audio".equals(type)) {
|
||||
if (part2 != null) {
|
||||
streamInformation.setSampleRate(parseAudioStreamSampleRate(part2));
|
||||
}
|
||||
if (part3 != null) {
|
||||
streamInformation.setChannelLayout(part3.toLowerCase().trim());
|
||||
}
|
||||
if (part4 != null) {
|
||||
streamInformation.setSampleFormat(part4.toLowerCase().trim());
|
||||
}
|
||||
if (part5 != null) {
|
||||
streamInformation.setBitrate(toLongObject(part5.toLowerCase().replaceAll("\\(.*\\)", "").replaceAll("kb/s", "").trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return streamInformation;
|
||||
}
|
||||
|
||||
static Pair<Long, Long> parseVideoDimensions(final String input) {
|
||||
Long width = null;
|
||||
Long height = null;
|
||||
|
||||
if (input != null) {
|
||||
final String[] dimensions = input.toLowerCase().replaceAll("\\[.*\\]", "").trim().split("x");
|
||||
width = toLongObject(safeGet(dimensions, 0));
|
||||
height = toLongObject(safeGet(dimensions, 1));
|
||||
}
|
||||
|
||||
return new Pair<>(width, height);
|
||||
}
|
||||
|
||||
static String parseVideoStreamSampleAspectRatio(final String input) {
|
||||
if (input != null) {
|
||||
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (parts[i].toLowerCase().equals("sar")) {
|
||||
return safeGet(parts, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseVideoStreamDisplayAspectRatio(final String input) {
|
||||
if (input != null) {
|
||||
String[] parts = input.replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (parts[i].toLowerCase().equals("dar")) {
|
||||
return safeGet(parts, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Long parseAudioStreamSampleRate(final String input) {
|
||||
if (input != null) {
|
||||
boolean khz = false;
|
||||
boolean mhz = false;
|
||||
|
||||
String lowerCase = input.toLowerCase();
|
||||
if (lowerCase.contains("khz")) {
|
||||
khz = true;
|
||||
}
|
||||
if (lowerCase.contains("mhz")) {
|
||||
mhz = true;
|
||||
}
|
||||
|
||||
String sampleRate = lowerCase
|
||||
.replaceAll("khz", "")
|
||||
.replaceAll("mhz", "")
|
||||
.replaceAll("hz", "")
|
||||
.trim();
|
||||
|
||||
if (khz) {
|
||||
return 1000 * toLong(sampleRate);
|
||||
} else if (mhz) {
|
||||
return 1000000 * toLong(sampleRate);
|
||||
} else {
|
||||
return toLong(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseStreamType(final String input) {
|
||||
if (input != null) {
|
||||
if (input.toLowerCase().contains("audio:")) {
|
||||
return "audio";
|
||||
} else if (input.toLowerCase().contains("video:")) {
|
||||
return "video";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseStreamCodec(final String input) {
|
||||
if (input != null) {
|
||||
return input.toLowerCase()
|
||||
.replaceAll("\\(.*\\)", "")
|
||||
.replaceAll("video:", "")
|
||||
.replaceAll("audio:", "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String parseStreamFullCodec(final String input) {
|
||||
if (input != null) {
|
||||
return input.toLowerCase()
|
||||
.replaceAll("video:", "")
|
||||
.replaceAll("audio:", "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Long parseStreamIndex(final String input) {
|
||||
String substring = substring(input, "Stream #0:", ":", Collections.singletonList("tream #0"));
|
||||
if (substring != null) {
|
||||
|
||||
// DISCARD PARANTHESIS
|
||||
return toLongObject(substring
|
||||
.replace(":", "")
|
||||
.replaceAll("\\(.*\\)", ""));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Long parseDuration(final String duration) {
|
||||
if (duration == null || duration.equals("N/A")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final Date calculated = DURATION_FORMAT.parse(duration);
|
||||
|
||||
long secondsPartInMilliseconds = calculated.getTime() - REFERENCE_DURATION.getTime();
|
||||
|
||||
int index = duration.indexOf('.');
|
||||
if (index > -1) {
|
||||
Long centiSeconds = toLong(duration.substring(index + 1));
|
||||
secondsPartInMilliseconds += 10 * centiSeconds;
|
||||
}
|
||||
|
||||
return secondsPartInMilliseconds;
|
||||
|
||||
} catch (final ParseException e) {
|
||||
Log.d(Config.TAG, String.format("Parsing duration: %s failed.", duration), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Long parseStartTime(final String startTime) {
|
||||
if (startTime == null || startTime.equals("N/A")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
BigDecimal bigDecimal = new BigDecimal(startTime);
|
||||
bigDecimal = bigDecimal.setScale(3, BigDecimal.ROUND_CEILING).multiply(new BigDecimal(1000));
|
||||
return bigDecimal.longValue();
|
||||
} catch (NumberFormatException e) {
|
||||
Log.d(Config.TAG, String.format("Parsing startTime: %s failed.", startTime), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String substring(final String string, final String start, final String end, final List<String> ignoredTokens) {
|
||||
String extractedSubstring = null;
|
||||
|
||||
if (string != null) {
|
||||
int formatStart = string.indexOf(start);
|
||||
if (formatStart > -1) {
|
||||
|
||||
int formatEnd = string.indexOf(end, formatStart + start.length());
|
||||
if (formatEnd > -1) {
|
||||
extractedSubstring = string.substring(formatStart + start.length(), formatEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((ignoredTokens != null) && (extractedSubstring != null)) {
|
||||
for (String token : ignoredTokens) {
|
||||
extractedSubstring = extractedSubstring.replaceAll(token, "");
|
||||
}
|
||||
}
|
||||
|
||||
return (extractedSubstring == null) ? null : extractedSubstring.trim();
|
||||
}
|
||||
|
||||
public static String substring(final String string, final String start, final List<String> ignoredTokens) {
|
||||
String extractedSubstring = null;
|
||||
|
||||
if (string != null) {
|
||||
int formatStart = string.indexOf(start);
|
||||
if (formatStart > -1) {
|
||||
extractedSubstring = string.substring(formatStart + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ((ignoredTokens != null) && (extractedSubstring != null)) {
|
||||
for (String token : ignoredTokens) {
|
||||
extractedSubstring = extractedSubstring.replaceAll(token, "");
|
||||
}
|
||||
}
|
||||
|
||||
return (extractedSubstring == null) ? null : extractedSubstring.trim();
|
||||
}
|
||||
|
||||
public static int index(final String string, String substring, int startIndex, int n) {
|
||||
int count = 1;
|
||||
|
||||
while (count <= n) {
|
||||
startIndex = string.indexOf(substring, startIndex + substring.length());
|
||||
count++;
|
||||
}
|
||||
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
public static int count(final String string, String substring) {
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
|
||||
do {
|
||||
index = string.indexOf(substring, index);
|
||||
if (index >= 0) {
|
||||
count++;
|
||||
index = index + substring.length();
|
||||
}
|
||||
} while (index >= 0);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static <K> K safeGet(final K[] array, final int index) {
|
||||
if (array == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final int size = array.length;
|
||||
if (size > index) {
|
||||
return array[index];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (final ArrayIndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static long toLong(final String value) {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static Long toLongObject(final String value) {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Provides helper methods to extract binary package information.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
class Packages {
|
||||
|
||||
private static final List<String> supportedExternalLibraries;
|
||||
|
||||
static {
|
||||
supportedExternalLibraries = new ArrayList<>();
|
||||
supportedExternalLibraries.add("chromaprint");
|
||||
supportedExternalLibraries.add("fontconfig");
|
||||
supportedExternalLibraries.add("freetype");
|
||||
supportedExternalLibraries.add("fribidi");
|
||||
supportedExternalLibraries.add("gmp");
|
||||
supportedExternalLibraries.add("gnutls");
|
||||
supportedExternalLibraries.add("kvazaar");
|
||||
supportedExternalLibraries.add("mp3lame");
|
||||
supportedExternalLibraries.add("libaom");
|
||||
supportedExternalLibraries.add("libass");
|
||||
supportedExternalLibraries.add("iconv");
|
||||
supportedExternalLibraries.add("libilbc");
|
||||
supportedExternalLibraries.add("libtheora");
|
||||
supportedExternalLibraries.add("libvidstab");
|
||||
supportedExternalLibraries.add("libvorbis");
|
||||
supportedExternalLibraries.add("libvpx");
|
||||
supportedExternalLibraries.add("libwebp");
|
||||
supportedExternalLibraries.add("libxml2");
|
||||
supportedExternalLibraries.add("opencore-amr");
|
||||
supportedExternalLibraries.add("opus");
|
||||
supportedExternalLibraries.add("shine");
|
||||
supportedExternalLibraries.add("sdl");
|
||||
supportedExternalLibraries.add("snappy");
|
||||
supportedExternalLibraries.add("soxr");
|
||||
supportedExternalLibraries.add("speex");
|
||||
supportedExternalLibraries.add("tesseract");
|
||||
supportedExternalLibraries.add("twolame");
|
||||
supportedExternalLibraries.add("wavpack");
|
||||
supportedExternalLibraries.add("x264");
|
||||
supportedExternalLibraries.add("x265");
|
||||
supportedExternalLibraries.add("xvid");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enabled external libraries by FFmpeg.
|
||||
*
|
||||
* @return enabled external libraries
|
||||
*/
|
||||
static List<String> getExternalLibraries() {
|
||||
final String buildConfiguration = Config.getNativeBuildConf();
|
||||
|
||||
final List<String> enabledLibraryList = new ArrayList<>();
|
||||
for (String supportedExternalLibrary : supportedExternalLibraries) {
|
||||
if (buildConfiguration.contains("enable-" + supportedExternalLibrary) ||
|
||||
buildConfiguration.contains("enable-lib" + supportedExternalLibrary)) {
|
||||
enabledLibraryList.add(supportedExternalLibrary);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(enabledLibraryList);
|
||||
|
||||
return enabledLibraryList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns MobileFFmpeg binary package name.
|
||||
*
|
||||
* @return guessed MobileFFmpeg binary package name
|
||||
*/
|
||||
static String getPackageName() {
|
||||
final List<String> externalLibraryList = getExternalLibraries();
|
||||
final boolean speex = externalLibraryList.contains("speex");
|
||||
final boolean fribidi = externalLibraryList.contains("fribidi");
|
||||
final boolean gnutls = externalLibraryList.contains("gnutls");
|
||||
final boolean xvid = externalLibraryList.contains("xvid");
|
||||
|
||||
boolean min = false;
|
||||
boolean minGpl = false;
|
||||
boolean https = false;
|
||||
boolean httpsGpl = false;
|
||||
boolean audio = false;
|
||||
boolean video = false;
|
||||
boolean full = false;
|
||||
boolean fullGpl = false;
|
||||
|
||||
if (speex && fribidi) {
|
||||
if (xvid) {
|
||||
fullGpl = true;
|
||||
} else {
|
||||
full = true;
|
||||
}
|
||||
} else if (speex) {
|
||||
audio = true;
|
||||
} else if (fribidi) {
|
||||
video = true;
|
||||
} else if (xvid) {
|
||||
if (gnutls) {
|
||||
httpsGpl = true;
|
||||
} else {
|
||||
minGpl = true;
|
||||
}
|
||||
} else {
|
||||
if (gnutls) {
|
||||
https = true;
|
||||
} else {
|
||||
min = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullGpl) {
|
||||
if (externalLibraryList.contains("chromaprint") &&
|
||||
externalLibraryList.contains("fontconfig") &&
|
||||
externalLibraryList.contains("freetype") &&
|
||||
externalLibraryList.contains("fribidi") &&
|
||||
externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls") &&
|
||||
externalLibraryList.contains("kvazaar") &&
|
||||
externalLibraryList.contains("mp3lame") &&
|
||||
externalLibraryList.contains("libaom") &&
|
||||
externalLibraryList.contains("libass") &&
|
||||
externalLibraryList.contains("iconv") &&
|
||||
externalLibraryList.contains("libilbc") &&
|
||||
externalLibraryList.contains("libtheora") &&
|
||||
externalLibraryList.contains("libvidstab") &&
|
||||
externalLibraryList.contains("libvorbis") &&
|
||||
externalLibraryList.contains("libvpx") &&
|
||||
externalLibraryList.contains("libwebp") &&
|
||||
externalLibraryList.contains("libxml2") &&
|
||||
externalLibraryList.contains("opencore-amr") &&
|
||||
externalLibraryList.contains("opus") &&
|
||||
externalLibraryList.contains("shine") &&
|
||||
externalLibraryList.contains("sdl") &&
|
||||
externalLibraryList.contains("snappy") &&
|
||||
externalLibraryList.contains("soxr") &&
|
||||
externalLibraryList.contains("speex") &&
|
||||
externalLibraryList.contains("tesseract") &&
|
||||
externalLibraryList.contains("twolame") &&
|
||||
externalLibraryList.contains("wavpack") &&
|
||||
externalLibraryList.contains("x264") &&
|
||||
externalLibraryList.contains("x265") &&
|
||||
externalLibraryList.contains("xvid")) {
|
||||
return "full-gpl";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (full) {
|
||||
if (externalLibraryList.contains("chromaprint") &&
|
||||
externalLibraryList.contains("fontconfig") &&
|
||||
externalLibraryList.contains("freetype") &&
|
||||
externalLibraryList.contains("fribidi") &&
|
||||
externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls") &&
|
||||
externalLibraryList.contains("kvazaar") &&
|
||||
externalLibraryList.contains("mp3lame") &&
|
||||
externalLibraryList.contains("libaom") &&
|
||||
externalLibraryList.contains("libass") &&
|
||||
externalLibraryList.contains("iconv") &&
|
||||
externalLibraryList.contains("libilbc") &&
|
||||
externalLibraryList.contains("libtheora") &&
|
||||
externalLibraryList.contains("libvorbis") &&
|
||||
externalLibraryList.contains("libvpx") &&
|
||||
externalLibraryList.contains("libwebp") &&
|
||||
externalLibraryList.contains("libxml2") &&
|
||||
externalLibraryList.contains("opencore-amr") &&
|
||||
externalLibraryList.contains("opus") &&
|
||||
externalLibraryList.contains("shine") &&
|
||||
externalLibraryList.contains("sdl") &&
|
||||
externalLibraryList.contains("snappy") &&
|
||||
externalLibraryList.contains("soxr") &&
|
||||
externalLibraryList.contains("speex") &&
|
||||
externalLibraryList.contains("tesseract") &&
|
||||
externalLibraryList.contains("twolame") &&
|
||||
externalLibraryList.contains("wavpack")) {
|
||||
return "full";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (video) {
|
||||
if (externalLibraryList.contains("fontconfig") &&
|
||||
externalLibraryList.contains("freetype") &&
|
||||
externalLibraryList.contains("fribidi") &&
|
||||
externalLibraryList.contains("kvazaar") &&
|
||||
externalLibraryList.contains("libaom") &&
|
||||
externalLibraryList.contains("libass") &&
|
||||
externalLibraryList.contains("iconv") &&
|
||||
externalLibraryList.contains("libtheora") &&
|
||||
externalLibraryList.contains("libvpx") &&
|
||||
externalLibraryList.contains("libwebp") &&
|
||||
externalLibraryList.contains("snappy")) {
|
||||
return "video";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (audio) {
|
||||
if (externalLibraryList.contains("mp3lame") &&
|
||||
externalLibraryList.contains("libilbc") &&
|
||||
externalLibraryList.contains("libvorbis") &&
|
||||
externalLibraryList.contains("opencore-amr") &&
|
||||
externalLibraryList.contains("opus") &&
|
||||
externalLibraryList.contains("shine") &&
|
||||
externalLibraryList.contains("soxr") &&
|
||||
externalLibraryList.contains("speex") &&
|
||||
externalLibraryList.contains("twolame") &&
|
||||
externalLibraryList.contains("wavpack")) {
|
||||
return "audio";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (httpsGpl) {
|
||||
if (externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls") &&
|
||||
externalLibraryList.contains("libvidstab") &&
|
||||
externalLibraryList.contains("x264") &&
|
||||
externalLibraryList.contains("x265") &&
|
||||
externalLibraryList.contains("xvid")) {
|
||||
return "https-gpl";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (https) {
|
||||
if (externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls")) {
|
||||
return "https";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (minGpl) {
|
||||
if (externalLibraryList.contains("libvidstab") &&
|
||||
externalLibraryList.contains("x264") &&
|
||||
externalLibraryList.contains("x265") &&
|
||||
externalLibraryList.contains("xvid")) {
|
||||
return "min-gpl";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
return "min";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,434 @@
|
||||
/*
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Stream information class.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class StreamInformation {
|
||||
|
||||
/**
|
||||
* Stream index
|
||||
*/
|
||||
private Long index;
|
||||
|
||||
private String type;
|
||||
private String codec;
|
||||
private String fullCodec;
|
||||
private String format;
|
||||
private String fullFormat;
|
||||
|
||||
private Long width;
|
||||
private Long height;
|
||||
|
||||
private Long bitrate;
|
||||
private Long sampleRate;
|
||||
private String sampleFormat;
|
||||
private String channelLayout;
|
||||
|
||||
/**
|
||||
* SAR
|
||||
*/
|
||||
private String sampleAspectRatio;
|
||||
|
||||
/**
|
||||
* DAR
|
||||
*/
|
||||
private String displayAspectRatio;
|
||||
|
||||
/**
|
||||
* fps
|
||||
*/
|
||||
private String averageFrameRate;
|
||||
|
||||
/**
|
||||
* tbr
|
||||
*/
|
||||
private String realFrameRate;
|
||||
|
||||
/**
|
||||
* tbn
|
||||
*/
|
||||
private String timeBase;
|
||||
|
||||
/**
|
||||
* tbc
|
||||
*/
|
||||
private String codecTimeBase;
|
||||
|
||||
/**
|
||||
* Metadata map
|
||||
*/
|
||||
private final Map<String, String> metadata;
|
||||
|
||||
public StreamInformation() {
|
||||
this.metadata = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream index.
|
||||
*
|
||||
* @return stream index, starting from zero
|
||||
*/
|
||||
public Long getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets stream index.
|
||||
*
|
||||
* @param index stream index, starting from zero
|
||||
*/
|
||||
public void setIndex(Long index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream type.
|
||||
*
|
||||
* @return stream type; audio or video
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets stream type.
|
||||
*
|
||||
* @param type stream type; audio or video
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream codec.
|
||||
*
|
||||
* @return stream codec
|
||||
*/
|
||||
public String getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets stream codec.
|
||||
*
|
||||
* @param codec stream codec
|
||||
*/
|
||||
public void setCodec(String codec) {
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns full stream codec.
|
||||
*
|
||||
* @return stream codec with additional profile and mode information
|
||||
*/
|
||||
public String getFullCodec() {
|
||||
return fullCodec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets full stream codec.
|
||||
*
|
||||
* @param fullCodec stream codec with additional profile and mode information
|
||||
*/
|
||||
public void setFullCodec(String fullCodec) {
|
||||
this.fullCodec = fullCodec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream format.
|
||||
*
|
||||
* @return stream format
|
||||
*/
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets stream format.
|
||||
*
|
||||
* @param format stream format
|
||||
*/
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns full stream format.
|
||||
*
|
||||
* @return stream format with
|
||||
*/
|
||||
public String getFullFormat() {
|
||||
return fullFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets full stream format.
|
||||
*
|
||||
* @param fullFormat stream format with
|
||||
*/
|
||||
public void setFullFormat(String fullFormat) {
|
||||
this.fullFormat = fullFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns width.
|
||||
*
|
||||
* @return width in pixels
|
||||
*/
|
||||
public Long getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets width.
|
||||
*
|
||||
* @param width width in pixels
|
||||
*/
|
||||
public void setWidth(Long width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns height.
|
||||
*
|
||||
* @return height in pixels
|
||||
*/
|
||||
public Long getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets height.
|
||||
*
|
||||
* @param height height in pixels
|
||||
*/
|
||||
public void setHeight(Long height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitrate.
|
||||
*
|
||||
* @return bitrate in kb/s
|
||||
*/
|
||||
public Long getBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bitrate.
|
||||
*
|
||||
* @param bitrate bitrate in kb/s
|
||||
*/
|
||||
public void setBitrate(Long bitrate) {
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sample rate.
|
||||
*
|
||||
* @return sample rate in hz
|
||||
*/
|
||||
public Long getSampleRate() {
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sample rate.
|
||||
*
|
||||
* @param sampleRate sample rate in hz
|
||||
*/
|
||||
public void setSampleRate(Long sampleRate) {
|
||||
this.sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sample format.
|
||||
*
|
||||
* @return sample format
|
||||
*/
|
||||
public String getSampleFormat() {
|
||||
return sampleFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sample format.
|
||||
*
|
||||
* @param sampleFormat sample format
|
||||
*/
|
||||
public void setSampleFormat(String sampleFormat) {
|
||||
this.sampleFormat = sampleFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns channel layout.
|
||||
*
|
||||
* @return channel layout
|
||||
*/
|
||||
public String getChannelLayout() {
|
||||
return channelLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets channel layout.
|
||||
*
|
||||
* @param channelLayout channel layout
|
||||
*/
|
||||
public void setChannelLayout(String channelLayout) {
|
||||
this.channelLayout = channelLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sample aspect ratio.
|
||||
*
|
||||
* @return sample aspect ratio
|
||||
*/
|
||||
public String getSampleAspectRatio() {
|
||||
return sampleAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sample aspect ratio.
|
||||
*
|
||||
* @param sampleAspectRatio sample aspect ratio
|
||||
*/
|
||||
public void setSampleAspectRatio(String sampleAspectRatio) {
|
||||
this.sampleAspectRatio = sampleAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display aspect ratio.
|
||||
*
|
||||
* @return display aspect ratio
|
||||
*/
|
||||
public String getDisplayAspectRatio() {
|
||||
return displayAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets display aspect ratio.
|
||||
*
|
||||
* @param displayAspectRatio display aspect ratio
|
||||
*/
|
||||
public void setDisplayAspectRatio(String displayAspectRatio) {
|
||||
this.displayAspectRatio = displayAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display aspect ratio.
|
||||
*
|
||||
* @return average frame rate in fps
|
||||
*/
|
||||
public String getAverageFrameRate() {
|
||||
return averageFrameRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets average frame rate.
|
||||
*
|
||||
* @param averageFrameRate average frame rate in fps
|
||||
*/
|
||||
public void setAverageFrameRate(String averageFrameRate) {
|
||||
this.averageFrameRate = averageFrameRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns real frame rate.
|
||||
*
|
||||
* @return real frame rate in tbr
|
||||
*/
|
||||
public String getRealFrameRate() {
|
||||
return realFrameRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets real frame rate.
|
||||
*
|
||||
* @param realFrameRate real frame rate in tbr
|
||||
*/
|
||||
public void setRealFrameRate(String realFrameRate) {
|
||||
this.realFrameRate = realFrameRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time base.
|
||||
*
|
||||
* @return time base in tbn
|
||||
*/
|
||||
public String getTimeBase() {
|
||||
return timeBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets time base.
|
||||
*
|
||||
* @param timeBase time base in tbn
|
||||
*/
|
||||
public void setTimeBase(String timeBase) {
|
||||
this.timeBase = timeBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns codec time base.
|
||||
*
|
||||
* @return codec time base in tbc
|
||||
*/
|
||||
public String getCodecTimeBase() {
|
||||
return codecTimeBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets codec time base.
|
||||
*
|
||||
* @param codecTimeBase codec time base in tbc
|
||||
*/
|
||||
public void setCodecTimeBase(String codecTimeBase) {
|
||||
this.codecTimeBase = codecTimeBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds metadata.
|
||||
*
|
||||
* @param key metadata key
|
||||
* @param value metadata value
|
||||
*/
|
||||
public void addMetadata(String key, String value) {
|
||||
this.metadata.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all metadata entries.
|
||||
*
|
||||
* @return set of metadata entries
|
||||
*/
|
||||
public Set<Map.Entry<String, String>> getMetadataEntries() {
|
||||
return this.metadata.entrySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class Pair<A, B> {
|
||||
|
||||
/**
|
||||
* The first element of the pair.
|
||||
*/
|
||||
protected A first;
|
||||
|
||||
/**
|
||||
* The second element of the pair.
|
||||
*/
|
||||
protected B second;
|
||||
|
||||
public Pair(final A first, final B second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public A getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public B getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class Trio<A, B, C> {
|
||||
|
||||
/**
|
||||
* The first element of the trio.
|
||||
*/
|
||||
protected final A first;
|
||||
|
||||
/**
|
||||
* The second element of the trio.
|
||||
*/
|
||||
protected final B second;
|
||||
|
||||
/**
|
||||
* The third element of the trio.
|
||||
*/
|
||||
protected final C third;
|
||||
|
||||
public Trio(final A first, final B second, final C third) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
this.third = third;
|
||||
}
|
||||
|
||||
public A getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public B getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public C getThird() {
|
||||
return third;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 |
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Tests for {@link Config} class.
|
||||
*/
|
||||
public class ConfigTest {
|
||||
|
||||
private static final String externalLibrariesCommandOutput = " configuration:\n" +
|
||||
" --cross-prefix=i686-linux-android-\n" +
|
||||
" --sysroot=/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/mobile-ffmpeg-i686/sysroot\n" +
|
||||
" --prefix=/Users/taner/Projects/mobile-ffmpeg/prebuilt/android-x86/ffmpeg\n" +
|
||||
" --pkg-config=/usr/local/bin/pkg-config --extra-cflags='-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -Wno-unused-function -fstrict-aliasing -fPIC -DANDROID -D__ANDROID__ -D__ANDROID_API__=21 -O2 -I/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/mobile-ffmpeg-i686/sysroot/usr/include -I/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/mobile-ffmpeg-i686/sysroot/usr/local/include'\n" +
|
||||
" --extra-cxxflags='-std=c++11 -fno-exceptions -fno-rtti'\n" +
|
||||
" --extra-ldflags='-march=i686 -Wl,--gc-sections,--icf=safe -lc -lm -ldl -llog -lc++_shared -L/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/mobile-ffmpeg-i686/i686-linux-android/lib -L/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/mobile-ffmpeg-i686/sysroot/usr/lib -L/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/mobile-ffmpeg-i686/lib -L/Users/taner/Library/Android/sdk/ndk-bundle/platforms/android-21/arch-x86/usr/lib'\n" +
|
||||
" --enable-version3\n" +
|
||||
" --arch=i686\n" +
|
||||
" --cpu=i686\n" +
|
||||
" --target-os=android\n" +
|
||||
" --disable-neon\n" +
|
||||
" --disable-asm\n" +
|
||||
" --disable-inline-asm\n" +
|
||||
" --enable-cross-compile\n" +
|
||||
" --enable-pic\n" +
|
||||
" --enable-jni\n" +
|
||||
" --enable-libvorbis\n" +
|
||||
" --enable-optimizations\n" +
|
||||
" --enable-swscale\n" +
|
||||
" --enable-shared\n" +
|
||||
" --enable-v4l2-m2m\n" +
|
||||
" --enable-small\n" +
|
||||
" --disable-openssl\n" +
|
||||
" --disable-xmm-clobber-test\n" +
|
||||
" --disable-debug\n" +
|
||||
" --disable-neon-clobber-test\n" +
|
||||
" --disable-programs\n" +
|
||||
" --disable-postproc\n" +
|
||||
" --disable-doc\n" +
|
||||
" --disable-htmlpages\n" +
|
||||
" --disable-manpages\n" +
|
||||
" --disable-podpages\n" +
|
||||
" --disable-txtpages\n" +
|
||||
" --disable-static\n" +
|
||||
" --disable-sndio\n" +
|
||||
" --disable-schannel\n" +
|
||||
" --disable-securetransport\n" +
|
||||
" --disable-xlib\n" +
|
||||
" --disable-cuda\n" +
|
||||
" --disable-cuvid\n" +
|
||||
" --disable-nvenc\n" +
|
||||
" --disable-vaapi\n" +
|
||||
" --disable-vdpau\n" +
|
||||
" --disable-videotoolbox\n" +
|
||||
" --disable-audiotoolbox\n" +
|
||||
" --disable-appkit\n" +
|
||||
" --disable-alsa\n" +
|
||||
" --disable-cuda\n" +
|
||||
" --disable-cuvid\n" +
|
||||
" --disable-nvenc\n" +
|
||||
" --disable-vaapi\n" +
|
||||
" --disable-vdpau\n" +
|
||||
" --disable-zlib\n";
|
||||
|
||||
@Test
|
||||
public void getExternalLibraries() {
|
||||
|
||||
final List<String> supportedExternalLibraries = new ArrayList<>();
|
||||
supportedExternalLibraries.add("chromaprint");
|
||||
supportedExternalLibraries.add("fontconfig");
|
||||
supportedExternalLibraries.add("freetype");
|
||||
supportedExternalLibraries.add("fribidi");
|
||||
supportedExternalLibraries.add("gmp");
|
||||
supportedExternalLibraries.add("gnutls");
|
||||
supportedExternalLibraries.add("kvazaar");
|
||||
supportedExternalLibraries.add("lame");
|
||||
supportedExternalLibraries.add("libaom");
|
||||
supportedExternalLibraries.add("libass");
|
||||
supportedExternalLibraries.add("libiconv");
|
||||
supportedExternalLibraries.add("libilbc");
|
||||
supportedExternalLibraries.add("libtheora");
|
||||
supportedExternalLibraries.add("libvidstab");
|
||||
supportedExternalLibraries.add("libvorbis");
|
||||
supportedExternalLibraries.add("libvpx");
|
||||
supportedExternalLibraries.add("libwebp");
|
||||
supportedExternalLibraries.add("libxml2");
|
||||
supportedExternalLibraries.add("opencore-amr");
|
||||
supportedExternalLibraries.add("opus");
|
||||
supportedExternalLibraries.add("shine");
|
||||
supportedExternalLibraries.add("sdl");
|
||||
supportedExternalLibraries.add("snappy");
|
||||
supportedExternalLibraries.add("soxr");
|
||||
supportedExternalLibraries.add("speex");
|
||||
supportedExternalLibraries.add("tesseract");
|
||||
supportedExternalLibraries.add("twolame");
|
||||
supportedExternalLibraries.add("wavpack");
|
||||
supportedExternalLibraries.add("x264");
|
||||
supportedExternalLibraries.add("x265");
|
||||
supportedExternalLibraries.add("xvidcore");
|
||||
supportedExternalLibraries.add("android-zlib");
|
||||
supportedExternalLibraries.add("android-media-codec");
|
||||
|
||||
|
||||
final List<String> enabledList = new ArrayList<>();
|
||||
for (String supportedExternalLibrary : supportedExternalLibraries) {
|
||||
if (externalLibrariesCommandOutput.contains("enable-" + supportedExternalLibrary) ||
|
||||
externalLibrariesCommandOutput.contains("enable-lib" + supportedExternalLibrary)) {
|
||||
enabledList.add(supportedExternalLibrary);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(enabledList);
|
||||
|
||||
Assert.assertNotNull(enabledList);
|
||||
Assert.assertTrue(enabledList.size() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPackageName() {
|
||||
Assert.assertEquals("min", listToPackageName(Collections.singletonList("")));
|
||||
Assert.assertEquals("min-gpl", listToPackageName(Collections.singletonList("xvidcore")));
|
||||
Assert.assertEquals("full-gpl", listToPackageName(Arrays.asList("gnutls", "speex", "fribidi", "xvidcore")));
|
||||
Assert.assertEquals("full", listToPackageName(Arrays.asList("fribidi", "speex")));
|
||||
Assert.assertEquals("video", listToPackageName(Collections.singletonList("fribidi")));
|
||||
Assert.assertEquals("audio", listToPackageName(Collections.singletonList("speex")));
|
||||
Assert.assertEquals("https", listToPackageName(Collections.singletonList("gnutls")));
|
||||
Assert.assertEquals("https-gpl", listToPackageName(Arrays.asList("gnutls", "xvidcore")));
|
||||
}
|
||||
|
||||
private String listToPackageName(final List<String> externalLibraryList) {
|
||||
boolean speex = externalLibraryList.contains("speex");
|
||||
boolean fribidi = externalLibraryList.contains("fribidi");
|
||||
boolean gnutls = externalLibraryList.contains("gnutls");
|
||||
boolean xvidcore = externalLibraryList.contains("xvidcore");
|
||||
|
||||
if (speex && fribidi) {
|
||||
if (xvidcore) {
|
||||
return "full-gpl";
|
||||
} else {
|
||||
return "full";
|
||||
}
|
||||
} else if (speex) {
|
||||
return "audio";
|
||||
} else if (fribidi) {
|
||||
return "video";
|
||||
} else if (xvidcore) {
|
||||
if (gnutls) {
|
||||
return "https-gpl";
|
||||
} else {
|
||||
return "min-gpl";
|
||||
}
|
||||
} else {
|
||||
if (gnutls) {
|
||||
return "https";
|
||||
} else {
|
||||
return "min";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +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 org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
/*
|
||||
* 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 org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* <p>Tests for {@link FFmpeg} class.
|
||||
*/
|
||||
public class FFmpegTest {
|
||||
|
||||
private static final String MEDIA_INFORMATION_MP3 =
|
||||
"Unknown attached picture mimetype: audio/x-wav, skipping.\n" +
|
||||
"[mp3 @ 0x7ffb94805800] Estimating duration from bitrate, this may be inaccurate\n" +
|
||||
"Input #0, mp3, from 'beethoven_-_symphony_no_9.mp3':\n" +
|
||||
" Metadata:\n" +
|
||||
" comment : \n" +
|
||||
" album : Symphony No.9\n" +
|
||||
" compilation : 0\n" +
|
||||
" date : -1\n" +
|
||||
" title : Symphony No.9\n" +
|
||||
" artist : Beethoven\n" +
|
||||
" album_artist : Beethoven\n" +
|
||||
" track : -1\n" +
|
||||
" lyrics-XXX : \n" +
|
||||
" Duration: 00:03:33.24, start: 0.000000, bitrate: 320 kb/s\n" +
|
||||
" Stream #0:0: Audio: mp3, 48000 Hz, stereo, fltp, 320 kb/s\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (mp3 (mp3float) -> pcm_s16le (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" comment : \n" +
|
||||
" album : Symphony No.9\n" +
|
||||
" compilation : 0\n" +
|
||||
" date : -1\n" +
|
||||
" title : Symphony No.9\n" +
|
||||
" artist : Beethoven\n" +
|
||||
" album_artist : Beethoven\n" +
|
||||
" track : -1\n" +
|
||||
" lyrics-XXX : \n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0: Audio: pcm_s16le, 48000 Hz, stereo, s16, 1536 kb/s\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavc58.18.100 pcm_s16le\n" +
|
||||
"size=N/A time=00:03:33.24 bitrate=N/A speed= 618x \n" +
|
||||
"video:0kB audio:39982kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown";
|
||||
|
||||
private static final String MEDIA_INFORMATION_JPG =
|
||||
"Input #0, image2, from '/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg':\n" +
|
||||
" Duration: 00:00:00.04, start: 0.000000, bitrate: 391187 kb/s\n" +
|
||||
" Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 2560x1708 [SAR 1:1 DAR 640:427], 25 tbr, 25 tbn, 25 tbc\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (mjpeg (native) -> wrapped_avframe (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavf58.18.104\n" +
|
||||
" Stream #0:0: Video: wrapped_avframe, yuvj420p, 2560x1708 [SAR 1:1 DAR 640:427], q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavc58.31.102 wrapped_avframe\n" +
|
||||
"frame= 1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.04 bitrate=N/A speed=0.668x \n" +
|
||||
"video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown\n";
|
||||
|
||||
private static final String MEDIA_INFORMATION_GIF =
|
||||
"Input #0, gif, from 'advanced_zoom_in_and_pan_with_fade_in_out.gif':\n" +
|
||||
" Duration: N/A, bitrate: N/A\n" +
|
||||
" Stream #0:0: Video: gif, bgra, 420x236, 6 fps, 6 tbr, 100 tbn, 100 tbc\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (gif (native) -> wrapped_avframe (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0: Video: wrapped_avframe, bgra, 420x236 [SAR 63:64 DAR 6615:3776], q=2-31, 200 kb/s, 6 fps, 6 tbn, 6 tbc\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavc58.18.100 wrapped_avframe\n" +
|
||||
"frame= 61 fps=0.0 q=-0.0 Lsize=N/A time=00:00:10.16 bitrate=N/A speed= 219x \n" +
|
||||
"video:32kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown";
|
||||
|
||||
private static final String MEDIA_INFORMATION_H264 =
|
||||
"Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'transition_rotate.mp4':\n" +
|
||||
" Metadata:\n" +
|
||||
" major_brand : isom\n" +
|
||||
" minor_version : 512\n" +
|
||||
" compatible_brands: isomiso2avc1mp41\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Duration: 00:00:15.00, start: 0.000000, bitrate: 7764 kb/s\n" +
|
||||
" Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 7762 kb/s, 25 fps, 30 tbr, 15360 tbn, 60 tbc (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" handler_name : VideoHandler\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" major_brand : isom\n" +
|
||||
" minor_version : 512\n" +
|
||||
" compatible_brands: isomiso2avc1mp41\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0(und): Video: wrapped_avframe, yuv420p, 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" handler_name : VideoHandler\n" +
|
||||
" encoder : Lavc58.18.100 wrapped_avframe\n" +
|
||||
"frame= 375 fps=0.0 q=-0.0 Lsize=N/A time=00:00:15.00 bitrate=N/A speed=35.9x \n" +
|
||||
"video:196kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown";
|
||||
|
||||
private static final String MEDIA_INFORMATION_PNG =
|
||||
"Input #0, png_pipe, from 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png':\n" +
|
||||
" Duration: N/A, bitrate: N/A\n" +
|
||||
" Stream #0:0: Video: png, rgba(pc), 544x184, 25 tbr, 25 tbn, 25 tbc\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (png (native) -> wrapped_avframe (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0: Video: wrapped_avframe, rgba, 544x184, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavc58.18.100 wrapped_avframe\n" +
|
||||
"frame= 1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.04 bitrate=N/A speed=27.1x \n" +
|
||||
"video:1kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown";
|
||||
|
||||
private static final String MEDIA_INFORMATION_H264_2 =
|
||||
"Input #0, h264, from 'test.h264':\n" +
|
||||
" Duration: N/A, bitrate: N/A\n" +
|
||||
" Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 25 fps, 25 tbr, 1200k tbn, 50 tbc\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0: Video: wrapped_avframe, yuv420p, 1920x1080, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavc58.18.100 wrapped_avframe\n" +
|
||||
"frame= 360 fps=0.0 q=-0.0 Lsize=N/A time=00:00:14.40 bitrate=N/A speed=25.5x \n" +
|
||||
"video:188kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown";
|
||||
|
||||
private static final String MEDIA_INFORMATION_MP4 =
|
||||
"Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_2160p_30fps_stereo_abl.mp4':\n" +
|
||||
" Metadata:\n" +
|
||||
" major_brand : isom\n" +
|
||||
" minor_version : 1\n" +
|
||||
" compatible_brands: isomavc1\n" +
|
||||
" creation_time : 2013-12-16T17:21:55.000000Z\n" +
|
||||
" title : Big Buck Bunny, Sunflower version\n" +
|
||||
" artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" +
|
||||
" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" +
|
||||
" genre : Animation\n" +
|
||||
" composer : Sacha Goedegebure\n" +
|
||||
" Duration: 00:10:34.53, start: 0.000000, bitrate: 10385 kb/s\n" +
|
||||
" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 3840x4320 [SAR 1:1 DAR 8:9], 9902 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:21:55.000000Z\n" +
|
||||
" handler_name : GPAC ISO Video Handler\n" +
|
||||
" Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:21:58.000000Z\n" +
|
||||
" handler_name : GPAC ISO Audio Handler\n" +
|
||||
" Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:21:58.000000Z\n" +
|
||||
" handler_name : GPAC ISO Audio Handler\n" +
|
||||
" Side data:\n" +
|
||||
" audio service type: main\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" +
|
||||
" Stream #0:2 -> #0:1 (ac3 (native) -> pcm_s16le (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" major_brand : isom\n" +
|
||||
" minor_version : 1\n" +
|
||||
" compatible_brands: isomavc1\n" +
|
||||
" composer : Sacha Goedegebure\n" +
|
||||
" title : Big Buck Bunny, Sunflower version\n" +
|
||||
" artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" +
|
||||
" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" +
|
||||
" genre : Animation\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0(und): Video: wrapped_avframe, yuv420p(progressive), 3840x4320 [SAR 1:1 DAR 8:9], q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:21:55.000000Z\n" +
|
||||
" handler_name : GPAC ISO Video Handler\n" +
|
||||
" encoder : Lavc58.18.100 wrapped_avframe\n" +
|
||||
" Stream #0:1(und): Audio: pcm_s16le, 48000 Hz, 5.1(side), s16, 4608 kb/s (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:21:58.000000Z\n" +
|
||||
" handler_name : GPAC ISO Audio Handler\n" +
|
||||
" encoder : Lavc58.18.100 pcm_s16le\n" +
|
||||
" Side data:\n" +
|
||||
" audio service type: main\n" +
|
||||
"frame= 2798 fps= 85 q=-0.0 size=N/A time=00:01:33.33 bitrate=N/A speed=2.85x \n";
|
||||
|
||||
private static final String MEDIA_INFORMATION_MP4_2 =
|
||||
"Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_stereo_arcd.mp4':\n" +
|
||||
" Metadata:\n" +
|
||||
" major_brand : isom\n" +
|
||||
" minor_version : 1\n" +
|
||||
" compatible_brands: isomavc1\n" +
|
||||
" creation_time : 2013-12-16T17:49:59.000000Z\n" +
|
||||
" title : Big Buck Bunny, Sunflower version\n" +
|
||||
" artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" +
|
||||
" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" +
|
||||
" genre : Animation\n" +
|
||||
" composer : Sacha Goedegebure\n" +
|
||||
" Duration: 00:10:34.53, start: 0.000000, bitrate: 4474 kb/s\n" +
|
||||
" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3992 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:49:59.000000Z\n" +
|
||||
" handler_name : GPAC ISO Video Handler\n" +
|
||||
" Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:50:04.000000Z\n" +
|
||||
" handler_name : GPAC ISO Audio Handler\n" +
|
||||
" Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:50:04.000000Z\n" +
|
||||
" handler_name : GPAC ISO Audio Handler\n" +
|
||||
" Side data:\n" +
|
||||
" audio service type: main\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" +
|
||||
" Stream #0:2 -> #0:1 (ac3 (native) -> pcm_s16le (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" major_brand : isom\n" +
|
||||
" minor_version : 1\n" +
|
||||
" compatible_brands: isomavc1\n" +
|
||||
" composer : Sacha Goedegebure\n" +
|
||||
" title : Big Buck Bunny, Sunflower version\n" +
|
||||
" artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" +
|
||||
" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" +
|
||||
" genre : Animation\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0(und): Video: wrapped_avframe, yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:49:59.000000Z\n" +
|
||||
" handler_name : GPAC ISO Video Handler\n" +
|
||||
" encoder : Lavc58.18.100 wrapped_avframe\n" +
|
||||
" Stream #0:1(und): Audio: pcm_s16le, 48000 Hz, 5.1(side), s16, 4608 kb/s (default)\n" +
|
||||
" Metadata:\n" +
|
||||
" creation_time : 2013-12-16T17:50:04.000000Z\n" +
|
||||
" handler_name : GPAC ISO Audio Handler\n" +
|
||||
" encoder : Lavc58.18.100 pcm_s16le\n" +
|
||||
" Side data:\n" +
|
||||
" audio service type: main\n" +
|
||||
"frame=19036 fps=401 q=-0.0 Lsize=N/A time=00:10:34.60 bitrate=N/A speed=13.4x \n" +
|
||||
"video:9964kB audio:356706kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown";
|
||||
|
||||
private static final String MEDIA_INFORMATION_OGG =
|
||||
"[theora @ 0x7fa30c026e00] 7 bits left in packet 82\n" +
|
||||
"[ogg @ 0x7fa30c005e00] Broken file, keyframe not correctly marked.\n" +
|
||||
"Input #0, ogg, from 'trailer_400p.ogg':\n" +
|
||||
" Duration: 00:00:33.00, start: 0.000000, bitrate: 1057 kb/s\n" +
|
||||
" Stream #0:0: Video: theora, yuv420p(bt470bg/bt470bg/bt709), 720x400, 25 fps, 25 tbr, 25 tbn, 25 tbc\n" +
|
||||
" Metadata:\n" +
|
||||
" ENCODER : ffmpeg2theora 0.19\n" +
|
||||
" Stream #0:1: Audio: vorbis, 48000 Hz, stereo, fltp, 80 kb/s\n" +
|
||||
" Metadata:\n" +
|
||||
" ENCODER : ffmpeg2theora 0.19\n" +
|
||||
"[theora @ 0x7fa30c1bd600] 7 bits left in packet 82\n" +
|
||||
"Stream mapping:\n" +
|
||||
" Stream #0:0 -> #0:0 (theora (native) -> wrapped_avframe (native))\n" +
|
||||
" Stream #0:1 -> #0:1 (vorbis (native) -> pcm_s16le (native))\n" +
|
||||
"Press [q] to stop, [?] for help\n" +
|
||||
"Output #0, null, to 'pipe:':\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavf58.12.100\n" +
|
||||
" Stream #0:0: Video: wrapped_avframe, yuv420p(progressive), 720x400, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavc58.18.100 wrapped_avframe\n" +
|
||||
" Stream #0:1: Audio: pcm_s16le, 48000 Hz, stereo, s16, 1536 kb/s\n" +
|
||||
" Metadata:\n" +
|
||||
" encoder : Lavc58.18.100 pcm_s16le\n" +
|
||||
"[ogg @ 0x7fa30c005e00] Broken file, keyframe not correctly marked.\n" +
|
||||
" Last message repeated 5 times\n" +
|
||||
"frame= 813 fps=0.0 q=-0.0 Lsize=N/A time=00:00:33.01 bitrate=N/A speed= 234x \n" +
|
||||
"video:426kB audio:6190kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown";
|
||||
|
||||
@Test
|
||||
public void mediaInformationMp3() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_MP3);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "mp3", "beethoven_-_symphony_no_9.mp3");
|
||||
assertMediaDuration(mediaInformation, 213240L, 0L, 320L);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertAudioStream(mediaInformation.getStreams().get(0), 0L, "mp3", "mp3", 48000L, "stereo", "fltp", 320L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationJpg() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_JPG);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "image2", "/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg");
|
||||
assertMediaDuration(mediaInformation, 40L, 0L, 391187L);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "mjpeg", "mjpeg", "yuvj420p", "yuvj420p(pc, bt470bg/unknown/unknown)", 2560L, 1708L, "1:1", "640:427", null, null, "25", "25", "25");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationGif() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_GIF);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "gif", "advanced_zoom_in_and_pan_with_fade_in_out.gif");
|
||||
assertMediaDuration(mediaInformation, null, null, null);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "gif", "gif", "bgra", "bgra", 420L, 236L, null, null, null, "6", "6", "100", "100");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationH264() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_H264);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "mov,mp4,m4a,3gp,3g2,mj2", "transition_rotate.mp4");
|
||||
assertMediaDuration(mediaInformation, 15000L, 0L, 7764L);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "h264", "h264 (main) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1280L, 720L, "1:1", "16:9", 7762L, "25", "30", "15360", "60");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationPng() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_PNG);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "png_pipe", "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png");
|
||||
assertMediaDuration(mediaInformation, null, null, null);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "png", "png", "rgba", "rgba(pc)", 544L, 184L, null, null, null, null, "25", "25", "25");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationH2642() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_H264_2);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "h264", "test.h264");
|
||||
assertMediaDuration(mediaInformation, null, null, null);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "h264", "h264 (main)", "yuv420p", "yuv420p(tv, bt709, progressive)", 1920L, 1080L, null, null, null, "25", "25", "1200k", "50");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationMp4() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_MP4);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "mov,mp4,m4a,3gp,3g2,mj2", "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_2160p_30fps_stereo_abl.mp4");
|
||||
assertMediaDuration(mediaInformation, 634530L, 0L, 10385L);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(3, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 3840L, 4320L, "1:1", "8:9", 9902L, "30", "30", "30k", "60");
|
||||
assertAudioStream(mediaInformation.getStreams().get(1), 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
|
||||
assertAudioStream(mediaInformation.getStreams().get(2), 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationMp42() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_MP4_2);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "mov,mp4,m4a,3gp,3g2,mj2", "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_stereo_arcd.mp4");
|
||||
assertMediaDuration(mediaInformation, 634530L, 0L, 4474L);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(3, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1920L, 1080L, "1:1", "16:9", 3992L, "30", "30", "30k", "60");
|
||||
assertAudioStream(mediaInformation.getStreams().get(1), 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
|
||||
assertAudioStream(mediaInformation.getStreams().get(2), 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationOgg() {
|
||||
MediaInformation mediaInformation = MediaInformationParser.from(MEDIA_INFORMATION_OGG);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "ogg", "trailer_400p.ogg");
|
||||
assertMediaDuration(mediaInformation, 33000L, 0L, 1057L);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(2, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "theora", "theora", "yuv420p", "yuv420p(bt470bg/bt470bg/bt709)", 720L, 400L, null, null, null, "25", "25", "25", "25");
|
||||
assertAudioStream(mediaInformation.getStreams().get(1), 1L, "vorbis", "vorbis", 48000L, "stereo", "fltp", 80L);
|
||||
}
|
||||
|
||||
private void assertMediaInput(MediaInformation mediaInformation, String format, String path) {
|
||||
Assert.assertEquals(format, mediaInformation.getFormat());
|
||||
Assert.assertEquals(path, mediaInformation.getPath());
|
||||
}
|
||||
|
||||
private void assertMediaDuration(MediaInformation mediaInformation, Long duration, Long startTime, Long bitrate) {
|
||||
Assert.assertEquals(duration, mediaInformation.getDuration());
|
||||
Assert.assertEquals(startTime, mediaInformation.getStartTime());
|
||||
Assert.assertEquals(bitrate, mediaInformation.getBitrate());
|
||||
}
|
||||
|
||||
private void assertAudioStream(StreamInformation streamInformation, Long index, String codec, String fullCodec, Long sampleRate, String channelLayout, String sampleFormat, Long bitrate) {
|
||||
Assert.assertEquals(index, streamInformation.getIndex());
|
||||
Assert.assertEquals("audio", streamInformation.getType());
|
||||
Assert.assertEquals(codec, streamInformation.getCodec());
|
||||
Assert.assertEquals(fullCodec, streamInformation.getFullCodec());
|
||||
Assert.assertEquals(sampleRate, streamInformation.getSampleRate());
|
||||
Assert.assertEquals(channelLayout, streamInformation.getChannelLayout());
|
||||
Assert.assertEquals(sampleFormat, streamInformation.getSampleFormat());
|
||||
Assert.assertEquals(bitrate, streamInformation.getBitrate());
|
||||
}
|
||||
|
||||
private void assertVideoStream(StreamInformation streamInformation, Long index, String codec, String fullCodec, String format, String fullFormat, Long width, Long height, String sar, String dar, Long bitrate, String averageFrameRate, String realFrameRate, String timeBase, String codecTimeBase) {
|
||||
Assert.assertEquals(index, streamInformation.getIndex());
|
||||
Assert.assertEquals("video", streamInformation.getType());
|
||||
Assert.assertEquals(codec, streamInformation.getCodec());
|
||||
Assert.assertEquals(fullCodec, streamInformation.getFullCodec());
|
||||
|
||||
Assert.assertEquals(format, streamInformation.getFormat());
|
||||
Assert.assertEquals(fullFormat, streamInformation.getFullFormat());
|
||||
|
||||
Assert.assertEquals(width, streamInformation.getWidth());
|
||||
Assert.assertEquals(height, streamInformation.getHeight());
|
||||
Assert.assertEquals(sar, streamInformation.getSampleAspectRatio());
|
||||
Assert.assertEquals(dar, streamInformation.getDisplayAspectRatio());
|
||||
|
||||
Assert.assertEquals(bitrate, streamInformation.getBitrate());
|
||||
|
||||
Assert.assertEquals(averageFrameRate, streamInformation.getAverageFrameRate());
|
||||
Assert.assertEquals(realFrameRate, streamInformation.getRealFrameRate());
|
||||
Assert.assertEquals(timeBase, streamInformation.getTimeBase());
|
||||
Assert.assertEquals(codecTimeBase, streamInformation.getCodecTimeBase());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Taner Sener
|
||||
*
|
||||
* This file is part of MobileFFmpeg.
|
||||
*
|
||||
* MobileFFmpeg is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MobileFFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.mobileffmpeg;
|
||||
|
||||
import com.arthenica.mobileffmpeg.util.Pair;
|
||||
import com.arthenica.mobileffmpeg.util.Trio;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class MediaInformationTest {
|
||||
|
||||
@Test
|
||||
public void parseVideoStream() {
|
||||
parseVideoStreamBlock(" Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 2560x1708 [SAR 1:1 DAR 640:427], 25 tbr, 25 tbn, 25 tbc", 0L, "mjpeg", "mjpeg", "yuvj420p", "yuvj420p(pc, bt470bg/unknown/unknown)", 2560L, 1708L, "1:1", "640:427", null, null, "25", "25", "25");
|
||||
parseVideoStreamBlock(" Stream #0:0: Video: gif, bgra, 420x236, 6 fps, 6 tbr, 100 tbn, 100 tbc", 0L, "gif",
|
||||
"gif", "bgra", "bgra", 420L, 236L, null, null, null, "6", "6", "100", "100");
|
||||
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 7762 kb/s, 25 fps, 30 tbr, 15360 tbn, 60 tbc (default)", 0L, "h264", "h264 (main) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1280L, 720L, "1:1", "16:9", 7762L, "25", "30", "15360", "60");
|
||||
parseVideoStreamBlock(" Stream #0:0: Video: png, rgba(pc), 544x184, 25 tbr, 25 tbn, 25 tbc", 0L, "png", "png", "rgba", "rgba(pc)", 544L, 184L, null, null, null, null, "25", "25", "25");
|
||||
parseVideoStreamBlock(" Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 25 fps, 25 tbr, 1200k tbn, 50 tbc", 0L, "h264", "h264 (main)", "yuv420p", "yuv420p(tv, bt709, progressive)", 1920L, 1080L, null, null, null, "25", "25", "1200k", "50");
|
||||
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 3840x4320 [SAR 1:1 DAR 8:9], 9902 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 3840L, 4320L, "1:1", "8:9", 9902L, "30", "30", "30k", "60");
|
||||
parseVideoStreamBlock(" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3992 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", 0L, "h264", "h264 (high) (avc1 / 0x31637661)", "yuv420p", "yuv420p", 1920L, 1080L, "1:1", "16:9", 3992L, "30", "30", "30k", "60");
|
||||
parseVideoStreamBlock(" Stream #0:0: Video: theora, yuv420p(bt470bg/bt470bg/bt709), 720x400, 25 fps, 25 tbr, 25 tbn, 25 tbc", 0L, "theora", "theora", "yuv420p", "yuv420p(bt470bg/bt470bg/bt709)", 720L, 400L, null, null, null, "25", "25", "25", "25");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAudioStream() {
|
||||
parseAudioStreamBlock("Stream #0:0: Audio: adpcm_ms ([2][0][0][0] / 0x0002), 22050 Hz, stereo, s16, 176 kb/s", 0L, "adpcm_ms", "adpcm_ms ([2][0][0][0] / 0x0002)", 22050L, "stereo", "s16", 176L);
|
||||
parseAudioStreamBlock("Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, mono, s16, 705 kb/s", 0L, "pcm_s16le", "pcm_s16le ([1][0][0][0] / 0x0001)", 44100L, "mono", "s16", 705L);
|
||||
parseAudioStreamBlock("Stream #0:0: Audio: pcm_s24le ([1][0][0][0] / 0x0001), 48000 Hz, mono, s32 (24 bit), 1152 kb/s", 0L, "pcm_s24le", "pcm_s24le ([1][0][0][0] / 0x0001)", 48000L, "mono", "s32 (24 bit)", 1152L);
|
||||
parseAudioStreamBlock("Stream #0:0: Audio: mp3, 48000 Hz, stereo, fltp, 192 kb/s", 0L, "mp3", "mp3", 48000L, "stereo", "fltp", 192L);
|
||||
parseAudioStreamBlock("Stream #0:0: Audio: vorbis, 44100 Hz, stereo, fltp, 128 kb/s", 0L, "vorbis", "vorbis", 44100L, "stereo", "fltp", 128L);
|
||||
parseAudioStreamBlock("Stream #0:0: Audio: pcm_u8 ([1][0][0][0] / 0x0001), 44100 Hz, stereo, u8, 705 kb/s", 0L, "pcm_u8", "pcm_u8 ([1][0][0][0] / 0x0001)", 44100L, "stereo", "u8", 705L);
|
||||
parseAudioStreamBlock("Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
|
||||
parseAudioStreamBlock("Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
|
||||
parseAudioStreamBlock("Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", 1L, "mp3", "mp3 (mp4a / 0x6134706d)", 48000L, "stereo", "fltp", 160L);
|
||||
parseAudioStreamBlock("Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", 2L, "ac3", "ac3 (ac-3 / 0x332d6361)", 48000L, "5.1(side)", "fltp", 320L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void index() {
|
||||
Assert.assertEquals(7, MediaInformationParser.index("one:two:three:", ":", 0, 2));
|
||||
Assert.assertEquals(13, MediaInformationParser.index("one:two:three:", ":", 0, 3));
|
||||
Assert.assertEquals(8, MediaInformationParser.index("one::two::three::", "::", 0, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void count() {
|
||||
Assert.assertEquals(3, MediaInformationParser.count("one:two:three:", ":"));
|
||||
Assert.assertEquals(2, MediaInformationParser.count("one,two:three,four", ","));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAverageFrameRate() {
|
||||
Assert.assertEquals("24", "24 fps".replaceAll("fps", "").trim());
|
||||
Assert.assertEquals("30", "30 fps".replaceAll("fps", "").trim());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBitrate() {
|
||||
Assert.assertEquals((Long) 3992L, MediaInformationParser.toLongObject("3992 kb/s".replaceAll("kb/s", "").trim()));
|
||||
Assert.assertEquals((Long) 7762L, MediaInformationParser.toLongObject("7762 kb/s".replaceAll("kb/s", "").trim()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseVideoStreamDisplayAspectRatio() {
|
||||
Assert.assertEquals(null, MediaInformationParser.parseVideoStreamDisplayAspectRatio(""));
|
||||
Assert.assertEquals(null, MediaInformationParser.parseVideoStreamDisplayAspectRatio("544x184"));
|
||||
Assert.assertEquals("640:427", MediaInformationParser.parseVideoStreamDisplayAspectRatio("2560x1708 [SAR 1:1 DAR 640:427]"));
|
||||
Assert.assertEquals("8:9", MediaInformationParser.parseVideoStreamDisplayAspectRatio("3840x4320 [SAR 1:1 DAR 8:9]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseVideoStreamSampleAspectRatio() {
|
||||
Assert.assertEquals(null, MediaInformationParser.parseVideoStreamSampleAspectRatio(""));
|
||||
Assert.assertEquals(null, MediaInformationParser.parseVideoStreamSampleAspectRatio("544x184"));
|
||||
Assert.assertEquals("1:1", MediaInformationParser.parseVideoStreamSampleAspectRatio("2560x1708 [SAR 1:1 DAR 640:427]"));
|
||||
Assert.assertEquals("1:1", MediaInformationParser.parseVideoStreamSampleAspectRatio("3840x4320 [SAR 1:1 DAR 8:9]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseVideoDimensions() {
|
||||
parseVideoDimensions("", null, null);
|
||||
parseVideoDimensions("544x184", 544L, 184L);
|
||||
parseVideoDimensions("720x400", 720L, 400L);
|
||||
parseVideoDimensions("2560x1708 [SAR 1:1 DAR 640:427]", 2560L, 1708L);
|
||||
parseVideoDimensions("3840x4320 [SAR 1:1 DAR 8:9]", 3840L, 4320L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAudioSampleRate() {
|
||||
Assert.assertEquals((Long) 44000L, MediaInformationParser.parseAudioStreamSampleRate("44000"));
|
||||
Assert.assertEquals((Long) 44000L, MediaInformationParser.parseAudioStreamSampleRate("44 khz"));
|
||||
Assert.assertEquals((Long) 44100L, MediaInformationParser.parseAudioStreamSampleRate("44100"));
|
||||
Assert.assertEquals((Long) 5000000L, MediaInformationParser.parseAudioStreamSampleRate("5 mhz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAudioStreamType() {
|
||||
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: theora"));
|
||||
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: png"));
|
||||
Assert.assertEquals("video", MediaInformationParser.parseStreamType("Video: h264 (Main)"));
|
||||
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
|
||||
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: mp3 (mp4a / 0x6134706D)"));
|
||||
Assert.assertEquals("audio", MediaInformationParser.parseStreamType("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseStreamCodec() {
|
||||
Assert.assertEquals("theora", MediaInformationParser.parseStreamCodec("Video: theora"));
|
||||
Assert.assertEquals("png", MediaInformationParser.parseStreamCodec("Video: png"));
|
||||
Assert.assertEquals("h264", MediaInformationParser.parseStreamCodec("Video: h264 (Main)"));
|
||||
Assert.assertEquals("adpcm_ms", MediaInformationParser.parseStreamCodec("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
|
||||
Assert.assertEquals("mp3", MediaInformationParser.parseStreamCodec("Audio: mp3 (mp4a / 0x6134706D)"));
|
||||
Assert.assertEquals("pcm_u8", MediaInformationParser.parseStreamCodec("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseStreamFullCodec() {
|
||||
Assert.assertEquals("theora", MediaInformationParser.parseStreamFullCodec("Video: theora"));
|
||||
Assert.assertEquals("png", MediaInformationParser.parseStreamFullCodec("Video: png"));
|
||||
Assert.assertEquals("h264 (main)", MediaInformationParser.parseStreamFullCodec("Video: h264 (Main)"));
|
||||
Assert.assertEquals("adpcm_ms ([2][0][0][0] / 0x0002)", MediaInformationParser.parseStreamFullCodec("Audio: adpcm_ms ([2][0][0][0] / 0x0002)"));
|
||||
Assert.assertEquals("mp3 (mp4a / 0x6134706d)", MediaInformationParser.parseStreamFullCodec("Audio: mp3 (mp4a / 0x6134706D)"));
|
||||
Assert.assertEquals("pcm_u8 ([1][0][0][0] / 0x0001)", MediaInformationParser.parseStreamFullCodec("Audio: pcm_u8 ([1][0][0][0] / 0x0001)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseStreamIndex() {
|
||||
Assert.assertEquals((Long) 0L, MediaInformationParser.parseStreamIndex("Stream #0:0(und): Audio"));
|
||||
Assert.assertEquals((Long) 1L, MediaInformationParser.parseStreamIndex("Stream #0:1(und): Video"));
|
||||
Assert.assertEquals((Long) 2L, MediaInformationParser.parseStreamIndex("Stream #0:2(und): Audio"));
|
||||
Assert.assertEquals((Long) 0L, MediaInformationParser.parseStreamIndex("Stream #0:0: Video"));
|
||||
Assert.assertEquals((Long) 1L, MediaInformationParser.parseStreamIndex("Stream #0:1: Audio"));
|
||||
Assert.assertEquals((Long) 2L, MediaInformationParser.parseStreamIndex("Stream #0:2: Video"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseDuration() {
|
||||
parseDuration(null, null);
|
||||
parseDuration("", null);
|
||||
parseDuration("N/A", null);
|
||||
parseDuration("00:03:33.24", 213240L);
|
||||
parseDuration("00:10:34.53", 634530L);
|
||||
parseDuration("00:00:00.04", 40L);
|
||||
parseDuration("00:00:15.00", 15000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseStartTime() {
|
||||
parseStartTime(null, null);
|
||||
parseStartTime("", null);
|
||||
parseStartTime("N/A", null);
|
||||
parseStartTime("0.000000", 0L);
|
||||
parseStartTime("10.003000", 10003L);
|
||||
parseStartTime("324.000000", 324000L);
|
||||
parseStartTime("-4.000000", -4000L);
|
||||
parseStartTime("14.00030", 14001L);
|
||||
parseStartTime("14.00080", 14001L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseDurationBlock() {
|
||||
parseDurationBlock(" Duration: 00:03:33.24, start: 0.000000, bitrate: 320 kb/s", 213240L, 0L, 320L);
|
||||
parseDurationBlock(" Duration: 00:00:00.04, start: 0.000000, bitrate: 391187 kb/s", 40L, 0L, 391187L);
|
||||
parseDurationBlock(" Duration: N/A, bitrate: N/A", null, null, null);
|
||||
parseDurationBlock(" Duration: 00:00:15.00, start: 0.000000, bitrate: 7764 kb/s", 15000L, 0L, 7764L);
|
||||
parseDurationBlock(" Duration: 00:10:34.53, start: 0.000000, bitrate: 4474 kb/s", 634530L, 0L, 4474L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInputBlock() {
|
||||
parseInputBlock("Input #0,", null, null);
|
||||
parseInputBlock("Input #0, ogg, from 'trailer_400p.ogg':", "ogg", "trailer_400p.ogg");
|
||||
parseInputBlock("Input #0, mp3, from 'beethoven_-_symphony_no_9.mp3':", "mp3", "beethoven_-_symphony_no_9.mp3");
|
||||
parseInputBlock("Input #0, image2, from '/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg':", "image2", "/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg");
|
||||
parseInputBlock("Input #0, gif, from 'advanced_zoom_in_and_pan_with_fade_in_out.gif':", "gif", "advanced_zoom_in_and_pan_with_fade_in_out.gif");
|
||||
parseInputBlock("Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'transition_rotate.mp4':", "mov,mp4,m4a,3gp,3g2,mj2", "transition_rotate.mp4");
|
||||
parseInputBlock("Input #0, png_pipe, from 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png':", "png_pipe", "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMetadataBlock() {
|
||||
parseMetadataBlock(" ENCODER:", "ENCODER", "");
|
||||
parseMetadataBlock(" ENCODER:ffmpeg2theora 0.19", "ENCODER", "ffmpeg2theora 0.19");
|
||||
parseMetadataBlock(" ENCODER : ffmpeg2theora 0.19", "ENCODER", "ffmpeg2theora 0.19");
|
||||
parseMetadataBlock(" creation_time : 2013-12-16T17:50:04.000000Z", "creation_time", "2013-12-16T17:50:04.000000Z");
|
||||
parseMetadataBlock(" handler_name : GPAC ISO Audio Handler", "handler_name", "GPAC ISO Audio Handler");
|
||||
parseMetadataBlock(" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net", "comment", "Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net");
|
||||
parseMetadataBlock(" minor_version : 1", "minor_version", "1");
|
||||
parseMetadataBlock(" encoder : Lavf58.12.100", "encoder", "Lavf58.12.100");
|
||||
parseMetadataBlock(" title : Planet X", "title", "Planet X");
|
||||
parseMetadataBlock(" compatible_brands: isomiso2avc1mp41", "compatible_brands", "isomiso2avc1mp41");
|
||||
}
|
||||
|
||||
private void parseVideoStreamBlock(String input, Long index, String codec, String fullCodec, String format, String fullFormat, Long width, Long height, String sampleAspectRatio, String displayAspectRatio, Long bitrate, String averageFrameRate, String realFrameRate, String timeBase, String codecTimeBase) {
|
||||
StreamInformation stream = MediaInformationParser.parseStreamBlock(input);
|
||||
|
||||
Assert.assertNotNull(stream);
|
||||
Assert.assertEquals(index, stream.getIndex());
|
||||
Assert.assertEquals("video", stream.getType());
|
||||
Assert.assertEquals(codec, stream.getCodec());
|
||||
Assert.assertEquals(fullCodec, stream.getFullCodec());
|
||||
|
||||
Assert.assertEquals(format, stream.getFormat());
|
||||
Assert.assertEquals(fullFormat, stream.getFullFormat());
|
||||
|
||||
Assert.assertEquals(width, stream.getWidth());
|
||||
Assert.assertEquals(height, stream.getHeight());
|
||||
Assert.assertEquals(sampleAspectRatio, stream.getSampleAspectRatio());
|
||||
Assert.assertEquals(displayAspectRatio, stream.getDisplayAspectRatio());
|
||||
|
||||
Assert.assertEquals(bitrate, stream.getBitrate());
|
||||
|
||||
Assert.assertEquals(averageFrameRate, stream.getAverageFrameRate());
|
||||
Assert.assertEquals(realFrameRate, stream.getRealFrameRate());
|
||||
Assert.assertEquals(timeBase, stream.getTimeBase());
|
||||
Assert.assertEquals(codecTimeBase, stream.getCodecTimeBase());
|
||||
}
|
||||
|
||||
private void parseAudioStreamBlock(String input, Long index, String codec, String fullCodec, Long sampleRate, String channelLayout, String sampleFormat, Long bitrate) {
|
||||
StreamInformation stream = MediaInformationParser.parseStreamBlock(input);
|
||||
|
||||
Assert.assertNotNull(stream);
|
||||
Assert.assertEquals(index, stream.getIndex());
|
||||
Assert.assertEquals("audio", stream.getType());
|
||||
Assert.assertEquals(codec, stream.getCodec());
|
||||
Assert.assertEquals(fullCodec, stream.getFullCodec());
|
||||
Assert.assertEquals(sampleRate, stream.getSampleRate());
|
||||
Assert.assertEquals(channelLayout, stream.getChannelLayout());
|
||||
Assert.assertEquals(sampleFormat, stream.getSampleFormat());
|
||||
Assert.assertEquals(bitrate, stream.getBitrate());
|
||||
}
|
||||
|
||||
private void parseVideoDimensions(String value, Long width, Long height) {
|
||||
Pair<Long, Long> videoDimensions = MediaInformationParser.parseVideoDimensions(value);
|
||||
|
||||
Assert.assertNotNull(videoDimensions);
|
||||
Assert.assertEquals(width, videoDimensions.getFirst());
|
||||
Assert.assertEquals(height, videoDimensions.getSecond());
|
||||
}
|
||||
|
||||
private void parseDuration(String value, Long expected) {
|
||||
Long duration = MediaInformationParser.parseDuration(value);
|
||||
|
||||
Assert.assertEquals(expected, duration);
|
||||
}
|
||||
|
||||
private void parseStartTime(String value, Long expected) {
|
||||
Long duration = MediaInformationParser.parseStartTime(value);
|
||||
|
||||
Assert.assertEquals(expected, duration);
|
||||
}
|
||||
|
||||
private void parseDurationBlock(String value, Long first, Long second, Long third) {
|
||||
Trio<Long, Long, Long> pair = MediaInformationParser.parseDurationBlock(value);
|
||||
|
||||
Assert.assertNotNull(pair);
|
||||
Assert.assertEquals(first, pair.getFirst());
|
||||
Assert.assertEquals(second, pair.getSecond());
|
||||
Assert.assertEquals(third, pair.getThird());
|
||||
}
|
||||
|
||||
private void parseInputBlock(String value, String first, String second) {
|
||||
Pair<String, String> pair = MediaInformationParser.parseInputBlock(value);
|
||||
|
||||
Assert.assertNotNull(pair);
|
||||
Assert.assertEquals(first, pair.getFirst());
|
||||
Assert.assertEquals(second, pair.getSecond());
|
||||
}
|
||||
|
||||
private void parseMetadataBlock(String value, String first, String second) {
|
||||
Pair<String, String> pair = MediaInformationParser.parseMetadataBlock(value);
|
||||
|
||||
Assert.assertNotNull(pair);
|
||||
Assert.assertNotNull(pair.getFirst());
|
||||
Assert.assertNotNull(pair.getSecond());
|
||||
Assert.assertEquals(first, pair.getFirst());
|
||||
Assert.assertEquals(second, pair.getSecond());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,26 +15,18 @@ 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 := c++_shared libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -45,8 +37,8 @@ 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 := c++_shared libavcodec-neon libavfilter-neon libswscale-neon libavformat libavutil libswresample libavdevice
|
||||
LOCAL_ARM_NEON := true
|
||||
|
||||
@@ -6,8 +6,8 @@ android {
|
||||
applicationId "com.arthenica.mobileffmpeg.test"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionCode 30
|
||||
versionName "3.0"
|
||||
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:1.2'
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:3.0'
|
||||
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,15 @@
|
||||
<?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" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -12,8 +18,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,325 @@
|
||||
/*
|
||||
* 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.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.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();
|
||||
// encodeChromaprint();
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
protected void encodeChromaprint() {
|
||||
File audioSampleFile = getAudioSampleFile();
|
||||
|
||||
Log.d(TAG, "Testing AUDIO encoding with 'chromaprint' muxer");
|
||||
|
||||
final String ffmpegCommand = String.format("-v quiet -i %s -f chromaprint -fp_format 2 -", audioSampleFile.getAbsolutePath());
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
|
||||
|
||||
int returnCode = FFmpeg.execute(ffmpegCommand);
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
}
|
||||
|
||||
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("-hide_banner -y -f lavfi -i sine=frequency=1000:duration=5 -c:a pcm_s16le %s", audioSampleFile.getAbsolutePath());
|
||||
|
||||
android.util.Log.d(TAG, String.format("Sample file is created with '%s'", ffmpegCommand));
|
||||
|
||||
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 "mp2 (twolame)":
|
||||
extension = "mpg";
|
||||
break;
|
||||
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 "mp2 (twolame)":
|
||||
return String.format("-hide_banner -y -i %s -c:a mp2 -b:a 192k %s", audioSampleFile, audioOutputFile);
|
||||
case "mp3 (liblame)":
|
||||
return String.format("-hide_banner -y -i %s -c:a libmp3lame -qscale:a 2 %s", audioSampleFile, audioOutputFile);
|
||||
case "mp3 (libshine)":
|
||||
return String.format("-hide_banner -y -i %s -c:a libshine -qscale:a 2 %s", audioSampleFile, audioOutputFile);
|
||||
case "vorbis":
|
||||
return String.format("-hide_banner -y -i %s -c:a libvorbis -b:a 64k %s", audioSampleFile, audioOutputFile);
|
||||
case "opus":
|
||||
return String.format("-hide_banner -y -i %s -c:a libopus -b:a 64k -vbr on -compression_level 10 %s", audioSampleFile, audioOutputFile);
|
||||
case "amr":
|
||||
return String.format("-hide_banner -y -i %s -ar 8000 -ab 12.2k %s", audioSampleFile, audioOutputFile);
|
||||
case "ilbc":
|
||||
return String.format("-hide_banner -y -i %s -c:a ilbc -ar 8000 -b:a 15200 %s", audioSampleFile, audioOutputFile);
|
||||
case "speex":
|
||||
return String.format("-hide_banner -y -i %s -c:a libspeex -ar 16000 %s", audioSampleFile, audioOutputFile);
|
||||
case "wavpack":
|
||||
return String.format("-hide_banner -y -i %s -c:a wavpack -b:a 64k %s", audioSampleFile, audioOutputFile);
|
||||
default:
|
||||
|
||||
// soxr
|
||||
return String.format("-hide_banner -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://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/FFmpeg-Logo.svg/568px-FFmpeg-Logo.svg.png";
|
||||
|
||||
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 -f null -", 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,153 @@
|
||||
|
||||
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.AsynchronousTaskService;
|
||||
import com.arthenica.mobileffmpeg.util.RunCallback;
|
||||
|
||||
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_ALL = {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.CAMERA
|
||||
};
|
||||
|
||||
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_ALL,
|
||||
REQUEST_EXTERNAL_STORAGE);
|
||||
}
|
||||
permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
PERMISSIONS_ALL,
|
||||
REQUEST_EXTERNAL_STORAGE);
|
||||
}
|
||||
permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
PERMISSIONS_ALL,
|
||||
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);
|
||||
int returnCode = FFmpeg.execute(arguments, " ");
|
||||
if (runCallback != null) {
|
||||
runCallback.apply(returnCode);
|
||||
}
|
||||
|
||||
return returnCode;
|
||||
@@ -70,11 +173,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) {
|
||||
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 -c:v " + videoCodec.toLowerCase() + " -r 30 " + fileDirectory + "/" + videoFile;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,216 +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.TextView;
|
||||
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() {
|
||||
final String video = "slideshow.mp4";
|
||||
|
||||
return new File(context.getFilesDir(), video).getAbsolutePath();
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
"-hide_banner -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
|
||||
"-hide_banner -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,402 @@
|
||||
/*
|
||||
* 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.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_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();
|
||||
// encodeWebp();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 encodeWebp() {
|
||||
final File imageFile = new File(mainActivity.getCacheDir(), "colosseum.jpg");
|
||||
final File outputFile = new File(mainActivity.getFilesDir(), "video.webp");
|
||||
|
||||
try {
|
||||
mainActivity.resourceToFile(R.drawable.colosseum, imageFile);
|
||||
|
||||
Log.d(TAG, "Testing VIDEO encoding with 'webp' codec");
|
||||
|
||||
final String ffmpegCommand = String.format("-hide_banner -i %s %s", imageFile.getAbsolutePath(), outputFile.getAbsolutePath());
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process started with arguments\n'%s'", ffmpegCommand));
|
||||
|
||||
int returnCode = FFmpeg.execute(ffmpegCommand);
|
||||
|
||||
Log.d(TAG, String.format("FFmpeg process exited with rc %d", returnCode));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Encode webp failed", e);
|
||||
Popup.show(mainActivity, "Encode webp 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() {
|
||||
|
||||
// NOTE THAT MPEG4 CODEC IS ASSIGNED HERE
|
||||
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:
|
||||
|
||||
// mpeg4, x264, xvid
|
||||
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"/>
|
||||