Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Video concatenation #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 4, 2019
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
### v0.7.0 (to be released)

- New: video concatenation to stitch together multiple media ([#14][14])
- New: select a specific track type (`VIDEO` or `AUDIO`) for sources ([#14][14])
- Breaking change: `TranscoderOptions.setDataSource()` renamed to `addDataSource()` ([#14][14])
- Breaking change: `TranscoderOptions.setRotation()` renamed to `setVideoRotation()` ([#14][14])
- Breaking change: `DefaultVideoStrategy.iFrameInterval()` renamed to `keyFrameInterval()` ([#14][14])
- Improvement: rotate videos through OpenGL instead of using metadata ([#14][14])

### v0.6.0

- New: ability to change video/audio speed and change each frame timestamp ([#10][10])
Expand All @@ -23,3 +32,4 @@
[8]: https://github.com/natario1/Transcoder/pull/8
[9]: https://github.com/natario1/Transcoder/pull/9
[10]: https://github.com/natario1/Transcoder/pull/10
[14]: https://github.com/natario1/Transcoder/pull/14
57 changes: 48 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ Using Transcoder in the most basic form is pretty simple:

```java
Transcoder.into(filePath)
.setDataSource(context, uri) // or...
.setDataSource(filePath) // or...
.setDataSource(fileDescriptor) // or...
.setDataSource(dataSource)
.addDataSource(context, uri) // or...
.addDataSource(filePath) // or...
.addDataSource(fileDescriptor) // or...
.addDataSource(dataSource)
.setListener(new TranscoderListener() {
public void onTranscodeProgress(double progress) {}
public void onTranscodeCompleted(int successCode) {}
Expand All @@ -42,6 +42,7 @@ Take a look at the demo app for a real example or keep reading below for documen
- Hardware accelerated
- Multithreaded
- Convenient, fluent API
- Concatenate multiple video and audio tracks [[docs]](#video-concatenation)
- Choose output size, with automatic cropping [[docs]](#video-size)
- Choose output rotation [[docs]](#video-rotation)
- Choose output speed [[docs]](#video-speed)
Expand Down Expand Up @@ -80,17 +81,55 @@ which is convenient but it means that they can not be used twice.
#### `UriDataSource`

The Android friendly source can be created with `new UriDataSource(context, uri)` or simply
using `setDataSource(context, uri)` in the transcoding builder.
using `addDataSource(context, uri)` in the transcoding builder.

#### `FileDescriptorDataSource`

A data source backed by a file descriptor. Use `new FileDescriptorDataSource(descriptor)` or
simply `setDataSource(descriptor)` in the transcoding builder.
simply `addDataSource(descriptor)` in the transcoding builder.

#### `FilePathDataSource`

A data source backed by a file absolute path. Use `new FilePathDataSource(path)` or
simply `setDataSource(path)` in the transcoding builder.
simply `addDataSource(path)` in the transcoding builder.

## Video Concatenation

As you might have guessed, you can use `addDataSource(source)` multiple times. All the source
files will be stitched together:

```java
Transcoder.into(filePath)
.addDataSource(source1)
.addDataSource(source2)
.addDataSource(source3)
// ...
```

In the above example, the three videos will be stitched together in the order they are added
to the builder. Once `source1` ends, we'll append `source2` and so on. The library will take care
of applying consistent parameters (frame rate, bit rate, sample rate) during the conversion.

This is a powerful tool since it can be used per-track:

```java
Transcoder.into(filePath)
.addDataSource(source1) // Audio & Video, 20 seconds
.addDataSource(TrackType.VIDEO, source2) // Video, 5 seconds
.addDataSource(TrackType.VIDEO, source3) // Video, 5 seconds
.addDataSource(TrackType.AUDIO, source4) // Audio, 10 sceonds
// ...
```

In the above example, the output file will be 30 seconds long:

```
_____________________________________________________________________________________
Video |___________________source1_____________________:_____source2_____:______source3______|
Audio |___________________source1_____________________:______________source4________________|
```

And that's all you need.

## Listening for events

Expand Down Expand Up @@ -312,7 +351,7 @@ DefaultVideoStrategy strategy = new DefaultVideoStrategy.Builder()
.bitRate(bitRate)
.bitRate(DefaultVideoStrategy.BITRATE_UNKNOWN) // tries to estimate
.frameRate(frameRate) // will be capped to the input frameRate
.iFrameInterval(interval) // interval between I-frames in seconds
.keyFrameInterval(interval) // interval between key-frames in seconds
.build();
```

Expand All @@ -325,7 +364,7 @@ rotation to the input video frames. Accepted values are `0`, `90`, `180`, `270`:

```java
Transcoder.into(filePath)
.setRotation(rotation) // 0, 90, 180, 270
.setVideoRotation(rotation) // 0, 90, 180, 270
// ...
```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.otaliastudios.transcoder.demo;

import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
Expand All @@ -11,6 +12,7 @@

import com.otaliastudios.transcoder.Transcoder;
import com.otaliastudios.transcoder.TranscoderListener;
import com.otaliastudios.transcoder.TranscoderOptions;
import com.otaliastudios.transcoder.internal.Logger;
import com.otaliastudios.transcoder.strategy.DefaultAudioStrategy;
import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy;
Expand Down Expand Up @@ -51,7 +53,9 @@ public class TranscoderActivity extends AppCompatActivity implements

private boolean mIsTranscoding;
private Future<Void> mTranscodeFuture;
private Uri mTranscodeInputUri;
private Uri mTranscodeInputUri1;
private Uri mTranscodeInputUri2;
private Uri mTranscodeInputUri3;
private File mTranscodeOutputFile;
private long mTranscodeStartTime;
private TrackStrategy mTranscodeVideoStrategy;
Expand All @@ -66,7 +70,9 @@ protected void onCreate(Bundle savedInstanceState) {
mButtonView = findViewById(R.id.button);
mButtonView.setOnClickListener(v -> {
if (!mIsTranscoding) {
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("video/*"), REQUEST_CODE_PICK);
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT)
.setType("video/*")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true), REQUEST_CODE_PICK);
} else {
mTranscodeFuture.cancel(true);
}
Expand Down Expand Up @@ -141,10 +147,19 @@ protected void onActivityResult(int requestCode, int resultCode, final Intent da
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK
&& resultCode == RESULT_OK
&& data != null
&& data.getData() != null) {
mTranscodeInputUri = data.getData();
transcode();
&& data != null) {
if (data.getData() != null) {
mTranscodeInputUri1 = data.getData();
mTranscodeInputUri2 = null;
mTranscodeInputUri3 = null;
transcode();
} else if (data.getClipData() != null) {
ClipData clipData = data.getClipData();
mTranscodeInputUri1 = clipData.getItemAt(0).getUri();
mTranscodeInputUri2 = clipData.getItemCount() >= 2 ? clipData.getItemAt(1).getUri() : null;
mTranscodeInputUri3 = clipData.getItemCount() >= 3 ? clipData.getItemAt(2).getUri() : null;
transcode();
}
}
}

Expand Down Expand Up @@ -180,12 +195,14 @@ private void transcode() {
// Launch the transcoding operation.
mTranscodeStartTime = SystemClock.uptimeMillis();
setIsTranscoding(true);
mTranscodeFuture = Transcoder.into(mTranscodeOutputFile.getAbsolutePath())
.setDataSource(this, mTranscodeInputUri)
.setListener(this)
TranscoderOptions.Builder builder = Transcoder.into(mTranscodeOutputFile.getAbsolutePath());
if (mTranscodeInputUri1 != null) builder.addDataSource(this, mTranscodeInputUri1);
if (mTranscodeInputUri2 != null) builder.addDataSource(this, mTranscodeInputUri2);
if (mTranscodeInputUri3 != null) builder.addDataSource(this, mTranscodeInputUri3);
mTranscodeFuture = builder.setListener(this)
.setAudioTrackStrategy(mTranscodeAudioStrategy)
.setVideoTrackStrategy(mTranscodeVideoStrategy)
.setRotation(rotation)
.setVideoRotation(rotation)
.setSpeed(speed)
.transcode();
}
Expand Down Expand Up @@ -216,7 +233,7 @@ public void onTranscodeCompleted(int successCode) {
LOG.i("Transcoding was not needed.");
onTranscodeFinished(true, "Transcoding not needed, source file not touched.");
startActivity(new Intent(Intent.ACTION_VIEW)
.setDataAndType(mTranscodeInputUri, "video/mp4")
.setDataAndType(mTranscodeInputUri1, "video/mp4")
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
}
Expand Down
16 changes: 4 additions & 12 deletions lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ public static TranscoderOptions.Builder into(@NonNull String outPath) {
@NonNull
public Future<Void> transcode(@NonNull final TranscoderOptions options) {
final TranscoderListener listenerWrapper = new ListenerWrapper(options.listenerHandler,
options.listener, options.getDataSource());
options.listener);
return mExecutor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
Engine engine = new Engine(options.getDataSource(), new Engine.ProgressCallback() {
Engine engine = new Engine(new Engine.ProgressCallback() {
@Override
public void onProgress(final double progress) {
listenerWrapper.onTranscodeProgress(progress);
Expand Down Expand Up @@ -154,29 +154,23 @@ public void onProgress(final double progress) {
}

/**
* Wraps a TranscoderListener and a DataSource object, ensuring that the source
* is released when transcoding ends, fails or is canceled.
*
* It posts events on the given handler.
* Wraps a TranscoderListener and posts events on the given handler.
*/
private static class ListenerWrapper implements TranscoderListener {

private Handler mHandler;
private TranscoderListener mListener;
private DataSource mDataSource;

private ListenerWrapper(@NonNull Handler handler, @NonNull TranscoderListener listener, @NonNull DataSource source) {
private ListenerWrapper(@NonNull Handler handler, @NonNull TranscoderListener listener) {
mHandler = handler;
mListener = listener;
mDataSource = source;
}

@Override
public void onTranscodeCanceled() {
mHandler.post(new Runnable() {
@Override
public void run() {
mDataSource.release();
mListener.onTranscodeCanceled();
}
});
Expand All @@ -187,7 +181,6 @@ public void onTranscodeCompleted(final int successCode) {
mHandler.post(new Runnable() {
@Override
public void run() {
mDataSource.release();
mListener.onTranscodeCompleted(successCode);
}
});
Expand All @@ -198,7 +191,6 @@ public void onTranscodeFailed(@NonNull final Throwable exception) {
mHandler.post(new Runnable() {
@Override
public void run() {
mDataSource.release();
mListener.onTranscodeFailed(exception);
}
});
Expand Down
Loading