ffmpeg 代码阅读笔记1/2

ffmpeg 代码阅读笔记1/2
============================================================
0. ffmpeg 主流程
============================================================

ret = ffmpeg_parse_options(argc, argv); //一招完成所有初始化.
if (transcode() < 0) // 一招完成循环处理所有数据.
transcode()退出就结束了.  有点一分为2的思想.

============================================================
甲: ffmpeg_parse_options 分析
============================================================
大致流程为5步:
    ret = split_commandline(&octx, argc, argv, options, groups,FF_ARRAY_ELEMS(groups)); //分隔命令行
    ret = parse_optgroup(NULL, &octx.global_opts);        //分析全局选项
    ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file); // 打开输入文件
    ret = init_complex_filters();                                            // 初始化复杂过滤器
    ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file); //打开输出文件
然后退出.

前言:
ffmpeg 有众多的选项,成千上万, ffmpeg -h full 有上万条输出!
这些选项被分成了几个表.  大致可分为6个表,
全局选项1个表, AVoption选项5个表下辖若干个表,构成树表.
============================================================
一. split_commandline 分析,
============================================================
split_commandline 都干了些什么?

ret = split_commandline(&octx, argc, argv, options, groups, FF_ARRAY_ELEMS(groups));
octx: option context, 输出参数, 全局选项都放到这里了.
argc,argv: 输入参数
options: 输入参数, 全局选项表入口, 最后一项为空项表示结束
groups: 输入参数,组表入口
FF_ARRAY_ELEMS(groups): 组表项个数, 实际上就是2,一个输出组,一个输入组

其实用不着split, 选项,参数都已经按空格被分割放在不同的argv[index]中了,问题是进一步把这些选项,参数放到哪里?
该函数把命令行分割为它的内部实现!

它依次扫描命令行的每一个选项, 首先判断是不是输入文件或者输出文件
  ret = match_group_separator(groups, nb_groups, opt);
如果不是那就先查是不是正常选项normal option. 从选项中去找它.
    po = find_option(options, opt); //options 是选项表, opt 是当前选项
会返回一个选项指针po
若po-name不为空表示找到了,把结果保存到octx中, 调用下面函数来执行
   add_opt(octx, po, opt, arg); //opt 是选项, arg 是选项参数
这里的octx是OptionParseContext, 见后面结构描述. octx用来收集用户的选项输入.
根据opt->flag,可能会保存到octx的全局group,或cur_group中
po->name为空表示在options这个表中没找到.

如果没找到,再查AVOptions, 把选项及参数都传人函数来查找.
    opt_default(NULL, opt, argv[optindex])
这一次它要大面积的查,从codec,fromat,resample,sws,swr 5个类中查
    const AVClass *cc = avcodec_get_class();
    const AVClass *fc = avformat_get_class();
    const AVClass *rc = avresample_get_class();
    const AVClass *sc = sws_get_class();
    const AVClass *swr_class = swr_get_class();

const AVOption *o = opt_find(void *obj, char *name, char *unit, int opt_flags, int search_flags);
直到查到为止consume=1或者找不到.
例如: 从format_class 类中查,obj为fc,name 为输入项,unit为NULL, opt_flags 为0, search_flags 查子表及伪目标.
    o = opt_find(&fc, opt, NULL, 0, AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
    opt_find 是对 av_opt_find 的简单封装.

查到了,例如从format 表中找到的,则加入到format_opts字典中.
    av_dict_set(&format_opts, opt, arg, FLAGS); //opt 选项,arg参数,FLAGS(追加或覆盖之意)
其中format_opts 是全局变量.是一个AVDictionary 指针, 结果存到这里去.
字典的概念后面有描述.

如果是输入文件,
        finish_group(octx, 1, arg); //arg->输入文件名
如果是输出文件,会调用
        finish_group(octx, 0, opt); //opt->输出文件名

把当前分析的所有选项cur_group都归结为输入文件(第二参数为1)或者输出文件(第二参数为0).

这里介绍几个感念:
A组: option 相关
1.OptionDef 结构, (选项标识定义)
    比较简单, 有名称,标志,offset或函数指针联合体,帮助信息,参数名称,成千上万个选项实例都是这样定义的.

2.Option 结构. (选项定义), 命令行上每个选项会对应一个Option 变量
    由一个OptionDef指针,一个key指针,一个value指针构成,key,value都是char 指针
3. OptionGroupDef 结构.(选项组标识定义)
    这是个简单结构,包含一个group名, 分隔符(可为空),及标志

4. OptionGroup 结构: (选项组定义) 每个文件会对应一个选项组
    包含一个Option 表, 实现为Option *opts, int nb_opts
    包含5个字典 AVDictionary 指针, 分别是codec_opts, format_opts,resample_opts,sws_dict,swr_opts
    还有头部OptionGroupDef(选项组标识指针) 及一个arg 字符串指针,用以存储文件名称.

5. OptionGroupList 结构.(选项组列表结构)
    是一个Group表 OptionGroup *groups, int nb_groups,
    还有一个表头部:OptionGroupDef
    所有输入文件对应一个选项组列表,所有输出文件对应另一个选项组列表.

6. OptionParseContext (选项分析上下文定义)
    包含一个全局的OptionGroup global_opts
    包含一个当前的OptionGroup cur_group, 这是暂存group opt 的地方,将来由finish_group 拷贝到group列表.
    包含一个OptionGroupList列表集合, OptionGroupList*, nb_groups
    其成员一般只有两个列表集合(nb_groups=2),输出,输入.

B组: 字典相关AVDictionary
1.字典是字典项的一个集合.
struct AVDictionary {
    int count;
    AVDictionaryEntry *elems;
}
2. 字典项 AVDictionaryEntry
字典项是一个key,value键值对
struct AVDictionaryEntry
{
    char *key;
    char *value;
};
其主要的接口函数:
    av_dict_get
    av_dict_set
    av_dict_count
    av_dict_free

关注点: options 选项表. 进一步可关注AV_Option 表

============================================================
2. parse_optgroup
============================================================
    应用全局选项:
  ret = parse_optgroup(NULL, &octx.global_opts); // 分析一组选项
  octx 是OptionParseContext
  例如: -y 是全局选项
    ret = write_option(NULL, o->opt, o->key, o->val);
    根据定义的标志,及偏移,假如是一个整数,算出值存到optctx对应位置.
    全局选项其optctx 为NULL, 会直接更改全局变量的值.
    例如 -y全局选项, 其opt定义指示把val直接存储到变量,o->opt中存储了变量的地址
    数据被写到了static int file_overwrite变量.

2.1 parse_optgroup() 函数分析
----------------------------------------
int parse_optgroup(void *optctx, OptionGroup *g)
{
    for (i = 0; i < g->nb_opts; i++) {
        Option *o = &g->opts[i];
        ret = write_option(optctx, o->opt, o->key, o->val);
    }
}
传来的指针optctx 是一个void 指针, 意味着不能使用结构的名称了,只能使用偏移来访问内部数据.

2.2 write_option() 函数分析
----------------------------------------
    SpecifierOpt 说明符选项. 说明符是这样的一个结构.
    typedef struct SpecifierOpt {
        char *specifier;    /**< stream/chapter/program/... specifier */
        union {
            uint8_t *str;
            int        i;
            int64_t  i64;
            uint64_t ui64;
            float      f;
            double   dbl;
        } u;
    } SpecifierOpt;
    别怕!, 最后一个概念了,都是这么回事,慢慢整清楚. 可翻译为描述符选项.
    例如-c copy , 其key为c,其value为copy
    对于SpecifierOpt 选项, octx中都给它留了位置.(新式的offset 或 spec)旧式用全局变量

    static int write_option(void *optctx, const OptionDef *po, const char *opt, const char *arg)
    {
        /* new-style options contain an offset into optctx, old-style address of a global var*/
        //OPT_OFFSET 或OPT_SPEC, optctx 中留了位置, 否则就是全局变量
        void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ?  (uint8_t *)optctx + po->u.off : po->u.dst_ptr;
        int *dstcount;
        if (po->flags & OPT_SPEC) { //如果是描述符.
            SpecifierOpt **so = dst; //dst存储的是SpecifierOpt 指针的指针.
            dstcount = (int *)(so + 1); //下一个位置是整形指针,也许dstcount叫p_dstcount 更好
            *so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1); //分配一个SpecifierOpt 内存
            char *p = strchr(opt, ':');        //opt 是key
            char *str= av_strdup(p ? p + 1 : "");
            (*so)[*dstcount - 1].specifier = str;    //把:后边字符串地址存入specifier 处.(*so)是新分配的内存指针
            dst = &(*so)[*dstcount - 1].u; // 调整目标地址指向参数处.
        }

        if (po->flags & OPT_STRING) {
            char *str= av_strdup(arg);
            *(char **)dst = str; //存储参数指针
        }
    }
    -c copy 是一个说明符选项, "copy" 指针被存入specifierOpt的union 处
    -f hls 是一个位置选项, "hls"地址被存入octx中format地址处

============================================================
3. 打开输入文件
============================================================
    ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);
    其中&octx.groups[1] 就是OptionGroupList l, 输入文件选项组

    3.1 open_files()函数分析, 调用一次打开所有的文件列表
    ----------------------------------------------------
    static int open_files(OptionGroupList *l, const char *inout, int (*open_file)(OptionsContext*, const char*))
    {
        for (i = 0; i < l->nb_groups; i++) { // 只有一个输入文件时,l->nb_groups 为1
            OptionGroup *g = &l->groups[i]; //输入参数没有带选项. 不过还是把选项参数传进去.若有在o中存储
            OptionsContext o;
            init_options(&o);
            o.g = g;  //从o中还可以方便的找到g

            ret = parse_optgroup(&o, g); //g中有选项指针,有个数,还有5个字典
            ret = open_file(&o, g->arg);
            uninit_options(&o);
        }
    }

    ret = open_file(&o, g->arg); g->arg是文件名, 此处实际调用是
    static int open_input_file(OptionsContext *o, const char *filename)

    本示例由于对输入文件没有设置选项,所以简单分析如下.
    进行一些默认的初始化之后,调用
    err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts);
    其中file_iformat = NULL;
    --> 再调用
    avformat_find_stream_info ...

============================================================
4. 初始化复杂过滤器
============================================================
    ret = init_complex_filters();                                            
    初始化复杂过滤器,此处没有使用, 先忽略


============================================================
5. 打开输出文件
============================================================
    调用也是open_files, 但传人参数不同.
    ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);
    parse_optgroup(&o, g);
    ret = write_option(optctx, o->opt, o->key, o->val);
    ret = open_file(&o, g->arg);
    实际调用是:
    static int open_output_file(OptionsContext *o, const char *filename)

一个庞大的函数呈现眼前:2156->2805, 600多行代码. 先到此.have a rest!

------------------------------------------------------------
static int open_output_file(OptionsContext *o, const char *filename)
------------------------------------------------------------
1. OutputFile 结构定义还算简单.
typedef struct OutputFile {
    AVFormatContext *ctx;
    AVDictionary *opts;
    int ost_index;       /* index of the first stream in output_streams */
    int64_t recording_time;  ///< desired length of the resulting file in microseconds == AV_TIME_BASE units
    int64_t start_time;      ///< start time in microseconds == AV_TIME_BASE units
    uint64_t limit_filesize; /* filesize limit expressed in bytes */

    int shortest;
    int header_written;
} OutputFile;

    OutputFile *of;
    of = av_mallocz(sizeof(*of));
    ...
    err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);
    of->ctx = oc;
    nb_filtergraphs 此处为0, 跳过处理.
    new_video_stream(o, oc, idx);
    new_audio_stream(o, oc, idx);
    代码虽多,大部分都跳过了.
************************************************************
实例分析: 下面是一个复杂的实例
************************************************************
ffmpeg -analyzeduration 10000000 -y -i /opt/test/test1.ts -c:v libx264 -c:a aac -b:v 2M -b:a 128k -s 640*480  -f hls -strftime 1 -hls_list_size 10 -start_number 1 -hls_flags delete_segments+append_list+program_date_time+second_level_segment_index -hls_segment_filename %Y%m%d-%%06d.ts -hls_time 10 playlist.m3u8
选项分析实例:
matched as option ... 是正常选项,在options 列表中给出.
其中 -y 是全局选项,
       add_opt(octx, po, opt, arg); //会放到octx->global_opts
其它6个 -c:v,-c:a -b:v,-b:a -s -f  // 已经属于输出文件选项组.
       add_opt(octx, po, opt, arg); //会放到octx->cur_group, 它们会归为输出文件选项.

matched as AVOption ... 是AV选项, 共7项,从5中类中查找选项.
        av_dict_set(&codec_opts, opt, arg, FLAGS);
        av_dict_set(&format_opts, opt, arg, FLAGS);
        ...
其中 -analyzeduration
     -strftime
     -hls_list_size
     -start_number
     -hls_flags
     -hls_segment_filename
     -hls_time
全部是从fc类中找到,所以都添加到format_opts 中.
这样split_commandline 结束.
补充:
      finish_group(octx, 0, opt);  //将octx->cur_group 内容copy 到输出文件列表中,opt 是输出文件名

全局选项设置
----------------------------------------
    将y=1 设置到 file_overwrite 变量上     
    ret = parse_optgroup(NULL, &octx.global_opts);

输出文件的选项设置
----------------------------------------
    ret = parse_optgroup(&o, g);
-c:v libx264 -c:a aac 选项的存储及使用.
    1. 被split 到输出文件组,被parse存储到OptionsContext
    SpecifierOpt *codec_names;
    使用:new_video_stream 或 new_audio_stream 中,调用new_output_stream,调用
    choose_encoder()中
        MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); // 获取codecs 名字
    选择了libx264, aac
        ost->enc = find_codec_or_die(codec_name, ost->st->codecpar->codec_type, 1); // 找encoder
        最终: avcodec_find_encoder_by_name(name) :

-b:v 2m -b:a 128k 选项的存储及使用
  

    1. 被split 到输出文件组,被parse存储到OptionsContext的OptionGroup中的codec_opts

        av_dict_set(&o->g->codec_opts, "b:v", arg, 0);
        av_dict_set(&o->g->codec_opts, "b:a", arg, 0);
    使用:new_video_stream 调用new_output_stream,调用
        ost->encoder_opts  = filter_codec_opts(o->g->codec_opts, ost->enc->id, oc, st, ost->enc);
            调用 check_stream_specifier(s, st, p + 1)) 过滤, 其中s formatContext,st stream, p+1  流描述符, 可以是v,a,s,d,t,V,及 p(program)
            检查流类型和用户输入的流描述符是否一致.一致的保存下来(实际是检查用户指定的流是否合法及是否匹配,由用户来选择流).
        transcode(), transcode_step()等会调用
        init_output_stream_wrapper(ost, NULL, 1) 调用 init_output_stream 调用
        if ((ret = avcodec_open2(ost->enc_ctx, codec, &ost->encoder_opts)) < 0)  //使用了编码器选项

-s 选项的存储及使用
    被存储到 SpecifierOpt *frame_sizes;
    宽高在哪里使用?: new_video_stream()中
        MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); //oc,st 是检查用的,从optctx提出value.
        //存储到AVCodecContext video_enc的width,height
        if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0)
           分析存储到 video_enc(AVCodecContext *), 直接修改了它的width 和 height.


-f 选项的存储及使用

    被存储到 OptionsContext 指定偏移处. const char *format 处.

    使用: open_output_file()中
    err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);

AVOption 的选项存储及使用
----------------------------------------
    av_dict_copy(&of->opts, o->g->format_opts, 0);
    ret = avformat_write_header(of->ctx, &of->opts); // 在write_header 中使用

附录: 命令行输出log
----------------------------------------
Splitting the commandline.
Reading option '-analyzeduration' ... matched as AVOption 'analyzeduration' with argument '10000000'.
Reading option '-y' ... matched as option 'y' (overwrite output files) with argument '1'.
Reading option '-i' ... matched as input url with argument '/opt/test/fault001.ts'.
Reading option '-c:v' ... matched as option 'c' (codec name) with argument 'libx264'.
Reading option '-c:a' ... matched as option 'c' (codec name) with argument 'aac'.
Reading option '-b:v' ... matched as option 'b' (video bitrate (please use -b:v)) with argument '2M'.
Reading option '-b:a' ... matched as option 'b' (video bitrate (please use -b:v)) with argument '128k'.
Reading option '-s' ... matched as option 's' (set frame size (WxH or abbreviation)) with argument '640*480'.
Reading option '-f' ... matched as option 'f' (force format) with argument 'hls'.
Reading option '-strftime' ... matched as AVOption 'strftime' with argument '1'.
Reading option '-hls_list_size' ... matched as AVOption 'hls_list_size' with argument '10'.
Reading option '-start_number' ... matched as AVOption 'start_number' with argument '1'.
Reading option '-hls_flags' ... matched as AVOption 'hls_flags' with argument 'delete_segments+append_list+program_date_time+second_level_segment_index'.
Reading option '-hls_segment_filename' ... matched as AVOption 'hls_segment_filename' with argument '%Y%m%d-%%06d.ts'.
Reading option '-hls_time' ... matched as AVOption 'hls_time' with argument '10'.
Reading option 'playlist.m3u8' ... matched as output url.
Finished splitting the commandline.
Parsing a group of options: global .
Applying option y (overwrite output files) with argument 1.
Successfully parsed a group of options.
Parsing a group of options: input url /opt/test/fault001.ts.
Successfully parsed a group of options.
Opening an input file: /opt/test/fault001.ts.
[NULL @ 0x55927b549c40] Opening '/opt/test/fault001.ts' for reading
[file @ 0x55927b54ab00] Setting default whitelist 'file,crypto,data'
[mpegts @ 0x55927b549c40] Format mpegts probed with size=2048 and score=50
[mpegts @ 0x55927b549c40] stream=0 stream_type=1b pid=100 prog_reg_desc=
[mpegts @ 0x55927b549c40] stream=1 stream_type=f pid=101 prog_reg_desc=
[mpegts @ 0x55927b549c40] Before avformat_find_stream_info() pos: 0 bytes read:32768 seeks:0 nb_streams:2
[mpegts @ 0x55927b549c40] probing stream 1 pp:2500
[mpegts @ 0x55927b549c40] Probe with size=2560, packets=1 detected aac with score=51
[mpegts @ 0x55927b549c40] probed stream 1
[mpegts @ 0x55927b549c40] Probe buffer size limit of 5000000 bytes reached
[mpegts @ 0x55927b549c40] After avformat_find_stream_info() pos: 0 bytes read:5787792 seeks:2 frames:239
Input #0, mpegts, from '/opt/test/fault001.ts':
  Duration: 00:00:30.83, start: 31.370667, bitrate: 10751 kb/s
  Program 1
    Metadata:
      service_name    : Service01
      service_provider: FFmpeg
  Stream #0:0[0x100], 83, 1/90000: Video: h264 (Main), 1 reference frame ([27][0][0][0] / 0x001B), yuv420p(tv, bt709, progressive, left), 1920x1080 (1920x1088), 0/1, 25 fps, 25 tbr, 90k tbn, 50 tbc
  Stream #0:1[0x101](eng), 156, 1/90000: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 320 kb/s
Successfully opened the file.
Parsing a group of options: output url playlist.m3u8.
Applying option c:v (codec name) with argument libx264.
Applying option c:a (codec name) with argument aac.
Applying option b:v (video bitrate (please use -b:v)) with argument 2M.
Applying option b:a (video bitrate (please use -b:v)) with argument 128k.
Applying option s (set frame size (WxH or abbreviation)) with argument 640*480.
Applying option f (force format) with argument hls.
Successfully parsed a group of options.
Opening an output file: playlist.m3u8.
Successfully opened the file.

new_video_stream()/ new_audio_stream()  其输出在哪里? 在output_streams里

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用C语言和FFmpeg打开笔记本电脑的摄像头,首先需要在Windows系统下安装好FFmpeg库。安装完成后,可以使用以下代码来实现: 1. 首先,包含FFmpeg的头文件和其他必要的库文件。 ```c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <windows.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> ``` 2. 初始化FFmpeg并打开摄像头。 ```c int main() { // 初始化FFmpeg av_register_all(); avformat_network_init(); avdevice_register_all(); AVFormatContext* formatContext = NULL; // 打开摄像头 AVInputFormat* inputFormat = av_find_input_format("dshow"); avformat_open_input(&formatContext, "video=Integrated Webcam", inputFormat, NULL); avformat_find_stream_info(formatContext, NULL); // 查找并打开视频流 int videoStream = -1; for (int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } if (videoStream == -1) { printf("无法打开视频流。\n"); return -1; } // 读取视频帧 AVPacket packet; av_init_packet(&packet); AVCodecContext* codecContext = formatContext->streams[videoStream]->codec; AVCodec* codec = avcodec_find_decoder(codecContext->codec_id); avcodec_open2(codecContext, codec, NULL); AVFrame* frame = av_frame_alloc(); AVFrame* frameRGB = av_frame_alloc(); int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height); uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t)); avpicture_fill((AVPicture*)frameRGB, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height); struct SwsContext* swsContext = sws_getContext( codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL ); while (av_read_frame(formatContext, &packet) >= 0) { if (packet.stream_index == videoStream) { avcodec_decode_video2(codecContext, frame, &frameFinished, &packet); if (frameFinished) { sws_scale( swsContext, frame->data, frame->linesize, 0, codecContext->height, frameRGB->data, frameRGB->linesize ); // 在这里可以对图像帧进行处理 } } av_packet_unref(&packet); } // 清理资源 av_frame_free(&frame); av_frame_free(&frameRGB); avcodec_close(codecContext); avformat_close_input(&formatContext); avformat_network_deinit(); return 0; } ``` 这个代码片段会打开笔记本电脑的摄像头,读取摄像头返回的图像帧,并将其存储在RGB格式的帧中。你可以根据需要,在代码中加入对图像帧的处理逻辑。最后,记得清理资源并关闭摄像头。 注意:这只是一个简单的示例,实际应用中可能需要处理更多的异常情况和错误处理。另外,由于某些Windows系统使用的摄像头驱动不兼容FFmpeg,可能需要额外的配置和处理才能正常工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值