diff options
Diffstat (limited to 'libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java')
-rw-r--r-- | libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java new file mode 100644 index 000000000..7ec32bc97 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder; + +import android.media.MediaFormat; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import net.ypresto.androidtranscoder.engine.MediaTranscoderEngine; +import net.ypresto.androidtranscoder.format.MediaFormatPresets; +import net.ypresto.androidtranscoder.format.MediaFormatStrategy; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class MediaTranscoder { + private static final String TAG = "MediaTranscoder"; + private static final int MAXIMUM_THREAD = 1; // TODO + private static volatile MediaTranscoder sMediaTranscoder; + private ThreadPoolExecutor mExecutor; + + private MediaTranscoder() { + mExecutor = new ThreadPoolExecutor( + 0, MAXIMUM_THREAD, 60, TimeUnit.SECONDS, + new LinkedBlockingQueue<Runnable>(), + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "MediaTranscoder-Worker"); + } + }); + } + + public static MediaTranscoder getInstance() { + if (sMediaTranscoder == null) { + synchronized (MediaTranscoder.class) { + if (sMediaTranscoder == null) { + sMediaTranscoder = new MediaTranscoder(); + } + } + } + return sMediaTranscoder; + } + + /** + * Transcodes video file asynchronously. + * Audio track will be kept unchanged. + * + * @param inFileDescriptor FileDescriptor for input. + * @param outPath File path for output. + * @param listener Listener instance for callback. + * @deprecated Use {@link #transcodeVideo(FileDescriptor, String, MediaFormatStrategy, MediaTranscoder.Listener)} which accepts output video format. + */ + @Deprecated + public Future<Void> transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final Listener listener) { + return transcodeVideo(inFileDescriptor, outPath, new MediaFormatStrategy() { + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + return MediaFormatPresets.getExportPreset960x540(); + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + return null; + } + }, listener); + } + + /** + * Transcodes video file asynchronously. + * Audio track will be kept unchanged. + * + * @param inPath File path for input. + * @param outPath File path for output. + * @param outFormatStrategy Strategy for output video format. + * @param listener Listener instance for callback. + * @throws IOException if input file could not be read. + */ + public Future<Void> transcodeVideo(final String inPath, final String outPath, final MediaFormatStrategy outFormatStrategy, final Listener listener) throws IOException { + FileInputStream fileInputStream = null; + FileDescriptor inFileDescriptor; + try { + fileInputStream = new FileInputStream(inPath); + inFileDescriptor = fileInputStream.getFD(); + } catch (IOException e) { + if (fileInputStream != null) { + try { + fileInputStream.close(); + } catch (IOException eClose) { + Log.e(TAG, "Can't close input stream: ", eClose); + } + } + throw e; + } + final FileInputStream finalFileInputStream = fileInputStream; + return transcodeVideo(inFileDescriptor, outPath, outFormatStrategy, new Listener() { + @Override + public void onTranscodeProgress(double progress) { + listener.onTranscodeProgress(progress); + } + + @Override + public void onTranscodeCompleted() { + closeStream(); + listener.onTranscodeCompleted(); + } + + @Override + public void onTranscodeCanceled() { + closeStream(); + listener.onTranscodeCanceled(); + } + + @Override + public void onTranscodeFailed(Exception exception) { + closeStream(); + listener.onTranscodeFailed(exception); + } + + private void closeStream() { + try { + finalFileInputStream.close(); + } catch (IOException e) { + Log.e(TAG, "Can't close input stream: ", e); + } + } + }); + } + + /** + * Transcodes video file asynchronously. + * Audio track will be kept unchanged. + * + * @param inFileDescriptor FileDescriptor for input. + * @param outPath File path for output. + * @param outFormatStrategy Strategy for output video format. + * @param listener Listener instance for callback. + */ + public Future<Void> transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final MediaFormatStrategy outFormatStrategy, final Listener listener) { + Looper looper = Looper.myLooper(); + if (looper == null) looper = Looper.getMainLooper(); + final Handler handler = new Handler(looper); + final AtomicReference<Future<Void>> futureReference = new AtomicReference<>(); + final Future<Void> createdFuture = mExecutor.submit(new Callable<Void>() { + @Override + public Void call() throws Exception { + Exception caughtException = null; + try { + MediaTranscoderEngine engine = new MediaTranscoderEngine(); + engine.setProgressCallback(new MediaTranscoderEngine.ProgressCallback() { + @Override + public void onProgress(final double progress) { + handler.post(new Runnable() { // TODO: reuse instance + @Override + public void run() { + listener.onTranscodeProgress(progress); + } + }); + } + }); + engine.setDataSource(inFileDescriptor); + engine.transcodeVideo(outPath, outFormatStrategy); + } catch (IOException e) { + Log.w(TAG, "Transcode failed: input file (fd: " + inFileDescriptor.toString() + ") not found" + + " or could not open output file ('" + outPath + "') .", e); + caughtException = e; + } catch (InterruptedException e) { + Log.i(TAG, "Cancel transcode video file.", e); + caughtException = e; + } catch (RuntimeException e) { + Log.e(TAG, "Fatal error while transcoding, this might be invalid format or bug in engine or Android.", e); + caughtException = e; + } + + final Exception exception = caughtException; + handler.post(new Runnable() { + @Override + public void run() { + if (exception == null) { + listener.onTranscodeCompleted(); + } else { + Future<Void> future = futureReference.get(); + if (future != null && future.isCancelled()) { + listener.onTranscodeCanceled(); + } else { + listener.onTranscodeFailed(exception); + } + } + } + }); + + if (exception != null) throw exception; + return null; + } + }); + futureReference.set(createdFuture); + return createdFuture; + } + + public interface Listener { + /** + * Called to notify progress. + * + * @param progress Progress in [0.0, 1.0] range, or negative value if progress is unknown. + */ + void onTranscodeProgress(double progress); + + /** + * Called when transcode completed. + */ + void onTranscodeCompleted(); + + /** + * Called when transcode canceled. + */ + void onTranscodeCanceled(); + + /** + * Called when transcode failed. + * + * @param exception Exception thrown from {@link MediaTranscoderEngine#transcodeVideo(String, MediaFormatStrategy)}. + * Note that it IS NOT {@link java.lang.Throwable}. This means {@link java.lang.Error} won't be caught. + */ + void onTranscodeFailed(Exception exception); + } +} |