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

简介: 安卓获取视频帧图片工具类

内容如题:

代码在文末:

核心使用:

这里使用了安卓自带的类(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 <T> List<List<T>> splitList(List<T> list, int groupSize) {
        if (list.size() <= groupSize) {
            List<List<T>> newList = new ArrayList<>(1);
            newList.add(list);
            return newList;
        }
        int length = list.size();
        // 计算可以分成多少组
        int num = (length + groupSize - 1) / groupSize;
        List<List<T>> 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<String> 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<Long> 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<List<Long>> splitList = splitList(snapTimeList, THREAD_SIZE);
        for (List<Long> 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--------------------------------------------------------------------------
项目地址

目录
相关文章
|
7月前
|
Android开发 容器
35. 【Android教程】视频页面:ViewPager
35. 【Android教程】视频页面:ViewPager
66 3
|
4月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
78 20
Android经典面试题之图片Bitmap怎么做优化
|
8月前
|
Android开发
Android通过手势(多点)缩放和拖拽图片
Android通过手势(多点)缩放和拖拽图片
63 4
|
8月前
|
Java Android开发
android 下载图片的问题
android 下载图片的问题
52 3
|
5月前
|
数据处理 开发工具 数据安全/隐私保护
Android平台RTMP推送|轻量级RTSP服务|GB28181接入之文字、png图片水印的精进之路
本文探讨了Android平台上推流模块中添加文字与PNG水印的技术演进。自2015年起,为了满足应急指挥及安防领域的需求,逐步发展出三代水印技术:第一代为静态文字与图像水印;第二代实现了动态更新水印内容的能力,例如实时位置与时间信息;至第三代,则优化了数据传输效率,直接使用Bitmap对象传递水印数据至JNI层,减少了内存拷贝次数。这些迭代不仅提升了用户体验和技术效率,也体现了开发者追求极致与不断创新的精神。
|
5月前
|
自然语言处理 定位技术 API
Android经典实战之如何获取图片的经纬度以及如何根据经纬度获取对应的地点名称
本文介绍如何在Android中从图片提取地理位置信息并转换为地址。首先利用`ExifInterface`获取图片内的经纬度,然后通过`Geocoder`将经纬度转为地址。注意操作需在子线程进行且考虑多语言支持。
282 4
|
5月前
|
编解码 监控 API
惊艳登场!揭秘如何在Android平台上轻松玩转GB28181标准,实现多视频通道接入的超实用指南!
【8月更文挑战第14天】GB28181是公共安全视频监控联网的技术标准。本文介绍如何在Android平台上实现该标准下的多视频通道接入。首先准备开发环境,接着引入GB28181 SDK依赖并初始化SDK。实现设备注册与登录后,通过指定不同通道号请求多路视频流。最后,处理接收到的数据并显示给用户。此过程涉及视频解码,需确保应用稳定及良好的用户体验。
113 0
|
5月前
|
XML 前端开发 Android开发
Android经典实战之Kotlin中实现圆角图片和圆形图片
本文介绍两种实现圆角图像视图的方法。第一种是通过自定义Kotlin `AppCompatImageView`,重写`onDraw`方法使用`Canvas`和`Path`进行圆角剪裁。第二种利用Android Material库中的`ShapeableImageView`,简单配置即可实现圆角效果。两种方法均易于实现且提供动态调整圆角半径的功能。
102 0
|
7月前
|
监控 Android开发 数据安全/隐私保护
安卓kotlin JetPack Compose 实现摄像头监控画面变化并录制视频
在这个示例中,开发者正在使用Kotlin和Jetpack Compose构建一个Android应用程序,该程序 能够通过手机后置主摄像头录制视频、检测画面差异、实时预览并将视频上传至FTP服务器的Android应用
|
7月前
|
JSON 编解码 Apache
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
76 1