关于安卓获取视频帧图片工具类实现

内容如题:

代码在文末:

核心使用:

这里使用了安卓自带的类(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--------------------------------------------------------------------------
项目地址

你可能感兴趣的:(安卓实战,android)