视频抽帧的实现方式是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:
// ...略
};
#endif //FFMPEGDEMO_FFVIDEOREADER_H
限于文章篇幅就没有贴具体的实现代码了,感兴趣的同学可以参考提交到github的源码
在MainActity的调用
快速抽帧
不同的应用场景有不同的优化思路,针对上面的视频缩略图抽帧的场景,我们的优化方向有两个
当然这里只提思路,具体的实现在Demo工程中就没有提供啦,最终的Demo效果如下