Android FFmpeg系列09--抽帧与快速抽帧

2022-11-19 09:57:09 浏览数 (1)

视频抽帧的实现方式是seek 解码的结合,在剪辑软件和播放器中都存在不少应用场景,比如剪辑软件导入视频后展示的封面图、视频时间轴等

(剪映导入演示视频oceans.mp4)

本篇文章基于之前的Demo工程实现一个抽帧的utils并仿照系统相册展示一个视频缩略图轨道

(系统相册导入演示视频oceans.mp4)

抽帧实现

FFMpegUtils.kt

对外工具类

代码语言:javascript复制
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

代码语言:javascript复制
#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数据)等

代码语言:javascript复制
#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的调用

快速抽帧

不同的应用场景有不同的优化思路,针对上面的视频缩略图抽帧的场景,我们的优化方向有两个

  • 缩略图size小,那么可以充分利用缓存
  • 抽帧的时间戳是可预测的,那么可以利用预解码、多解码器分段解码、seek跳过非参考帧等手段;

当然这里只提思路,具体的实现在Demo工程中就没有提供啦,最终的Demo效果如下

0 人点赞