diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9b628be..3620067 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,10 +55,9 @@ - - + + diff --git a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java index 3d3032b..913e686 100644 --- a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java +++ b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java @@ -30,7 +30,8 @@ public class MainActivity extends BaseActivity { R.id.btn_push, R.id.btn_live, R.id.btn_filter, - R.id.btn_reverse + R.id.btn_reverse, + R.id.btn_preview ); } @@ -62,6 +63,9 @@ public class MainActivity extends BaseActivity { case R.id.btn_reverse://视频倒播 intent.setClass(MainActivity.this, VideoReverseActivity.class); break; + case R.id.btn_preview://视频拖动实时预览 + intent.setClass(MainActivity.this, VideoPreviewActivity.class); + break; default: break; } diff --git a/app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.java new file mode 100644 index 0000000..1418a3c --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.java @@ -0,0 +1,158 @@ +package com.frank.ffmpeg.activity; + +import android.Manifest; +import android.media.MediaPlayer; +import android.os.Build; +import android.os.Environment; +import android.os.Bundle; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.widget.SeekBar; + +import com.frank.ffmpeg.R; +import com.frank.ffmpeg.hardware.HardwareDecode; + +import java.io.IOException; +import androidx.appcompat.app.AppCompatActivity; + +/** + * 视频拖动实时预览 + * Created by frank on 2019/11/16. + */ + +public class VideoPreviewActivity extends AppCompatActivity implements HardwareDecode.OnDataCallback { + + private final static String TAG = VideoPreviewActivity.class.getSimpleName(); + + private final static String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); + private static String PATH1 = ROOT_PATH + "/What.mp4"; +// private final static String PATH2 = ROOT_PATH + "/bird-1080P.mkv"; +// PATH1 = "https://www.apple.com/105/media/cn/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797" + +// "/films/bruce/mac-bruce-tpl-cn-2018_1280x720h.mp4"; + + private SeekBar previewBar; + private HardwareDecode hardwareDecode; + private long duration; + + private MediaPlayer mediaPlayer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_preview); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1234); + } + + initView(); + setListener(); + } + + private void initView() { + previewBar = findViewById(R.id.preview_bar); + + SurfaceView surfaceVideo = findViewById(R.id.surface_view); + setCallback(surfaceVideo); + + SurfaceView surfaceView = findViewById(R.id.surface_preview); + setCallbackAndPlay(PATH1, surfaceView); + +// SurfaceView surfaceViewOther = findViewById(R.id.surface_view_other); +// setCallbackAndPlay(PATH2, surfaceViewOther); + } + + private void setCallback(SurfaceView surfaceView) { + mediaPlayer = new MediaPlayer(); + mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + Log.e(TAG, "onPrepared..."); + mediaPlayer.start(); + } + }); + + surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(SurfaceHolder holder) { + try { + mediaPlayer.setDataSource(PATH1); + mediaPlayer.setSurface(holder.getSurface()); + mediaPlayer.prepareAsync(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + + } + }); + + } + + private void setCallbackAndPlay(final String filePath, SurfaceView surfaceView) { + surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(SurfaceHolder holder) { + + hardwareDecode = new HardwareDecode(holder.getSurface(), filePath, VideoPreviewActivity.this); + hardwareDecode.decode(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + + } + }); + } + + private void setListener() { + previewBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (!fromUser) { + return; + } + previewBar.setProgress(progress); + if (hardwareDecode != null && progress < duration) { + hardwareDecode.seekTo(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + //TODO + if (mediaPlayer != null) { + Log.e(TAG, "onStop progress=" + seekBar.getProgress()); + mediaPlayer.seekTo(seekBar.getProgress()/1000); + } + } + }); + } + + @Override + public void onData(long duration) { + Log.e(TAG,"duration=" + duration); + this.duration = duration; + previewBar.setMax((int) duration); + } + +} diff --git a/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java b/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java new file mode 100644 index 0000000..aff80f4 --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java @@ -0,0 +1,174 @@ +package com.frank.ffmpeg.hardware; + +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.util.Log; +import android.view.Surface; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * 使用MediaExtractor抽帧,MediaCodec解码,然后渲染到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 long SLEEP_TIME = 10; + + 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); + } + } + + private class VideoDecodeThread extends Thread { + + private MediaExtractor mediaExtractor; + + 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()); + } + } + + @Override + public void run() { + super.run(); + + mediaExtractor = new MediaExtractor(); + MediaFormat mediaFormat = null; + String mimeType = ""; + try { + mediaExtractor.setDataSource(mFilePath); + for (int i=0; i 50) { +// Log.e(TAG, "pts=" + bufferInfo.presentationTimeUs + "--time=" + time); +// } +// startTime = System.currentTimeMillis(); +// while (bufferInfo.presentationTimeUs > (System.currentTimeMillis() - startTime)*1000) { +// try { +// Thread.sleep(SLEEP_TIME); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// Log.e(TAG, "error=" + e.toString()); +// } +// } + //渲染到surface + mediaCodec.releaseOutputBuffer(outputIndex, true); + break; + } + + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + isEof = true; + Log.e(TAG, "is end of stream..."); + } + } + + mediaCodec.stop(); + mediaCodec.release(); + mediaExtractor.release(); + } catch (IOException e) { + Log.e(TAG, "setDataSource error=" + e.toString()); + } + + } + } + +} diff --git a/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.java b/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.java new file mode 100644 index 0000000..b0f9243 --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.java @@ -0,0 +1,79 @@ +package com.frank.ffmpeg.util; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * 时间转换工具类 + * Created by frank on 2018/11/12. + */ + +public class TimeUtil { + + private static final String YMDHMS= "yyyy-MM-dd HH:mm:ss"; + + /** + * 时间戳年月日时分秒 + * @param time time + * @return 年月日时分秒 yyyy/MM/dd HH:mm:ss + */ + public static String getDetailTime(long time) { + SimpleDateFormat format = new SimpleDateFormat(YMDHMS, Locale.getDefault()); + Date date = new Date(time); + return format.format(date); + } + + /** + * 时间转为时间戳 + * @param time time + * @return 时间戳 + */ + public static long getLongTime(String time, Locale locale) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(YMDHMS, locale); + try { + Date dt = simpleDateFormat.parse(time); + return dt.getTime(); + } catch (ParseException e) { + e.printStackTrace(); + } + return 0; + } + + private static String addZero(int time){ + if (time >= 0 && time < 10){ + return "0" + time; + }else if(time >= 10){ + return "" + time; + }else { + return ""; + } + } + + /** + * 获取视频时长 + * @param time time + * @return 视频时长 + */ + public static String getVideoTime(long time){ + if (time <= 0) + return null; + time = time / 1000; + int second, minute=0, hour=0; + second = (int)time % 60; + time = time / 60; + if (time > 0){ + minute = (int)time % 60; + hour = (int)time / 60; + } + if (hour > 0){ + return addZero(hour) + ":" + addZero(minute) + ":" + addZero(second); + }else if (minute > 0){ + return addZero(minute) + ":" + addZero(second); + }else { + return "00:" + addZero(second); + } + } + +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9b29e52..d8af25c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -74,4 +74,13 @@ android:layout_centerHorizontal="true" android:layout_below="@+id/btn_filter"/> +