parent
7aa92655e2
commit
7c4b591ef3
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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> |
Loading…
Reference in new issue