1 ffplay.c的意义
2 FFplay框架分析
ffplay.c 重要逻辑解析
1.init_dynload函数分析
SetDllDirectory
///参考windows 的解释,这个是为了防止dll攻击中的一个步骤
https://support.microsoft.com/zh-cn/topic/%E5%AE%89%E5%85%A8%E5%8A%A0%E8%BD%BD%E5%BA%93%E4%BB%A5%E9%98%B2%E6%AD%A2-dll-%E9%A2%84%E5%8A%A0%E8%BD%BD%E6%94%BB%E5%87%BB-d41303ec-0748-9211-f317-2edc819682e1
#ifdef _WIN32
/* Calling SetDllDirectory with the empty string
(but not NULL) removes the current working directory
from the DLL search path as a security pre-caution. */
SetDllDirectory("");
#endif
2.设置日志标志,特别是AV_LOG_SKIP_REPEATED,以跳过重复的日志消息
///hunnadede av_log_set_flags函数,
该函数用于设置日志标志,特别是AV_LOG_SKIP_REPEATED,以跳过重复的日志消息
av_log_set_flags(AV_LOG_SKIP_REPEATED);
3 参数解析:parse_loglevel(argc, argv, options);
参数代表的是我们在执行代码时候的 传递的参数,类似于
argc,参数个数 。
argv,参数char **argv 。
options 支持的所有ffplay 的额外的参数 。
我们可以看到options 的代表的:OptionDef options[]
OptionDef的数据结构为:
typedef struct OptionDef {
const char *name;
int flags;
#define HAS_ARG 0x0001
#define OPT_BOOL 0x0002
#define OPT_EXPERT 0x0004
#define OPT_STRING 0x0008
#define OPT_VIDEO 0x0010
#define OPT_AUDIO 0x0020
#define OPT_INT 0x0080
#define OPT_FLOAT 0x0100
#define OPT_SUBTITLE 0x0200
#define OPT_INT64 0x0400
#define OPT_EXIT 0x0800
#define OPT_DATA 0x1000
#define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only).
implied by OPT_OFFSET or OPT_SPEC */
#define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */
#define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt.
Implies OPT_OFFSET. Next element after the offset is
an int containing element count in the array. */
#define OPT_TIME 0x10000
#define OPT_DOUBLE 0x20000
#define OPT_INPUT 0x40000
#define OPT_OUTPUT 0x80000
union {
void *dst_ptr;
int (*func_arg)(void *, const char *, const char *);
size_t off;
} u;
const char *help;
const char *argname;
} OptionDef;
options的所有值为:
static const OptionDef options[] = {
{ "L", OPT_EXIT, { .func_arg = show_license }, "show license" }, \
{ "h", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "?", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "-help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "version", OPT_EXIT, { .func_arg = show_version }, "show version" }, \
{ "buildconf", OPT_EXIT, { .func_arg = show_buildconf }, "show build configuration" }, \
{ "formats", OPT_EXIT, { .func_arg = show_formats }, "show available formats" }, \
{ "muxers", OPT_EXIT, { .func_arg = show_muxers }, "show available muxers" }, \
{ "demuxers", OPT_EXIT, { .func_arg = show_demuxers }, "show available demuxers" }, \
{ "devices", OPT_EXIT, { .func_arg = show_devices }, "show available devices" }, \
{ "codecs", OPT_EXIT, { .func_arg = show_codecs }, "show available codecs" }, \
{ "decoders", OPT_EXIT, { .func_arg = show_decoders }, "show available decoders" }, \
{ "encoders", OPT_EXIT, { .func_arg = show_encoders }, "show available encoders" }, \
{ "bsfs", OPT_EXIT, { .func_arg = show_bsfs }, "show available bit stream filters" }, \
{ "protocols", OPT_EXIT, { .func_arg = show_protocols }, "show available protocols" }, \
{ "filters", OPT_EXIT, { .func_arg = show_filters }, "show available filters" }, \
{ "pix_fmts", OPT_EXIT, { .func_arg = show_pix_fmts }, "show available pixel formats" }, \
{ "layouts", OPT_EXIT, { .func_arg = show_layouts }, "show standard channel layouts" }, \
{ "sample_fmts", OPT_EXIT, { .func_arg = show_sample_fmts }, "show available audio sample formats" }, \
{ "colors", OPT_EXIT, { .func_arg = show_colors }, "show available color names" }, \
{ "loglevel", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \
{ "v", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \
{ "report", 0, { (void*)opt_report }, "generate a report" }, \
{ "max_alloc", HAS_ARG, { .func_arg = opt_max_alloc }, "set maximum size of a single allocated block", "bytes" }, \
{ "cpuflags", HAS_ARG | OPT_EXPERT, { .func_arg = opt_cpuflags }, "force specific cpu flags", "flags" }, \
{ "hide_banner", OPT_BOOL | OPT_EXPERT, {&hide_banner}, "do not show program banner", "hide_banner" }, \
{ "sources" , OPT_EXIT | HAS_ARG, { .func_arg = show_sources }, \
"list sources of the input device", "device" }, \
{ "sinks" , OPT_EXIT | HAS_ARG, { .func_arg = show_sinks }, \
"list sinks of the output device", "device" },
{ "x", HAS_ARG, { .func_arg = opt_width }, "force displayed width", "width" },
{ "y", HAS_ARG, { .func_arg = opt_height }, "force displayed height", "height" },
{ "s", HAS_ARG | OPT_VIDEO, { .func_arg = opt_frame_size }, "set frame size (WxH or abbreviation)", "size" },
{ "fs", OPT_BOOL, { &is_full_screen }, "force full screen" },
{ "an", OPT_BOOL, { &audio_disable }, "disable audio" },
{ "vn", OPT_BOOL, { &video_disable }, "disable video" },
{ "sn", OPT_BOOL, { &subtitle_disable }, "disable subtitling" },
{ "ast", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_AUDIO] }, "select desired audio stream", "stream_specifier" },
{ "vst", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_VIDEO] }, "select desired video stream", "stream_specifier" },
{ "sst", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_SUBTITLE] }, "select desired subtitle stream", "stream_specifier" },
{ "ss", HAS_ARG, { .func_arg = opt_seek }, "seek to a given position in seconds", "pos" },
{ "t", HAS_ARG, { .func_arg = opt_duration }, "play \"duration\" seconds of audio/video", "duration" },
{ "bytes", OPT_INT | HAS_ARG, { &seek_by_bytes }, "seek by bytes 0=off 1=on -1=auto", "val" },
{ "seek_interval", OPT_FLOAT | HAS_ARG, { &seek_interval }, "set seek interval for left/right keys, in seconds", "seconds" },
{ "nodisp", OPT_BOOL, { &display_disable }, "disable graphical display" },
{ "noborder", OPT_BOOL, { &borderless }, "borderless window" },
{ "alwaysontop", OPT_BOOL, { &alwaysontop }, "window always on top" },
{ "volume", OPT_INT | HAS_ARG, { &startup_volume}, "set startup volume 0=min 100=max", "volume" },
{ "f", HAS_ARG, { .func_arg = opt_format }, "force format", "fmt" },
{ "pix_fmt", HAS_ARG | OPT_EXPERT | OPT_VIDEO, { .func_arg = opt_frame_pix_fmt }, "set pixel format", "format" },
{ "stats", OPT_BOOL | OPT_EXPERT, { &show_status }, "show status", "" },
{ "fast", OPT_BOOL | OPT_EXPERT, { &fast }, "non spec compliant optimizations", "" },
{ "genpts", OPT_BOOL | OPT_EXPERT, { &genpts }, "generate pts", "" },
{ "drp", OPT_INT | HAS_ARG | OPT_EXPERT, { &decoder_reorder_pts }, "let decoder reorder pts 0=off 1=on -1=auto", ""},
{ "lowres", OPT_INT | HAS_ARG | OPT_EXPERT, { &lowres }, "", "" },
{ "sync", HAS_ARG | OPT_EXPERT, { .func_arg = opt_sync }, "set audio-video sync. type (type=audio/video/ext)", "type" },
{ "autoexit", OPT_BOOL | OPT_EXPERT, { &autoexit }, "exit at the end", "" },
{ "exitonkeydown", OPT_BOOL | OPT_EXPERT, { &exit_on_keydown }, "exit on key down", "" },
{ "exitonmousedown", OPT_BOOL | OPT_EXPERT, { &exit_on_mousedown }, "exit on mouse down", "" },
{ "loop", OPT_INT | HAS_ARG | OPT_EXPERT, { &loop }, "set number of times the playback shall be looped", "loop count" },
{ "framedrop", OPT_BOOL | OPT_EXPERT, { &framedrop }, "drop frames when cpu is too slow", "" },
{ "infbuf", OPT_BOOL | OPT_EXPERT, { &infinite_buffer }, "don't limit the input buffer size (useful with realtime streams)", "" },
{ "window_title", OPT_STRING | HAS_ARG, { &window_title }, "set window title", "window title" },
{ "left", OPT_INT | HAS_ARG | OPT_EXPERT, { &screen_left }, "set the x position for the left of the window", "x pos" },
{ "top", OPT_INT | HAS_ARG | OPT_EXPERT, { &screen_top }, "set the y position for the top of the window", "y pos" },
#if CONFIG_AVFILTER
{ "vf", OPT_EXPERT | HAS_ARG, { .func_arg = opt_add_vfilter }, "set video filters", "filter_graph" },
{ "af", OPT_STRING | HAS_ARG, { &afilters }, "set audio filters", "filter_graph" },
#endif
{ "rdftspeed", OPT_INT | HAS_ARG| OPT_AUDIO | OPT_EXPERT, { &rdftspeed }, "rdft speed", "msecs" },
{ "showmode", HAS_ARG, { .func_arg = opt_show_mode}, "select show mode (0 = video, 1 = waves, 2 = RDFT)", "mode" },
{ "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, { .func_arg = opt_default }, "generic catch all option", "" },
{ "i", OPT_BOOL, { &dummy}, "read specified file", "input_file"},
{ "codec", HAS_ARG, { .func_arg = opt_codec}, "force decoder", "decoder_name" },
{ "acodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &audio_codec_name }, "force audio decoder", "decoder_name" },
{ "scodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &subtitle_codec_name }, "force subtitle decoder", "decoder_name" },
{ "vcodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &video_codec_name }, "force video decoder", "decoder_name" },
{ "autorotate", OPT_BOOL, { &autorotate }, "automatically rotate video", "" },
{ "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT, { &find_stream_info },
"read and decode the streams to fill missing information with heuristics" },
{ "filter_threads", HAS_ARG | OPT_INT | OPT_EXPERT, { &filter_nbthreads }, "number of filter threads per graph" },
{ NULL, },
};
///hunandede 如果在命令行中有关于log 的参数,该方法是为了将关于log的参数解析出来,那么首先我们就要知道,如果命令中有log相关的参数,那么要解析的命令是啥?
///如何知道 ffplay 有哪些参数呢?我们可以在cmd 上使用 ffplay -h > ffplay_command.txt 将ffplay 命令的help都存储到ffplay_comand.txt中
/// 然后再次搜索 -loglevel,就会发现说明如下
/// -loglevel loglevel set logging level,,,很显然,第二个loglever 应该是一个具体的值,或者常量
/// -v loglevel set logging level,,,很显然,唯一的
/// 那么这个对应的常量,或者具体的值应该怎么填写呢?
/// 查看ffplay 的文档 https://ffmpeg.org/ffplay.html ,然后在文档中search loglevel
这里比较搞笑的是,虽然是文档是ffplay的例子,但是官网上的例子都写的是 ffpmeg开头,猜测这里有两个原因,第一个是ffplay 命令的这些参数对于 ffmpeg 都是适用的,第二个就是文档的发布者有点偷懒,于是试了下ffplay 命令的这些参数和 ffmpeg 的命令是否通用,用了两个还真的适用,不过这样写,真的有点让开发者摸不着头脑。
参考官网的说明 ffplay 在命令的使用应该是如下的:
-loglevel [flags+]loglevel | -v [flags+]loglevel
设置库使用的日志记录级别和标志。
可选前缀可以包含以下值:flags
'repeat'
指示不应将重复的日志输出压缩到第一行 并且 “Last message repeat n times” 行将被省略。
'level'
指示日志输出应为每条消息添加前缀 线。这可以用作对数着色的替代方法,例如,当转储 log 到 file。[level]
标志也可以单独使用,只需添加 '+'/'-' 前缀来设置/重置单个 标志,但不影响其他或更改 .什么时候 同时设置 and ,需要 '+' 分隔符 介于最后一个值和之前 之间。flagsloglevelflagsloglevelflagsloglevel
loglevel是包含以下值之一的字符串或数字:
'quiet, -8'
什么也没显示;沉默。
'panic, 0'
仅显示可能导致进程崩溃的致命错误,例如 断言失败。这目前不用于任何用途。
'fatal, 8'
仅显示致命错误。这些是错误,之后过程绝对 无法继续。
'error, 16'
显示所有错误,包括可以从中恢复的错误。
'warning, 24'
显示所有警告和错误。任何可能与 将显示不正确或意外的事件。
'info, 32'
在处理过程中显示信息性消息。这是对 warnings 和 errors。这是默认值。
'verbose, 40'
与 相同,但更详细。info
'debug, 48'
显示所有内容,包括调试信息。
'trace, 56'
例如,要启用重复日志输出,请添加前缀,并设置loglevel为 :verbose
//这里ffplay 的例子,官网用的是 ffmpeg,我们换成ffplay测试一下
ffmpeg -loglevel repeat+level+verbose -i input output
ffplay -loglevel repeat+level+verbose D:\yinshipinres\source.200kbps.768x320.flv
///如果我们不想看到任何log
ffplay -loglevel "quiet" D:\yinshipinres\source.200kbps.768x320.flv
ffplay -loglevel "-8" D:\yinshipinres\source.200kbps.768x320.flv
ffplay -loglevel quiet D:\yinshipinres\source.200kbps.768x320.flv
ffplay -loglevel -8 D:\yinshipinres\source.200kbps.768x320.flv
注意这里要用的是双引号,或者没有任何符号
如下命令 的截图
ffplay -loglevel repeat+level+verbose D:\yinshipinres\source.200kbps.768x320.flv
C:\Users\Administrator>ffplay -loglevel repeat+level+verbose D:\yinshipinres\source.200kbps.768x320.flv
[info] ffplay version 6.0-full_build-www.gyan.dev Copyright (c) 2003-2023 the FFmpeg developers
[info] built with gcc 12.2.0 (Rev10, Built by MSYS2 project)
[info] configuration: --enable-gpl --enable-version3 --enable-shared --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libaribb24 --enable-libdav1d --enable-libdavs2 --enable-libuavs3d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libaom --enable-libjxl --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libvpl --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
[info] libavutil 58. 2.100 / 58. 2.100
[info] libavcodec 60. 3.100 / 60. 3.100
[info] libavformat 60. 3.100 / 60. 3.100
[info] libavdevice 60. 1.100 / 60. 1.100
[info] libavfilter 9. 3.100 / 9. 3.100
[info] libswscale 7. 1.100 / 7. 1.100
[info] libswresample 4. 10.100 / 4. 10.100
[info] libpostproc 57. 1.100 / 57. 1.100
[verbose] Initialized direct3d renderer.
[h264 @ 00000181a6fe0880] [verbose] Reinit context to 768x320, pix_fmt: yuv420p
[info] Input #0, flv, from 'D:\yinshipinres\source.200kbps.768x320.flv':
[info] Metadata:
[info] major_brand : isom
[info] minor_version : 512
[info] compatible_brands: isomiso2avc1mp41
[info] encoder : Lavf54.63.104
[info] Duration: 00:03:30.73, start: 0.034000, bitrate: 251 kb/s
[info] Stream #0:0: Video: h264 (High), 1 reference frame, yuv420p(progressive, left), 768x320 [SAR 1:1 DAR 12:5], 212 kb/s, 25 fps, 25 tbr, 1k tbn
[info] Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 30 kb/s
[ffplay_abuffer @ 00000181b8736300] [verbose] tb:1/44100 samplefmt:fltp samplerate:44100 chlayout:stereo
[ffplay_abuffersink @ 00000181b8735000] [verbose] auto-inserting filter 'auto_aresample_0' between the filter 'ffplay_abuffer' and the filter 'ffplay_abuffersink'
[auto_aresample_0 @ 00000181b8735200] [verbose] ch:2 chl:stereo fmt:fltp r:44100Hz -> ch:2 chl:stereo fmt:s16 r:44100Hz
[h264 @ 00000181a703ac80] [verbose] Reinit context to 768x320, pix_fmt: yuv420p
[ffplay_abuffer @ 00000181b8735b00] [verbose] tb:1/44100 samplefmt:fltp samplerate:44100 chlayout:stereo
[ffplay_abuffersink @ 00000181b8736100] [verbose] auto-inserting filter 'auto_aresample_0' between the filter 'ffplay_abuffer' and the filter 'ffplay_abuffersink'
[auto_aresample_0 @ 00000181b8734e00] [verbose] ch:2 chl:stereo fmt:fltp r:44100Hz -> ch:2 chl:stereo fmt:s16 r:44100Hz
[ffplay_buffer @ 00000181b8734f00] [verbose] w:768 h:320 pixfmt:yuv420p tb:1/1000 fr:25/1 sar:1/1
[verbose] Created 768x320 texture with SDL_PIXELFORMAT_IYUV.
[info] 8.45 A-V: -0.011 fd= 26 aq= 6KB vq= 24KB sq= 0B f=0/0
///如果我们不想看到任何log,如下命令的截图
ffplay -loglevel "quiet" D:\yinshipinres\source.200kbps.768x320.flv
ffplay -loglevel "-8" D:\yinshipinres\source.200kbps.768x320.flv
我们知道了关于loglevel 的相关操作,那么再来看代码是怎么写的。
void parse_loglevel(int argc, char **argv, const OptionDef *options)
{
///查看命令中是否有loglevel 的参数,如果有,则返回 i,i 的值是在 OptionDef[]中的位置,,如果没有则返回0
int idx = locate_option(argc, argv, options, "loglevel");
const char *env;
//这个是给ffmpeg 使用的
check_options(options);
///这个还是for loglevel 的另一种写法,-loglevel [flags+]loglevel | -v [flags+]loglevel
/// 这里给挨着的 两个只有一行代码的if 加上大括号,是为了方便写注释
if (!idx){
idx = locate_option(argc, argv, options, "v");
}
if (idx && argv[idx + 1]){
/// 如果真的有 log,那么就执行 opt_loglevel函数,opt_loglevel函数的主要作用是 av_log_set_flags 和 av_log_set_level
opt_loglevel(NULL, "loglevel", argv[idx + 1]);
}
///这个是看是否有report,可以参考上面的代码,看一下再在cmd中如果有 report 是如何操作的
idx = locate_option(argc, argv, options, "report");
if ((env = getenv("FFREPORT")) || idx) {
init_report(env);
if (report_file) {
int i;
fprintf(report_file, "Command line:\n");
for (i = 0; i < argc; i++) {
dump_argument(argv[i]);
fputc(i < argc - 1 ? ' ' : '\n', report_file);
}
fflush(report_file);
}
}
///hide_banner,的含义是 是否显示 ffmpeg build的详细信息。因此如果我们不想显示详细信息,在这里可以直接将hide_banner设置为1
idx = l