FFplay 播放器的命令行是可以指定多个视频滤镜,然后按 w 键切换查看效果的,命令如下:

# linux 命令
ffplay.exe -x 400 \
-vf "drawtext=fontsize=200:fontfile=FreeSerif.ttf:text='FFmpeg':x=150:y=100" \
-vf "drawtext=fontsize=200:fontfile=FreeSerif.ttf:text='Principle':x=150:y=100" \
-i juren.mp4
# windows 命令
ffplay.exe -x 400 ^
-vf "drawtext=fontsize=200:fontfile=FreeSerif.ttf:text='FFmpeg':x=150:y=100" ^
-vf "drawtext=fontsize=200:fontfile=FreeSerif.ttf:text='Principle':x=150:y=100" ^
-i juren.mp4

扩展知识:Linux 里面连接两行命令用的是 \ 斜杠符号,而 Windows 系统连接两行命令用的是 ^ 平方符号。

FreeSerif.ttf 是字体文件,如果没有这个字体文件,请网上下载保存到跟 ffplay.exe 同样的目录

上面的命令定义了两个视频滤镜,这两个滤镜都是往视频加一个文字水印,那是不是两个滤镜都会生效呢?

不是,只会默认第一个滤镜生效,但是你可以按 w 键切换到第二个滤镜,效果如下:

FFplay视频滤镜分析_FFplay

FFplay视频滤镜分析_FFplay_02


下面来讲一下 ffpaly.c 里面是怎么解析 -vf 参数的,如下:

FFplay视频滤镜分析_FFplay_03

可以看到,是调的 opt_add_vfilter() 来处理 -vf 命令行参数的。

opt_add_vfilter() 函数的代码如下:

static const char **vfilters_list = NULL;
static int opt_add_vfilter(void *optctx, const char *opt, const char *arg)
{
    GROW_ARRAY(vfilters_list, nb_vfilters);
    vfilters_list[nb_vfilters - 1] = arg;
    return 0;
}

vfilters_list 是一个二级指针,GROW_ARRAY() 是 ffmpeg 封装的函数,可以动态扩容。


我们再来看一下处理 w 键事件的逻辑,如下:

FFplay视频滤镜分析_FFplay_04

w 键的逻辑是,切换到下一个视频滤镜,如果没有下一个视频滤镜了,就切换到音频波形图。

当 cur_stream->vfilter_idx 产生变化的时候,就会触发重新配置视频滤镜的逻辑,如下:

FFplay视频滤镜分析_FFplay_05

上图的 is->vfilter_idx 其实就是 cur_stream->vfilter_idxffplay 有些函数会把 is 这个名称换成 cur_stream,实际上他们是同一个指针。


configure_video_filters() 函数比较简单,所以只讲一个重点,就是它里面有一个 ffmpeg 像素格式到 SDL 像素格式的映射过程,这个如果你的项目也用到,可以抄过去这个表。

FFplay视频滤镜分析_FFplay_06

static const struct TextureFormatEntry {
    enum AVPixelFormat format;
    int texture_fmt;
} sdl_texture_format_map[] = {
    { AV_PIX_FMT_RGB8,           SDL_PIXELFORMAT_RGB332 },
    { AV_PIX_FMT_RGB444,         SDL_PIXELFORMAT_RGB444 },
    { AV_PIX_FMT_RGB555,         SDL_PIXELFORMAT_RGB555 },
    { AV_PIX_FMT_BGR555,         SDL_PIXELFORMAT_BGR555 },
    { AV_PIX_FMT_RGB565,         SDL_PIXELFORMAT_RGB565 },
    { AV_PIX_FMT_BGR565,         SDL_PIXELFORMAT_BGR565 },
    { AV_PIX_FMT_RGB24,          SDL_PIXELFORMAT_RGB24 },
    { AV_PIX_FMT_BGR24,          SDL_PIXELFORMAT_BGR24 },
    { AV_PIX_FMT_0RGB32,         SDL_PIXELFORMAT_RGB888 },
    { AV_PIX_FMT_0BGR32,         SDL_PIXELFORMAT_BGR888 },
    { AV_PIX_FMT_NE(RGB0, 0BGR), SDL_PIXELFORMAT_RGBX8888 },
    { AV_PIX_FMT_NE(BGR0, 0RGB), SDL_PIXELFORMAT_BGRX8888 },
    { AV_PIX_FMT_RGB32,          SDL_PIXELFORMAT_ARGB8888 },
    { AV_PIX_FMT_RGB32_1,        SDL_PIXELFORMAT_RGBA8888 },
    { AV_PIX_FMT_BGR32,          SDL_PIXELFORMAT_ABGR8888 },
    { AV_PIX_FMT_BGR32_1,        SDL_PIXELFORMAT_BGRA8888 },
    { AV_PIX_FMT_YUV420P,        SDL_PIXELFORMAT_IYUV },
    { AV_PIX_FMT_YUYV422,        SDL_PIXELFORMAT_YUY2 },
    { AV_PIX_FMT_UYVY422,        SDL_PIXELFORMAT_UYVY },
    { AV_PIX_FMT_NONE,           SDL_PIXELFORMAT_UNKNOWN },
};

configure_video_filters() 函数最后会配置 is->in_video_filter 跟 is->out_video_filter,就是入口滤镜跟出口滤镜,只需要把解码出来的视频帧往 in_video_filter 丢,然后再从 out_video_filter 读就行了。

更多滤镜函数的用法,请阅读 《FFmpeg实战之路》 一章。