parent
c668a359a5
commit
48f8bd8018
@ -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()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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 |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue