ffmpeg ffplay.c 源码分析一:ffplay 框架分析,主要数据结构分析

1 ffplay.c的意义

ffplay.c是FFmpeg源码⾃带的播放器,调⽤FFmpeg和SDL API实现⼀个⾮常有⽤的播放器。
例如哔哩哔哩著名开源项⽬ijkplayer也是基于ffplay.c进⾏⼆次开发。
ffplay实现了播放器的主体功能,掌握其原理对于我们独⽴开发播放器⾮常有帮助。

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值