拖动视频小图预览

拖动视频小图预览
pull/107/head
xufulong 5 years ago
parent 7aa92655e2
commit 7c4b591ef3
  1. 7
      app/src/main/AndroidManifest.xml
  2. 6
      app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
  3. 158
      app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.java
  4. 174
      app/src/main/java/com/frank/ffmpeg/hardware/HardwareDecode.java
  5. 79
      app/src/main/java/com/frank/ffmpeg/util/TimeUtil.java
  6. 9
      app/src/main/res/layout/activity_main.xml
  7. 40
      app/src/main/res/layout/activity_preview.xml
  8. 2
      app/src/main/res/values/strings.xml

@ -55,10 +55,9 @@
<!--视频倒播-->
<activity android:name=".activity.VideoReverseActivity"
android:screenOrientation="landscape" />
<!--申请悬浮窗权限-->
<activity android:name=".floating.FloatActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"/>
<!--视频播放拖动预览-->
<activity android:name=".activity.VideoPreviewActivity"
android:screenOrientation="portrait" />
</application>

@ -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;
}

@ -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);
}
}

@ -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<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.e(TAG, "width=" + width + "--height=" + height + "--duration==" + duration);
//TODO:重新设置分辨率
mediaFormat.setInteger(MediaFormat.KEY_WIDTH, width/10);
mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, height/10);
Log.e(TAG, "mediaFormat=" + mediaFormat.toString());
//配置MediaCodec,并且start
MediaCodec mediaCodec = MediaCodec.createDecoderByType(mimeType);
mediaCodec.configure(mediaFormat, mSurface, null, 0);
mediaCodec.start();
long startTime = System.currentTimeMillis();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
boolean isEof = false;
while (!isInterrupted() && !isEof) {
//从缓冲区取出一个缓冲块,如果当前无可用缓冲块,返回inputIndex<0
int inputIndex = mediaCodec.dequeueInputBuffer(DEQUEUE_TIME);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputIndex];
int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);
//入队列
if (sampleSize < 0) {
isEof = true;
mediaCodec.queueInputBuffer(inputIndex,0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, mediaExtractor.getSampleTime(), 0);
mediaExtractor.advance();
}
}
//出队列
int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIME);
switch (outputIndex) {
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.e(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.e(TAG, "output buffer changed...");
break;
default:
// long time = (System.currentTimeMillis()-startTime);
// if (time > 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());
}
}
}
}

@ -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);
}
}
}

@ -74,4 +74,13 @@
android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_filter"/>
<Button
android:id="@+id/btn_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video_preview"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_reverse"/>
</RelativeLayout>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.VideoPreviewActivity">
<SurfaceView
android:id="@+id/surface_preview"
android:layout_width="64dp"
android:layout_height="48dp"
android:layout_centerInParent="true"
android:layout_marginBottom="50dp"
android:layout_marginEnd="50dp" />
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="320dp"
android:layout_height="240dp"
android:layout_centerInParent="true"/>
<SurfaceView
android:id="@+id/surface_view_other"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_marginTop="100dp"
android:layout_centerHorizontal="true"
android:layout_below="@+id/surface_view"
android:visibility="gone"/>
<SeekBar
android:id="@+id/preview_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="40dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>

@ -39,7 +39,7 @@
<string name="video_denoise">视频降噪</string>
<string name="video_image">视频转图片</string>
<string name="video_pip">视频画中画</string>
<string name="video_floating">悬浮窗播放</string>
<string name="video_preview">播放预览</string>
<string name="swap">切换</string>
<string name="start">开始</string>

Loading…
Cancel
Save