diff --git a/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java b/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java deleted file mode 100644 index 3e82dcd..0000000 --- a/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.frank.ffmpeg.hardware; - -import android.media.MediaCodec; -import android.media.MediaExtractor; -import android.media.MediaFormat; -import android.os.SystemClock; -import android.util.Log; -import android.view.Surface; - -import java.nio.ByteBuffer; - -/** - * Extract by MediaExtractor, decode by MediaCodec, and render to Surface - * Created by frank on 2019/11/16. - */ - -public class HardwareDecode { - - private final static String TAG = HardwareDecode.class.getSimpleName(); - - private final static long DEQUEUE_TIME = 10 * 1000; - private final static int SLEEP_TIME = 10; - - private final static int RATIO_1080 = 1080; - private final static int RATIO_480 = 480; - private final static int RATIO_240 = 240; - - private Surface mSurface; - - private String mFilePath; - - private VideoDecodeThread videoDecodeThread; - - private OnDataCallback mCallback; - - public interface OnDataCallback { - void onData(long duration); - } - - public HardwareDecode(Surface surface, String filePath, OnDataCallback onDataCallback) { - this.mSurface = surface; - this.mFilePath = filePath; - this.mCallback = onDataCallback; - } - - public void decode() { - videoDecodeThread = new VideoDecodeThread(); - videoDecodeThread.start(); - } - - public void seekTo(long seekPosition) { - if (videoDecodeThread != null && !videoDecodeThread.isInterrupted()) { - videoDecodeThread.seekTo(seekPosition); - } - } - - public void setPreviewing(boolean previewing) { - if (videoDecodeThread != null) { - videoDecodeThread.setPreviewing(previewing); - } - } - - public void release() { - if (videoDecodeThread != null && !videoDecodeThread.isInterrupted()) { - videoDecodeThread.interrupt(); - videoDecodeThread.release(); - videoDecodeThread = null; - } - } - - private class VideoDecodeThread extends Thread { - - private MediaExtractor mediaExtractor; - - private MediaCodec mediaCodec; - - private boolean isPreviewing; - - void setPreviewing(boolean previewing) { - this.isPreviewing = previewing; - } - - void seekTo(long seekPosition) { - try { - if (mediaExtractor != null) { - mediaExtractor.seekTo(seekPosition, MediaExtractor.SEEK_TO_CLOSEST_SYNC); - } - } catch (IllegalStateException e) { - Log.e(TAG, "seekTo error=" + e.toString()); - } - } - - void release() { - try { - if (mediaCodec != null) { - mediaCodec.stop(); - mediaCodec.release(); - } - if (mediaExtractor != null) { - mediaExtractor.release(); - } - } catch (Exception e) { - Log.e(TAG, "release error=" + e.toString()); - } - } - - /** - * setting the preview resolution according to video aspect ratio - * - * @param mediaFormat mediaFormat - */ - private void setPreviewRatio(MediaFormat mediaFormat) { - if (mediaFormat == null) { - return; - } - int videoWidth = mediaFormat.getInteger(MediaFormat.KEY_WIDTH); - int videoHeight = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); - int previewRatio; - if (videoWidth >= RATIO_1080) { - previewRatio = 10; - } else if (videoWidth >= RATIO_480) { - previewRatio = 6; - } else if (videoWidth >= RATIO_240) { - previewRatio = 4; - } else { - previewRatio = 1; - } - int previewWidth = videoWidth / previewRatio; - int previewHeight = videoHeight / previewRatio; - Log.e(TAG, "videoWidth=" + videoWidth + "--videoHeight=" + videoHeight - + "--previewWidth=" + previewWidth + "--previewHeight=" + previewHeight); - mediaFormat.setInteger(MediaFormat.KEY_WIDTH, previewWidth); - mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, previewHeight); - } - - @Override - public void run() { - super.run(); - - mediaExtractor = new MediaExtractor(); - MediaFormat mediaFormat = null; - String mimeType = ""; - try { - mediaExtractor.setDataSource(mFilePath); - for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { - mediaFormat = mediaExtractor.getTrackFormat(i); - mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); - if (mimeType != null && mimeType.startsWith("video/")) { - mediaExtractor.selectTrack(i); - break; - } - } - if (mediaFormat == null || mimeType == null) { - return; - } - int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH); - int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); - long duration = mediaFormat.getLong(MediaFormat.KEY_DURATION); - if (mCallback != null) { - mCallback.onData(duration); - } - Log.i(TAG, "width=" + width + "--height=" + height + "--duration==" + duration); - - //setting preview resolution - setPreviewRatio(mediaFormat); - Log.i(TAG, "mediaFormat=" + mediaFormat.toString()); - - //config MediaCodec, and start - mediaCodec = MediaCodec.createDecoderByType(mimeType); - mediaCodec.configure(mediaFormat, mSurface, null, 0); - mediaCodec.start(); - MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); - - while (!isInterrupted()) { - if (!isPreviewing) { - SystemClock.sleep(SLEEP_TIME); - continue; - } - //dequeue from input buffer - int inputIndex = mediaCodec.dequeueInputBuffer(DEQUEUE_TIME); - if (inputIndex >= 0) { - ByteBuffer inputBuffer = inputBuffers[inputIndex]; - int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0); - //enqueue to input buffer - if (sampleSize < 0) { - mediaCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); - } else { - mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, mediaExtractor.getSampleTime(), 0); - mediaExtractor.advance(); - } - } - - //dequeue from output buffer - int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIME); - switch (outputIndex) { - case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: - Log.i(TAG, "output format changed..."); - break; - case MediaCodec.INFO_TRY_AGAIN_LATER: - Log.i(TAG, "try again later..."); - break; - case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: - Log.i(TAG, "output buffer changed..."); - break; - default: - //render to surface - mediaCodec.releaseOutputBuffer(outputIndex, true); - break; - } - } - } catch (Exception e) { - Log.e(TAG, "setDataSource error=" + e.toString()); - } - } - } - -} diff --git a/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.kt b/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.kt new file mode 100644 index 0000000..d6991d5 --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.kt @@ -0,0 +1,200 @@ +package com.frank.ffmpeg.hardware + +import android.media.MediaCodec +import android.media.MediaExtractor +import android.media.MediaFormat +import android.os.SystemClock +import android.util.Log +import android.view.Surface + +/** + * Extract by MediaExtractor, decode by MediaCodec, and render to Surface + * Created by frank on 2019/11/16. + */ + +class HardwareDecode(private val mSurface: Surface, private val mFilePath: String, private val mCallback: OnDataCallback?) { + + private var videoDecodeThread: VideoDecodeThread? = null + + interface OnDataCallback { + fun onData(duration: Long) + } + + fun decode() { + videoDecodeThread = VideoDecodeThread() + videoDecodeThread!!.start() + } + + fun seekTo(seekPosition: Long) { + if (videoDecodeThread != null && !videoDecodeThread!!.isInterrupted) { + videoDecodeThread!!.seekTo(seekPosition) + } + } + + fun setPreviewing(previewing: Boolean) { + if (videoDecodeThread != null) { + videoDecodeThread!!.setPreviewing(previewing) + } + } + + fun release() { + if (videoDecodeThread != null && !videoDecodeThread!!.isInterrupted) { + videoDecodeThread!!.interrupt() + videoDecodeThread!!.release() + videoDecodeThread = null + } + } + + private inner class VideoDecodeThread : Thread() { + + private var mediaExtractor: MediaExtractor? = null + + private var mediaCodec: MediaCodec? = null + + private var isPreviewing: Boolean = false + + internal fun setPreviewing(previewing: Boolean) { + this.isPreviewing = previewing + } + + internal fun seekTo(seekPosition: Long) { + try { + if (mediaExtractor != null) { + mediaExtractor!!.seekTo(seekPosition, MediaExtractor.SEEK_TO_CLOSEST_SYNC) + } + } catch (e: IllegalStateException) { + Log.e(TAG, "seekTo error=$e") + } + + } + + internal fun release() { + try { + if (mediaCodec != null) { + mediaCodec!!.stop() + mediaCodec!!.release() + } + if (mediaExtractor != null) { + mediaExtractor!!.release() + } + } catch (e: Exception) { + Log.e(TAG, "release error=$e") + } + + } + + /** + * setting the preview resolution according to video aspect ratio + * + * @param mediaFormat mediaFormat + */ + private fun setPreviewRatio(mediaFormat: MediaFormat?) { + if (mediaFormat == null) { + return + } + val videoWidth = mediaFormat.getInteger(MediaFormat.KEY_WIDTH) + val videoHeight = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT) + val previewRatio: Int + if (videoWidth >= RATIO_1080) { + previewRatio = 10 + } else if (videoWidth >= RATIO_480) { + previewRatio = 6 + } else if (videoWidth >= RATIO_240) { + previewRatio = 4 + } else { + previewRatio = 1 + } + val previewWidth = videoWidth / previewRatio + val previewHeight = videoHeight / previewRatio + Log.e(TAG, "videoWidth=" + videoWidth + "--videoHeight=" + videoHeight + + "--previewWidth=" + previewWidth + "--previewHeight=" + previewHeight) + mediaFormat.setInteger(MediaFormat.KEY_WIDTH, previewWidth) + mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, previewHeight) + } + + override fun run() { + super.run() + + mediaExtractor = MediaExtractor() + var mediaFormat: MediaFormat? = null + var mimeType: String? = "" + try { + mediaExtractor!!.setDataSource(mFilePath) + for (i in 0 until mediaExtractor!!.trackCount) { + mediaFormat = mediaExtractor!!.getTrackFormat(i) + mimeType = mediaFormat!!.getString(MediaFormat.KEY_MIME) + if (mimeType != null && mimeType.startsWith("video/")) { + mediaExtractor!!.selectTrack(i) + break + } + } + if (mediaFormat == null || mimeType == null) { + return + } + val width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH) + val height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT) + val duration = mediaFormat.getLong(MediaFormat.KEY_DURATION) + mCallback?.onData(duration) + Log.i(TAG, "width=$width--height=$height--duration==$duration") + + //setting preview resolution + setPreviewRatio(mediaFormat) + Log.i(TAG, "mediaFormat=$mediaFormat") + + //config MediaCodec, and start + mediaCodec = MediaCodec.createDecoderByType(mimeType) + mediaCodec!!.configure(mediaFormat, mSurface, null, 0) + mediaCodec!!.start() + val bufferInfo = MediaCodec.BufferInfo() + val inputBuffers = mediaCodec!!.inputBuffers + + while (!isInterrupted) { + if (!isPreviewing) { + SystemClock.sleep(SLEEP_TIME.toLong()) + continue + } + //dequeue from input buffer + val inputIndex = mediaCodec!!.dequeueInputBuffer(DEQUEUE_TIME) + if (inputIndex >= 0) { + val inputBuffer = inputBuffers[inputIndex] + val sampleSize = mediaExtractor!!.readSampleData(inputBuffer, 0) + //enqueue to input buffer + if (sampleSize < 0) { + mediaCodec!!.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) + } else { + mediaCodec!!.queueInputBuffer(inputIndex, 0, sampleSize, mediaExtractor!!.sampleTime, 0) + mediaExtractor!!.advance() + } + } + + //dequeue from output buffer + val outputIndex = mediaCodec!!.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIME) + when (outputIndex) { + MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> Log.i(TAG, "output format changed...") + MediaCodec.INFO_TRY_AGAIN_LATER -> Log.i(TAG, "try again later...") + MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> Log.i(TAG, "output buffer changed...") + else -> + //render to surface + mediaCodec!!.releaseOutputBuffer(outputIndex, true) + } + } + } catch (e: Exception) { + Log.e(TAG, "setDataSource error=$e") + } + + } + } + + companion object { + + private val TAG = HardwareDecode::class.java.simpleName + + private const val DEQUEUE_TIME = (10 * 1000).toLong() + private const val SLEEP_TIME = 10 + + private const val RATIO_1080 = 1080 + private const val RATIO_480 = 480 + private const val RATIO_240 = 240 + } + +}