關(guān)于安卓獲取視頻幀圖片工具類實(shí)現(xiàn)

內(nèi)容如題:

代碼在文末:

核心使用:

這里使用了安卓自帶的類(MediaMetadataRetriever)實(shí)現(xiàn):

思路與細(xì)節(jié):

(1)既然做一個(gè)工具欄,首先要判斷能否讀取視頻,換而言之,就是視頻文件的判斷。
(2)獲取視頻幀,外部調(diào)用,一般是兩種,一個(gè)就是時(shí)長(zhǎng)間隔獲取,一個(gè)就是數(shù)量多少來(lái)獲取,本質(zhì)上,還是一個(gè)時(shí)長(zhǎng)間隔的判斷,這里注意時(shí)長(zhǎng)單位以及精度問(wèn)題。
(3)獲取了圖片,則需要保存,保存的時(shí)候,要注意安卓系統(tǒng)版本引起的問(wèn)題。
(4)獲取圖片的過(guò)程,是否需要多線程支持,多線程支持又如何處理等細(xì)節(jié)。
(5)內(nèi)存的釋放,避免內(nèi)存泄漏。

核心代碼如下圖:

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";
    //多線程數(shù)量
    private final int THREAD_SIZE = 3;

    public VideoSnapUtils(Context context) {
        this.mContext = context;
    }


    //內(nèi)部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();
        // 計(jì)算可以分成多少組
        int num = (length + groupSize - 1) / groupSize;
        List<List<T>> newList = new ArrayList<>(num);
        for (int i = 0; i < num; i++) {
            // 開始位置
            int fromIndex = i * groupSize;
            // 結(jié)束位置
            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 {// 創(chuàng)建文件
        if (!file.exists()) {// 如果文件不存在
            if (!file.getParentFile().exists()) {// 如果文件父目錄不存在
                createFile(file.getParentFile(), false);
            } else {// 存在文件父目錄
                if (isFile) {// 創(chuàng)建文件
                    file.createNewFile();// 創(chuàng)建新文件
                } else {
                    file.mkdir();// 創(chuàng)建目錄
                }
            }
        }
    }

    /**
     * 視頻獲取截圖
     * 處理特殊情況,interval或者duration為0的時(shí)候
     */
    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;
        }
        //計(jì)算出每個(gè)截圖的位置
        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 時(shí)間間距
     */
    public void startByInterval(String path, long interval) {
        LibVideoExecutorsManager.getInstance().getCacheExecutors(TAG).execute(new Runnable() {
            @Override
            public void run() {
                try {
                    //根據(jù)數(shù)量,計(jì)算間隔時(shí)長(zhǎng)
                    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 圖片數(shù)量
     */
    public void startByCount(String path, int count) {
        LibVideoExecutorsManager.getInstance().getCacheExecutors(TAG).execute(new Runnable() {
            @Override
            public void run() {
                try {
                    //根據(jù)數(shù)量,計(jì)算間隔時(shí)長(zhǎng)
                    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);
    }


    /**
     * 監(jiān)聽
     */
    public VideoSnapUtils setOnSnapListener(VideoSnapListener listener) {
        this.mListener = listener;
        return this;
    }

    private void removeSnapListener() {
        this.mListener = null;
    }

}

LibVideoExecutorsManager--就是線程池管理類
VideoSnapListener--就是監(jiān)聽類

that's all--------------------------------------------------------------------------
項(xiàng)目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容