1
1
# Transcoder
2
2
3
- This project is an improved fork of [ ypresto/android-transcoder] ( https://github.com/ypresto/android-transcoder ) .
4
- Lots of changes were made, so the documentation must be rewritten. You can, however, take a look at the
5
- demo app which provides a working example of the new API.
3
+ Transcodes and compresses video files into the MP4 format, with audio support.
6
4
7
5
``` groovy
8
6
implementation 'com.otaliastudios:transcoder:0.4.0'
9
7
```
10
8
9
+ This project is an improved fork of [ ypresto/android-transcoder] ( https://github.com/ypresto/android-transcoder ) .
10
+ It features a lot of improvements over the original project, including:
11
+
12
+ - Multithreading support
13
+ - Various bugs fixed
14
+ - Accept content:// Uris and other types of input
15
+ - Real error handling instead of errors being thrown
16
+ - Source project is over-conservative when choosing options that * might* not be supported. We prefer to try and let the codec fail
17
+ - More convenient APIs for transcoding & choosing options
18
+ - Do ** not** perform transcoding if the source video already matches our options (configurable)
19
+ - Expose internal logs through Logger (so they can be reported to e.g. Crashlytics)
20
+
21
+ Using Transcoder in the most basic form is pretty simple:
22
+
23
+ ``` java
24
+ MediaTranscoder . into(filePath)
25
+ .setDataSource(context, uri) // or...
26
+ .setDataSource(filePath) // or...
27
+ .setDataSource(fileDescriptor) // or...
28
+ .setDataSource(dataSource)
29
+ .setListener(new MediaTranscoder .Listener () {
30
+ public void onTranscodeProgress (double progress ) {}
31
+ public void onTranscodeCompleted (int successCode ) {}
32
+ public void onTranscodeCanceled () {}
33
+ public void onTranscodeFailed (@NonNull Throwable exception ) {}
34
+ }). transcode()
35
+ ```
36
+
37
+ Take a look at the demo app for a real example or keep reading below for documentation.
38
+
11
39
## Setup
12
40
13
41
This library requires API level 18 (Android 4.3, JELLY_BEAN_MR2) or later.
@@ -21,6 +49,261 @@ adding this line to your manifest file:
21
49
In this case you should check at runtime that API level is at least 18, before
22
50
calling any method here.
23
51
52
+ ## DataSource
53
+
54
+ Starting a transcoding operation will require a source for our data, which is not necessarily
55
+ a ` File ` . The ` DataSource ` objects will automatically take care about releasing streams / resources,
56
+ which is convenient but it means that they can not be used twice.
57
+
58
+ ### ` UriDataSource `
59
+
60
+ The Android friendly source can be created with ` new UriDataSource(context, uri) ` or simply
61
+ using ` setDataSource(context, uri) ` in the transcoding builder.
62
+
63
+ ### ` FileDescriptorDataSource `
64
+
65
+ A data source backed by a file descriptor. Use ` new FileDescriptorDataSource(descriptor) ` or
66
+ simply ` setDataSource(descriptor) ` in the transcoding builder.
67
+
68
+ ### ` FilePathDataSource `
69
+
70
+ A data source backed by a file absolute path. Use ` new FilePathDataSource(path) ` or
71
+ simply ` setDataSource(path) ` in the transcoding builder.
72
+
73
+ ## Listening for events
74
+
75
+ Transcoding will happen on a background thread, but we will send updates through the ` MediaTranscoder.Listener `
76
+ interface, which can be applied when building the request:
77
+
78
+ ``` java
79
+ MediaTranscoder . into(filePath)
80
+ .setListenerHandler(handler)
81
+ .setListener(new MediaTranscoder .Listener () {
82
+ public void onTranscodeProgress (double progress ) {}
83
+ public void onTranscodeCompleted (int successCode ) {}
84
+ public void onTranscodeCanceled () {}
85
+ public void onTranscodeFailed (@NonNull Throwable exception ) {}
86
+ })
87
+ // ...
88
+ ```
89
+
90
+ All of the listener callbacks are called:
91
+
92
+ - If present, on the handler specified by ` setListenerHandler() `
93
+ - If it has a handler, on the thread that started the ` transcode() ` call
94
+ - As a last resort, on the UI thread
95
+
96
+ ### ` onTranscodeProgress `
97
+
98
+ This simply sends a double indicating the current progress. The value is typically between 0 and 1,
99
+ but can be a negative value to indicate that we are not able to compute progress (yet?).
100
+
101
+ This is the right place to update a ProgressBar, for example.
102
+
103
+ ### ` onTranscodeCanceled `
104
+
105
+ The transcoding operation was canceled. This can happen when the ` Future ` returned by ` transcode() `
106
+ is cancelled by the user.
107
+
108
+ ### ` onTranscodeFailed `
109
+
110
+ This can happen in a number of cases and is typically out of our control. Input options might be
111
+ wrong, write permissions might be missing, codec might be absent, input file might be not supported
112
+ or simply corrupted.
113
+
114
+ You can take a look at the ` Throwable ` being passed to know more about the exception.
115
+
116
+ ### ` onTranscodeCompleted `
117
+
118
+ Transcoding operation did succeed. The success code can be:
119
+
120
+ | Code| Meaning|
121
+ | ----| -------|
122
+ | ` MediaTranscoder.SUCCESS_TRANSCODED ` | Transcoding was executed successfully. Transcoded file was written to the output path.|
123
+ | ` MediaTranscoder.SUCCESS_NOT_NEEDED ` | Transcoding was not executed because it was considered ** not needed** by the ` Validator ` .|
124
+
125
+ Keep reading [ below] ( #validators ) to know about ` Validator ` s.
126
+
127
+ ## Validators
128
+
129
+ Validators tell the engine whether the transcoding process should start or not based on the status
130
+ of the audio and video track.
131
+
132
+ ``` java
133
+ MediaTranscoder . into(filePath)
134
+ .setValidator(validator)
135
+ // ...
136
+ ```
137
+
138
+ This can be used, for example, to:
139
+
140
+ - avoid transcoding when video resolution is already OK with our needs
141
+ - avoid operating on files without an audio/video stream
142
+ - avoid operating on files with an audio/video stream
143
+
144
+ Validators should implement the ` validate(TrackStatus, TrackStatus) ` and inspect the status for video
145
+ and audio tracks. When ` false ` is returned, transcoding will complete with the ` SUCCESS_NOT_NEEDED ` status code.
146
+ The TrackStatus enum contains the following values:
147
+
148
+ | Value| Meaning|
149
+ | -----| -------|
150
+ | ` TrackStatus.ABSENT ` | This track was absent in the source file.|
151
+ | ` TrackStatus.PASS_THROUGH ` | This track is about to be copied as-is in the target file.|
152
+ | ` TrackStatus.COMPRESSING ` | This track is about to be processed and compressed in the target file.|
153
+ | ` TrackStatus.REMOVING ` | This track will be removed in the target file.|
154
+
155
+ The ` TrackStatus ` value depends on the [ output strategy] ( #output-strategies ) that was used.
156
+ We provide a few validators that can be injected for typical usage.
157
+
158
+ ### ` DefaultValidator `
159
+
160
+ This is the default validator and it returns true when any of the track is ` COMPRESSING ` or ` REMOVING ` .
161
+ In the other cases, transcoding is typically not needed so we abort the operation.
162
+
163
+ ### ` WriteAlwaysValidator `
164
+
165
+ This validator always returns true and as such will always write to target file, no matter the track status,
166
+ presence of tracks and so on. For instance, the output container file might have no tracks.
167
+
168
+ ### ` WriteVideoValidator `
169
+
170
+ A Validator that gives priority to the video track. Transcoding will not happen if the video track does not need it,
171
+ even if the audio track might need it. If reducing file size is your only concern, this can avoid compressing
172
+ files that would not benefit so much from compressing the audio track only.
173
+
174
+ ## Output Strategies
175
+
176
+ Output strategies return options for each track (audio or video) for the engine to understand ** how**
177
+ and ** if** this track should be transcoded, and whether the whole process should be aborted.
178
+
179
+ ``` java
180
+ MediaTranscoder . into(filePath)
181
+ .setVideoOutputStrategy(videoStrategy)
182
+ .setAudioOutputStrategy(audioStrategy)
183
+ // ...
184
+ ```
185
+
186
+ The point of ` OutputStrategy ` is to inspect the input ` android.media.MediaFormat ` and return
187
+ the output ` android.media.MediaFormat ` , filled with required options.
188
+
189
+ This library offers track specific strategies that help with audio and video options (see
190
+ [ Audio Strategies] ( #audio-strategies ) and [ Video Strategies] ( #video-strategies ) ).
191
+ In addition, we have a few built-in strategies that can work for both audio and video:
192
+
193
+ ### ` PassThroughTrackStrategy `
194
+
195
+ An OutputStrategy that asks the encoder to keep this track as is, by returning the same input
196
+ format. Note that this is risky, as the input track format might not be supported my the MP4 container.
197
+
198
+ This will set the ` TrackStatus ` to ` TrackStatus.PASS_THROUGH ` .
199
+
200
+ ### ` RemoveTrackStrategy `
201
+
202
+ An OutputStrategy that asks the encoder to remove this track from the output container, by returning null.
203
+ For instance, this can be used as an audio strategy to remove audio from video/audio streams.
204
+
205
+ This will set the ` TrackStatus ` to ` TrackStatus.REMOVING ` .
206
+
207
+ ## Audio Strategies
208
+
209
+ The default internal strategy for audio is a ` DefaultAudioStrategy ` , which converts the
210
+ audio stream to AAC format with the specified number of channels.
211
+
212
+ ``` java
213
+ MediaTranscoder . into(filePath)
214
+ .setAudioOutputStrategy(DefaultAudioStrategy(1 )) // or..
215
+ .setAudioOutputStrategy(DefaultAudioStrategy(2 )) // or..
216
+ .setAudioOutputStrategy(DefaultAudioStrategy(DefaultAudioStrategy . AUDIO_CHANNELS_AS_IS ))
217
+ // ...
218
+ ```
219
+
220
+ Take a look at the source code to understand how to manage the ` android.media.MediaFormat ` object.
221
+
222
+ ## Video Strategies
223
+
224
+ The default internal strategy for video is a ` DefaultVideoStrategy ` , which converts the
225
+ video stream to AVC format and is very configurable. The class helps in defining an output size
226
+ that matches the aspect ratio of the input stream size, which is a basic requirement for video strategies.
227
+
228
+ ### Video Size
229
+
230
+ We provide helpers for common tasks:
231
+
232
+ ``` java
233
+ DefaultVideoStrategy strategy;
234
+
235
+ // Sets an exact size. Of course this is risky if you don't read the input size first.
236
+ strategy = DefaultVideoStrategy . exact(1080 , 720 ). build()
237
+
238
+ // Keeps the aspect ratio, but scales down the input size with the given fraction.
239
+ strategy = DefaultVideoStrategy . fraction(0.5F ). build()
240
+
241
+ // Ensures that each video size is at most the given value - scales down otherwise.
242
+ strategy = DefaultVideoStrategy . atMost(1000 ). build()
243
+
244
+ // Ensures that minor and major dimension are at most the given values - scales down otherwise.
245
+ strategy = DefaultVideoStrategy . atMost(500 , 1000 ). build()
246
+ ```
247
+
248
+ In fact, all of these will simply call ` new DefaultVideoStrategy.Builder(resizer) ` with a special
249
+ resizer. We offer handy resizers:
250
+
251
+ | Name| Description|
252
+ | ----| -----------|
253
+ | ` ExactResizer ` | Returns the dimensions passed to the constructor. Throws if aspect ratio does not match.|
254
+ | ` FractionResizer ` | Reduces the input size by the given fraction (0..1).|
255
+ | ` AtMostResizer ` | If needed, reduces the input size so that the "at most" constraints are matched. Aspect ratio is kept.|
256
+ | ` PassThroughResizer ` | Returns the input size unchanged.|
257
+
258
+ You can also group resizers through ` MultiResizer ` , which applies resizers in chain:
259
+
260
+ ``` java
261
+ // First scales down, then ensures size is at most 1000. Order matters!
262
+ Resizer resizer = new MultiResizer ()
263
+ resizer. addResizer(new FractionResizer (0.5F ))
264
+ resizer. addResizer(new AtMostResizer (1000 ))
265
+ ```
266
+
267
+ This option is already available through the DefaultVideoStrategy builder, so you can do:
268
+
269
+ ``` java
270
+ DefaultVideoStrategy strategy = new DefaultVideoStrategy .Builder ()
271
+ .addResizer(new FractionResizer (0.5F ))
272
+ .addResizer(new AtMostResizer (1000 ))
273
+ .build()
274
+ ```
275
+
276
+ ### Other options
277
+
278
+ You can configure the ` DefaultVideoStrategy ` with other options unrelated to the video size:
279
+
280
+ ``` java
281
+ DefaultVideoStrategy strategy = new DefaultVideoStrategy .Builder ()
282
+ .bitRate(bitRate)
283
+ .bitRate(DefaultVideoStrategy . BITRATE_UNKNOWN ) // tries to estimate
284
+ .frameRate(frameRate) // will be capped to the input frameRate
285
+ .iFrameInterval(interval) // interval between I-frames in seconds
286
+ .build()
287
+ ```
288
+
289
+ ## Compatibility
290
+
291
+ As stated pretty much everywhere, ** not all codecs/devices/manufacturers support all sizes/options** .
292
+ This is a complex issue which is especially important for video strategies, as a wrong size can lead
293
+ to a transcoding error or corrupted file.
294
+
295
+ Android platform specifies requirements for manufacturers through the [ CTS (Compatibility test suite)] ( https://source.android.com/compatibility/cts ) .
296
+ Only a few codecs and sizes are strictly required to work.
297
+
298
+ We collect common presets in the ` DefaultVideoStrategies ` class:
299
+
300
+ ``` java
301
+ MediaTranscoder . into(filePath)
302
+ .setVideoOutputStrategy(DefaultVideoStrategies . for720x1280()) // 16:9
303
+ .setVideoOutputStrategy(DefaultVideoStrategies . for360x480()) // 4:3
304
+ // ...
305
+ ```
306
+
24
307
## License
25
308
26
309
This project is licensed under Apache 2.0. It consists of improvements over
0 commit comments