内容如题:
这里使用了安卓自带的类(MediaMetadataRetriever)实现:
(1)既然做一个工具栏,首先要判断能否读取视频,换而言之,就是视频文件的判断。
(2)获取视频帧,外部调用,一般是两种,一个就是时长间隔获取,一个就是数量多少来获取,本质上,还是一个时长间隔的判断,这里注意时长单位以及精度问题。
(3)获取了图片,则需要保存,保存的时候,要注意安卓系统版本引起的问题。
(4)获取图片的过程,是否需要多线程支持,多线程支持又如何处理等细节。
(5)内存的释放,避免内存泄漏。
核心代码如下图:
package com.marvhong.videoeditor.snap.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.text.TextUtils;
import android.util.Log;
import com.marvhong.videoeditor.snap.thread.LibVideoExecutorsManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* FileName: VideoSnapUtils
* Author: lzt
* Date: 2022/4/15 14:39
* 视频截图工具栏
*/
public class VideoSnapUtils implements Serializable {
private final String TAG = VideoSnapUtils.class.getSimpleName();
private VideoSnapListener mListener;
private Context mContext;
private final String mRootPath = "LibVideoSnap";
//多线程数量
private final int THREAD_SIZE = 3;
public VideoSnapUtils(Context context) {
this.mContext = context;
}
//内部func-------------------------------------------------------------------------------------
public List> splitList(List list, int groupSize) {
if (list.size() <= groupSize) {
List> newList = new ArrayList<>(1);
newList.add(list);
return newList;
}
int length = list.size();
// 计算可以分成多少组
int num = (length + groupSize - 1) / groupSize;
List> newList = new ArrayList<>(num);
for (int i = 0; i < num; i++) {
// 开始位置
int fromIndex = i * groupSize;
// 结束位置
int toIndex = (i + 1) * groupSize < length ? (i + 1) * groupSize : length;
newList.add(list.subList(fromIndex, toIndex));
}
return newList;
}
private String saveBitmap(Bitmap bm, String path, String name) throws Exception {
Log.e(TAG, "保存图片");
File pathFile = new File(path);
if (!pathFile.exists()) {
createFile(pathFile, false);
}
File file = new File(path + name);
if (!file.exists()) {
createFile(file, true);
}
FileOutputStream out = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
return path + name;
}
private void createFile(File file, boolean isFile) throws Exception {// 创建文件
if (!file.exists()) {// 如果文件不存在
if (!file.getParentFile().exists()) {// 如果文件父目录不存在
createFile(file.getParentFile(), false);
} else {// 存在文件父目录
if (isFile) {// 创建文件
file.createNewFile();// 创建新文件
} else {
file.mkdir();// 创建目录
}
}
}
}
/**
* 视频获取截图
* 处理特殊情况,interval或者duration为0的时候
*/
private void startSnap(String path, long duration, long interval) throws Exception {
String parentPath = mContext.getExternalCacheDir().getPath() + "/" + mRootPath + "/";
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
final List resultList = new ArrayList<>();
if ((duration == 0 || interval == 0) || (interval >= duration)) {
//获取视频第一帧
if (mListener != null) {
mListener.start(1);
}
Bitmap firstBit = retriever.getFrameAtTime();
retriever.release();
String firstSavePath = saveBitmap(firstBit, parentPath, System.currentTimeMillis() + ".png");
if (!TextUtils.isEmpty(firstSavePath)) {
resultList.add(firstSavePath);
if (mListener != null) {
mListener.progress(1, 1, firstSavePath);
mListener.finish(resultList);
}
} else {
if (mListener != null) {
mListener.error("save error path is empty");
}
}
if (!firstBit.isRecycled()) {
firstBit.recycle();
}
return;
}
//计算出每个截图的位置
long rest = duration % interval;
final int totalCount;
if (rest > 0) {
totalCount = (int) (duration / interval) + 1;
} else {
totalCount = (int) (duration / interval);
}
if (mListener != null) {
mListener.start(totalCount);
}
List snapTimeList = new ArrayList<>();
for (int i = 0; i < totalCount; i++) {
snapTimeList.add(i * interval * 1000);
}
//分割list--多线程
long startTime = System.currentTimeMillis();
AtomicInteger countSize = new AtomicInteger(0);
List> splitList = splitList(snapTimeList, THREAD_SIZE);
for (List cache : splitList) {
LibVideoExecutorsManager.getInstance().getCacheExecutors(TAG).execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < cache.size(); i++) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
try {
Bitmap cacheBitmap = retriever.getFrameAtTime(cache.get(i), MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
String savePath = saveBitmap(cacheBitmap, parentPath, System.currentTimeMillis() + String.valueOf(i) + ".png");
if (!cacheBitmap.isRecycled()) {
cacheBitmap.recycle();
}
if (!TextUtils.isEmpty(savePath)) {
resultList.add(savePath);
if (mListener != null) {
if (cache.get(i) == 0) {
mListener.progress(totalCount, 1, savePath);
} else {
mListener.progress(totalCount, (int) (cache.get(i) / 1000 / interval), savePath);
}
if (countSize.incrementAndGet() == totalCount) {
mListener.finish(resultList);
Log.i("snapVideo", "finish time: " + (System.currentTimeMillis() -startTime));
}
}
} else {
throw new Exception("save pic error,path is save failed");
}
retriever.release();
} catch (Exception e) {
retriever.release();
if (mListener != null) {
mListener.error("save error path is empty");
if (countSize.incrementAndGet() == totalCount) {
mListener.finish(resultList);
Log.i("snapVideo", "finish time: " + (System.currentTimeMillis() -startTime));
}
}
break;
}
}
}
});
}
}
//外部func-------------------------------------------------------------------------------------
/**
* 截图
*
* @param path 视频路径
* @param interval 时间间距
*/
public void startByInterval(String path, long interval) {
LibVideoExecutorsManager.getInstance().getCacheExecutors(TAG).execute(new Runnable() {
@Override
public void run() {
try {
//根据数量,计算间隔时长
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
long duration = Long.parseLong(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
retriever.release();
startSnap(path, duration, interval);
} catch (Exception e) {
if (mListener != null) {
mListener.error(e.getMessage());
}
}
}
});
}
/**
* 截图
*
* @param path 视频路径
* @param count 图片数量
*/
public void startByCount(String path, int count) {
LibVideoExecutorsManager.getInstance().getCacheExecutors(TAG).execute(new Runnable() {
@Override
public void run() {
try {
//根据数量,计算间隔时长
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
long duration = Long.parseLong(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
retriever.release();
long interval = duration / count;
startSnap(path, duration, interval);
} catch (Exception e) {
if (mListener != null) {
mListener.error(e.getMessage());
}
}
}
});
}
/**
* 停止
*/
public void stop() {
removeSnapListener();
LibVideoExecutorsManager.getInstance().closeCacheExecutors(TAG);
}
/**
* 监听
*/
public VideoSnapUtils setOnSnapListener(VideoSnapListener listener) {
this.mListener = listener;
return this;
}
private void removeSnapListener() {
this.mListener = null;
}
}
LibVideoExecutorsManager–就是线程池管理类
VideoSnapListener–就是监听类
that’s all--------------------------------------------------------------------------
项目地址