视频抽帧的实现方式是 seek+解码 的结合,在剪辑软件和播放器中都存在不少应用场景,比如剪辑软件导入视频后展示的封面图、视频时间轴等
(剪映导入演示视频oceans.mp4)
本篇文章基于之前的Demo工程实现一个抽帧的utils并仿照系统相册展示一个视频缩略图轨道
(系统相册导入演示视频oceans.mp4)
抽帧实现
FFMpegUtils.kt
对外工具类
object FFMpegUtils {
interface VideoFrameArrivedInterface {
* @param duration
* 给定视频时长,返回待抽帧的pts arr,单位为s
fun onFetchStart(duration: Double): DoubleArray
* 每抽帧一次回调一次
fun onProgress(frame: ByteBuffer, timestamps: Double, width: Int, height: Int, index: Int): Boolean
* 抽帧动作结束
fun onFetchEnd()
fun getVideoFrames(path: String,
width: Int,
height: Int,
cb: VideoFrameArrivedInterface) {
getVideoFramesCore(path, width, height, cb)
private external fun getVideoFramesCore(path: String,
width: Int,
height: Int,
cb: VideoFrameArrivedInterface)
}
FFReader.h
封装一个Reader基类,用于读取音频、视频avpacket
#ifndef FFMPEGDEMO_FFREADER_H
#define FFMPEGDEMO_FFREADER_H
#include <string>
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
enum TrackType {
Track_Video,
Track_Audio
* read AVPacket class
class FFReader {
public:
FFReader();
virtual ~FFReader();
bool init(std::string &path);
bool selectTrack(TrackType type);
int fetchAvPacket(AVPacket *pkt);
bool isKeyFrame(AVPacket *pkt);
* 获取timestamp对应的关键帧index,基于BACKWARD
* @param timestamp: 时间单位s
* @return
int getKeyFrameIndex(int64_t timestamp);
double getDuration();
* seek
* @param timestamp: 时间单位s
void seek(int64_t timestamp);
void flush();
void release();
private:
// ....略
#endif //FFMPEGDEMO_FFREADER_H
FFVideoReader.h
继承自FFReader,负责解码视频帧、resize、格式转化(通过libyuv统一输出RGBA数据)等
#ifndef FFMPEGDEMO_FFVIDEOREADER_H
#define FFMPEGDEMO_FFVIDEOREADER_H
#include "FFReader.h"
class FFVideoReader: public FFReader{
public:
FFVideoReader(std::string &path);
~FFVideoReader();
void getFrame(int64_t pts, int width, int height, uint8_t *buffer);
private:
// ...略