ffmpeg parse_options函数解析

本文详细解析了FFMPEG中的`parse_options`函数及其相关子函数,包括`parse_option`、`opt_input_file`和`output_packet`中的`do_video_out`函数。重点讨论了解析过程和关键结构体的作用,如`AVFormatContext`,以及在解协议、解封装、解码过程中的角色。FFMPEG结构体分为解协议、解封装、解码和数据存储等类别,每个类别都有相应的关键结构和功能。
摘要由CSDN通过智能技术生成

void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,void (*parse_arg_function)(void *, const char*))函数解析

现在终于要分析这个函数了,从注释来看就是解析命令行参数
/**
 * Parse the command line arguments.
 *
 * @param optctx an opaque options context
 * @param options Array with the definitions required to interpret every
 * option of the form: -option_name [argument]
 * @param parse_arg_function Name of the function called to process every
 * argument without a leading option name flag. NULL if such arguments do
 * not have to be processed.
 */
函数的注释很明确了,参数的使用也说的很明白,那么我们开始阅读源码吧!
parse_options(NULL, argc, argv, options, opt_output_file);这是在main()函数调用的情况

看看源代码:在cmdutils.c文件中
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
                   void (*parse_arg_function)(void *, const char*))
{
    const char *opt;
    int optindex, handleoptions = 1, ret;
    /* perform system-dependent conversions for arguments list */
    prepare_app_arguments(&argc, &argv);  //一个空的函数
    /* parse options */
    optindex = 1;
    while (optindex < argc) {
        opt = argv[optindex++]; // 注意,此时optindex =1;然后加一重新写入内存
        if (handleoptions && opt[0] == '-' && opt[1] != '\0') { // 带有“-”的是命令参数,没有带的就是紧跟参数的设置,比如“-i a.png”那么 “-i”b表示输入的意思,紧跟的"a.png"就是输入的文件名
            if (opt[1] == '-' && opt[2] == '\0') {  // 判断该命令是不是 “--”如果是就不处理,直接结束本次的循环。这一个逻辑很强,特别是 handleoptions变量的引入
                handleoptions = 0;
                continue;
            }
            opt++;  //去掉参数的“-”的真正参数项 例如 opt ="-i" 那么 opt++后就是 opt ="i";
            if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)//这里详细看下面关于这个函数的详细解析
                exit_program(1);
            optindex += ret; //解析正确了就+1,感觉这真是多此一举,何必要ret这个变量,没有参透大神是什么意思
        } else {
            if (parse_arg_function) //这个就是处理输出文件的
                parse_arg_function(optctx, opt);
        }
    }
}


看看parse_option(optctx, opt, argv[optindex], options)函数

int parse_option(void *optctx, const char *opt, const char *arg,  const OptionDef *options)

/**
 * Parse one given option.
 *
 * @return on success 1 if arg was consumed, 0 otherwise; negative number on error
 */

  函数的功能已经注释的很明确了,就是解析一条给出的具体命令
  从上面的调用来看传送的参数 一个是NULL不用管,第二个参数是去掉“-”的参数和紧跟的参数。例如“-i a.png” 那么传入的参数就是 parse_option(NULL,i,a.png,options);
  函数的源代码:
int parse_option(void *optctx, const char *opt, const char *arg,
                 const OptionDef *options)
{
    const OptionDef *po;
    int bool_val = 1;
    int *dstcount;
    void *dst;
    po = find_option(options, opt); //找打这个参数在数组的位置,返回的是这个数组的该地址
    if (!po->name && opt[0] == 'n' && opt[1] == 'o') { //处理“no”这个bool型参数
        /* handle 'no' bool option */
        po = find_option(options, opt + 2);
        if (!(po->name && (po->flags & OPT_BOOL)))
            goto unknown_opt;
        bool_val = 0;
    }
    if (!po->name) //po->NULL 那么就处理下面
        po = find_option(options, "default");
    if (!po->name) {
unknown_opt:
        av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'\n", opt);
        return AVERROR(EINVAL);
    }
    if (po->flags & HAS_ARG && !arg) {  //紧跟参数后面的设置 例如“-i k” arg = k
        av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'\n", opt);
        return AVERROR(EINVAL);
    }
    /* new-style options contain an offset into optctx, old-style address of
     * a global var*/
    //  如果这个选项在options出现过并且是OPT_OFFSET或者是OPT_SPEC的,则dst指向该参数在optctx中偏移地址(uint8_t *)optctx + po->u.off;如果不是OPT_OFFSET和OPT_SPEC型的,则dst指向该选项的地址po->u.dst_ptr
   //为了简化思考的难度 我假设我输入的是这样的命令“ffmpeg -i test.png  kuan.mp4” 也就是将一张图片转为一个视频,这样对于“-i”这个命令,根据options数组发现:  { "i", HAS_ARG, {(void*)opt_input_file}, "input file name", "filename" },显然对于这样的命令项,执行下面命令后 dst =NULL;
    dst = po->flags & (OPT_OFFSET | OPT_SPEC) ? (uint8_t *)optctx + po->u.off
                                              : po->u.dst_ptr;
    //有了上一条假设命令那么该函数体不执行
    if (po->flags & OPT_SPEC) {
        SpecifierOpt **so = dst;
        char *p = strchr(opt, ':');
        dstcount = (int *)(so + 1);
        *so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1);
        (*so)[*dstcount - 1].specifier = av_strdup(p ? p + 1 : "");
        dst = &(*so)[*dstcount - 1].u;
    }
    if (po->flags & OPT_STRING) {
        char *str;
        str = av_strdup(arg);
        *(char **)dst = str;
    } else if (po->flags & OPT_BOOL) {
        *(int *)dst = bool_val;
    } else if (po->flags & OPT_INT) {
        *(int *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT_MIN, INT_MAX);
    } else if (po->flags & OPT_INT64) {
        *(int64_t *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX);
    } else if (po->flags & OPT_TIME) {
        *(int64_t *)dst = parse_time_or_die(opt, arg, 1);
    } else if (po->flags & OPT_FLOAT) {
        *(float *)dst = parse_number_or_die(opt, arg, OPT_FLOAT, -INFINITY, INFINITY);
    } else if (po->flags & OPT_DOUBLE) {
        *(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY);
    } else if (po->u.func_arg) { //此处的代码要执行 ,当然要执行的是 po->func_arg(opt,arg);这个函数的分析到此为此,我下面的分析都是假设“ffmpeg -i a.png kuan.mp4”来分析函数的调用,因此有的读者可以根据自己需要来分析具体的命令。
        int ret = po->flags & OPT_FUNC2 ? po->u.func2_arg(optctx, opt, arg)
                                        : po->u.func_arg(opt, arg); // 这里的函数指针是opt_input_file因此下面我不得不分析这个函数,不同的命令这里的函数指针是不同的
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR,
                   "Failed to set value '%s' for option '%s'\n", arg, opt);
            return ret;
        }
    }
    if (po->flags & OPT_EXIT)
        exit_program(0);
    return !!(po->flags & HAS_ARG);
}


从代码的阅读来看,“ffmpeg -i a.png kuan.mp4”这条命令,我需要分析下opt_input_file函数下面就是这条函数的分析,当然我仍然以这条命令为例子,那么传入的参数就是opt_input_file(i,a.png);

a. opt_input_file函数解析(具体的命令具体分析函数)

注意:下面的代码分析都基于“ffmpeg -i a.png kuan.mp4”的命令分析的。

看看这个函数opt_output_file,该函数在ffmpeg.c源文件中,而且还以static修饰
看看源码:接着就逐行阅读吧
static int opt_input_file(const char *opt, const char *filename)
{
    AVFormatContext *ic; //对于该数据结构参考另一篇博客
    AVInputFormat *file_iformat = NULL;
    int err, i, ret, rfps, rfps_base;
    int64_t timestamp;
    uint8_t buf[128];
    AVDictionary **opts;
    int orig_nb_streams;                     // number of streams before avformat_find_stream_info

    if (last_asked_format) {   //从我分析的情形来看这个函数不执行
        if (!(file_iformat = av_find_input_format(last_asked_format))) {
            fprintf(stderr, "Unknown input format: '%s'\n", last_asked_format);
            exit_program(1);
        }
        last_asked_format = NULL;
    }

    if (!strcmp(filename, "-")) //不执行
        filename = "pipe:";
   //下面的代码很令人费解,不知道干什么的,其表达式这样: using_stdin = using_stdin|((!strncmp(filename, "pipe:", 5) ||!strcmp(filename, "/dev/stdin"));不过根据根据具体的命令,using_stdin = 0;
   using_stdin |= !strncmp(filename, "pipe:", 5) ||    //这个在后面就知道了,在输出文件的阶段,以这个参数来判断文件重名
                    !strcmp(filename, "/dev/stdin");

    /* get default parameters from command line */  //从命令行获取默认的参数
    ic = avformat_alloc_context(); // 初始化
    if (!ic) { //一般的能成功吧
        print_error(filename, AVERROR(ENOMEM));
        exit_program(1);
    }
    if (audio_sample_rate) {
        snprintf(buf, sizeof(buf), "%d", audio_sample_rate);
        av_dict_set(&format_opts, "sample_rate", buf, 0);
    }
    if (audio_channels) {
        snprintf(buf, sizeof(buf), "%d", audio_channels);
        av_dict_set(&format_opts, "channels", buf, 0);
    }
    if (frame_rate.num) {
        snprintf(buf, sizeof(buf), "%d/%d", frame_rate.num, frame_rate.den);
        av_dict_set(&format_opts, "framerate", buf, 0);
    }
    if (frame_width && frame_height) {
        snprintf(buf, sizeof(buf), "%dx%d", frame_width, frame_height);
        av_dict_set(&format_opts, "video_size", buf, 0);
    } // 上面都不用看
    if (frame_pix_fmt != PIX_FMT_NONE)
        av_dict_set(&format_opts, "pixel_format", av_get_pix_fmt_name(frame_pix_fmt), 0);
    //指定音频,视频,字幕的编码id
    ic->video_codec_id   =
        find_codec_or_die(video_codec_name   , AVMEDIA_TYPE_VIDEO   , 0);
    ic->audio_codec_id   =
        find_codec_or_die(audio_codec_name   , AVMEDIA_TYPE_AUDIO   , 0);
    ic->subtitle_codec_id=
        find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0); //字幕的编解码id
    ic->flags |= AVFMT_FLAG_NONBLOCK;

    /* open the input file with generic libav function */
    err = avformat_open_input(&ic, filename, file_iformat, &format_opts); // Open an input stream and read the header. The codecs are not opened.该函数的代码阅读
    if (err < 0) {    //不用看
        print_error(filename, err);
        exit_program(1);
    }
    assert_avoptions(format_opts); //不用看

    if(opt_programid) {//不用看 ·
        int i, j;
        int found=0;
        for(i=0; i<ic->nb_streams; i++){
            ic->streams[i]->discard= AVDISCARD_ALL;
        }
        for(i=0; i<ic->nb_programs; i++){
            AVProgram *p= ic->programs[i];
            if(p->id != opt_programid){
                p->discard = AVDISCARD_ALL;
            }else{
                found=1; 
                for(j=0; j<p->nb_stream_indexes; j++){
                    ic->streams[p->stream_index[j]]->discard= AVDISCARD_DEFAULT;
                }
            }
        }
        if(!found){
            fprintf(stderr, "Specified program id not found\n");
            exit_program(1);
        }
        opt_programid=0;
    }

    if (loop_input) {
        av_log(NULL, AV_LOG_WARNING, "-loop_input is deprecated, use -loop 1\n");
        ic->loop_input = loop_input;
    }

    /* Set AVCodecContext options for avformat_find_stream_info */
    opts = setup_find_stream_info_opts(ic, codec_opts);
    orig_nb_streams = ic->nb_streams;

    /* If not enough info to get the stream parameters, we decode the
       first frames to get it. (used in mpeg case for example) */
    ret = avformat_find_stream_info(ic, opts); //这个函数,注释说的很明白了 ret = 0
    if (ret < 0 && verbose >= 0) {
        fprintf(stderr, "%s: could not find codec parameters\n", filename);
        av_close_input_file(ic);
        exit_program(1);
    }

    timestamp = start_time;
    /* add the stream start time */
    if (ic->start_time != AV_NOPTS_VALUE)
        timestamp += ic->start_time;

    /* if seeking requested, we execute it */
    if (start_time != 0) {
        ret = av_seek_frame(ic, -1, timestamp, AVSEEK_FLAG_BACKWARD);
        if (ret < 0) {
            fprintf(stderr, "%s: could not seek to position %0.3f\n",
                    filename, (double)timestamp / AV_TIME_BASE);
        }
        /* reset seek info */
        start_time = 0;
    }

    /* update the current parameters so that they match the one of the input stream */
    for(i=0;i<ic->nb_streams;i++) {
        AVStream *st = ic->streams[i];
        AVCodecContext *dec = st->codec;
        InputStream *ist;

        dec->thread_count = thread_count;

        input_streams = grow_array(input_streams, sizeof(*input_streams), &nb_input_streams, nb_input_streams + 1); //这个需要仔细的看看,特别是指针的偏移这一点不懂那么这里就看不懂了
        ist = &input_streams[nb_input_streams - 1];
        ist->st = st;
        ist->file_index = nb_input_files;
        ist->discard = 1;
        ist->opts = filter_codec_opts(codec_opts, ist->st->codec->codec_id, ic, st);

        if (i < nb_ts_scale)
            ist->ts_scale = ts_scale[i];

        switch (dec->codec_type) {
        case AVMEDIA_TYPE_AUDIO:
            ist->dec = avcodec_find_decoder_by_name(audio_codec_name);
            if(audio_disable)
                st->discard= AVDISCARD_ALL;
            break;
        case AVMEDIA_TYPE_VIDEO:
            ist->dec = avcodec_find_decoder_by_name(video_codec_name);
            rfps      = ic->streams[i]->r_frame_rate.num;
            rfps_base = ic->streams[i]->r_frame_rate.den;
            if (dec->lowres) {
                dec->flags |= CODEC_FLAG_EMU_EDGE;
                dec->height >>= dec->lowres;
                dec->width  >>= dec->lowres;
            }
            if(me_threshold)
                dec->debug |= FF_DEBUG_MV;

            if (dec->time_base.den != rfps*dec->ticks_per_frame || dec->time_base.num != rfps_base) {

                if (verbose >= 0)
                    fprintf(stderr,"\nSeems stream %d codec frame rate differs from container frame rate: %2.2f (%d/%d) -> %2.2f (%d/%d)\n",
                            i, (float)dec->time_base.den / dec->time_base.num, dec->time_base.den, dec->time_base.num,

                    (float)rfps / rfps_base, rfps, rfps_base);
            }

            if(video_disable)
                st->discard= AVDISCARD_ALL;
            else if(video_discard)
                st->discard= video_discard;
            break;
        case AVMEDIA_TYPE_DATA:
            break;
        case AVMEDIA_TYPE_SUBTITLE:
            ist->dec = avcodec_find_decoder_by_name(subtitle_codec_name);
            if(subtitle_disable)
                st->discard = AVDISCARD_ALL;
            break;
        case AVMEDIA_TYPE_ATTACHMENT:
        case AVMEDIA_TYPE_UNKNOWN:
            break;
        default:
            abort();
        }
    }

    /* dump the file content */
    if (verbose >= 0)
        av_dump_format(ic, nb_input_files, filename, 0);  //输出基本的信息属于库函数的内容,不做深入的研究

    input_files = grow_array(input_files, sizeof(*input_files), &nb_input_files, nb_input_files + 1);//同样使用了指针的偏移
    input_files[nb_input_files - 1].ctx        = ic;
    input_files[nb_input_files - 1].ist_index  = nb_input_streams - ic->nb_streams;
    input_files[nb_input_files - 1].ts_offset  = input_ts_offset - (copy_ts ? 0 : timestamp);
    input_files[nb_input_files - 1].nb_streams = ic->nb_streams;

    frame_rate    = (AVRational){0, 0};
    frame_pix_fmt = PIX_FMT_NONE;
    frame_height = 0;
    frame_width  = 0;
    audio_sample_rate = 0;
    audio_channels    = 0;
    audio_sample_fmt  = AV_SAMPLE_FMT_NONE;
    av_freep(&ts_scale);
    nb_ts_scale = 0;

    for (i = 0; i < orig_nb_streams; i++)
        av_dict_free(&opts[i]);
    av_freep(&opts);
    av_freep(&video_codec_name);
    av_freep(&audio_codec_name);
    av_freep(&subtitle_codec_name);
    uninit_opts();
    init_opts();
    return 0;
}

看完了opt_input_file函数(注意使用的是“ffmpeg  -i k.png kuan.mp4”命令),这个函数其实最终要完成的是input_files的赋值,其中使用的指针偏移的技巧很重要。
在实际上调试发现,AVFormatContext ic 被赋值,但是没有编码或者解码的信息,在成员数据段 AVStream 也没有具体的解码信息,也就是AVCoder的值为0




opt_output_file实际上parse_options函数最后回归到这个函数上

实际上经过上面的分析发现parse_options函数似乎就是检查参数的合法性而已,看了半天感觉就是这个输出文件函数比较重要,那么现在就好好的分析一下这个函数

源代码
static void opt_output_file(void *optctx, const char *filename)
{
    AVFormatContext *oc; //详解下面对这个的函数分析
    int err, use_video, use_audio, use_subtitle, use_data;
    int input_has_video, input_has_audio, input_has_subtitle, input_has_data;
    AVOutputFormat *file_oformat;
    if (!strcmp(filename, "-"))
        filename = "pipe:";
    oc = avformat_alloc_context(); //初始化,分配内存
    if (!oc) {
        print_error(filename, AVERROR(ENOMEM));
        exit_program(1);
    }
    if (last_asked_format) { //不用看,直接看else内容
        file_oformat = av_guess_format(last_asked_format, NULL, NULL);
        if (!file_oformat) {
            fprintf(stderr, "Requested output format '%s' is not a suitable output format\n", last_asked_format);
            exit_program(1);
        }
        last_asked_format = NULL;
    } else {
        file_oformat = av_guess_format(NULL, filename, NULL); //这个函数调用的是库函数的内容,这个函数的注释是这样的:Return the output format in the list of registered output formats* which best matches the provided parameters, or return NULL if    * there is no match.也就是说就是返回输出文件的格式,前提是这个格式必须
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值