『FFMPEG』ffmpeg-0.11.1的命令行解析与控制

本文深入解析FFmpeg命令行参数的内部处理流程,包括参数重置、命令行解析及选项转储等方面,并详细介绍了FFmpeg中重要的数据结构与核心函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. ffmpeg的常用命令19 ffmpeg commands for all needs『FFMPEG』ffmpeg/avconv使用学习笔记『较详细』


2. 几个重要的数据结构

涉及到的source file,ffmpeg.c opt.h opt.c cmdutils.h cmdutils.c dict.h dict.c opt.c opt.h

该数组定义了各个选项对应的操作需要的信息,下面再详细看

#define OFFSET(x) offsetof(OptionsContext, x)
static const OptionDef options[] = {    /* main options */
#include "cmdutils_common_opts.h"
。。。
};
typedef struct SpecifierOpt {
    char *specifier;    /**< stream/chapter/program/... specifier */
    union {
        uint8_t *str;
        int        i;
        int64_t  i64;
        float      f;
        double   dbl;
    } u;
} SpecifierOpt;
typedef struct {
    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_GRAB   0x0040
#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_FUNC2  0x2000
#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
     union {
        void *dst_ptr;
        int (*func_arg)(const char *, const char *);
        int (*func2_arg)(void *, const char *, const char *);
        size_t off;
    } u;
    const char *help;
    const char *argname;
} OptionDef;

用到了字典结构

typedef struct {
    char *key;
    char *value;
} AVDictionaryEntry;
struct AVDictionary {
    int count;
    AVDictionaryEntry *elems;
};
typedef struct AVDictionary AVDictionary;
/**
 * Get a dictionary entry with matching key.
 */
AVDictionaryEntry *
av_dict_get(AVDictionary *m, const char *key, const AVDictionaryEntry *prev, int flags);

/**
 * Set the given entry in *pm, overwriting an existing entry.
 */
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);

/**
 * Copy entries from one AVDictionary struct into another.
 */
void av_dict_copy(AVDictionary **dst, AVDictionary *src, int flags);

/**
 * Free all the memory allocated for an AVDictionary struct
 * and all keys and values.
 */
void av_dict_free(AVDictionary **m);

3. 细数命令的解析过程,这一小节参考,取其中一部分ffmpeg参数解析

int main(int argc,char**argv)
{
    //初始化参数容器
    OptionsContext o={0};
    //重置参数
    reset_options(&o);
    //解析参数
    parse_options(&o, argc, argv, options,opt_output_file);
}

(1) 参数重置:reset_options, ffmpeg.c
先对OptionContext这个结构做一些说明:

OptionContext 包含了在视频编转码过程中需要用到的参数,这些参数来自于命令行的输入。
参数在OptionContext中的存储类型定义:
#define OPT_* 在structOptionDef的定义中可以看到。


然后直接看代码:

static void reset_options(OptionsContext *o, int is_input)
{
    const OptionDef *po = options; // 指向全局变量
    OptionsContext bak= *o;
    int i;

    /* all OPT_SPEC and OPT_STRING can be freed in generic way */
	// 1. Release special types
    while (po->name) { // 遍历options
        void *dst = (uint8_t*)o + po->u.off; // dst指针指向option对应在OptionContext的位置

        if (po->flags & OPT_SPEC) { // OPT_SPEC 对应struct SpecifierOpt
            SpecifierOpt **so = dst; // 
            int i, *count = (int*)(so + 1); // 注意到OptionContext中SpecifierOpt *后就是上一字段组的长度
            // 指针的操作so+1实际上是so的地址做了sizeof(*so)的偏移量
            
            for (i = 0; i < *count; i++) { // 遍历这个数组
                av_freep(&(*so)[i].specifier);
                if (po->flags & OPT_STRING)
                    av_freep(&(*so)[i].u.str);
            }

			// 释放SpecifierOpt  数组, Pay attention to func __av_freep, and Memory alignment.
            av_freep(so);
            *count = 0;
        } else if (po->flags & OPT_OFFSET && po->flags & OPT_STRING) // OPT_STRING 对应char *
            av_freep(dst);
        po++;
    }

	// 2. Release other types
    for (i = 0; i < o->nb_stream_maps; i++)
        av_freep(&o->stream_maps[i].linklabel);
    av_freep(&o->stream_maps);
    av_freep(&o->audio_channel_maps);
    av_freep(&o->streamid_map);

	// 3. Set default value
    memset(o, 0, sizeof(*o));

    if(is_input) o->recording_time = bak.recording_time;
    else         o->recording_time = INT64_MAX;
    o->mux_max_delay  = 0.7;
    o->limit_filesize = UINT64_MAX;
    o->chapters_input_file = INT_MAX;

	// 4. Re-initialize options
    uninit_opts(); // 主要进行: SwsContext*sws_opts,AVDictionary*format_opts,*codec_opts三个全局变量的创建和释放工作。
    init_opts();
}

(2) 命令行参数解析,直接看代码:parse_options, cmdutils.c

/**
 *	optctx——类型为OptionContext,是传入回调的函参
 *	int argc——命令行参数个数
 *	char**argv——命令行参数列表
 *	const OptionDef*options——选项列表
 *	void(*parse_arg_function)(void*,constchar*)——自定义的解析方法
 */
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); // Windows下为宽字符

    /* parse options */
    optindex = 1;
    while (optindex < argc) {
        opt = argv[optindex++]; // argv[0] is "ffmpeg"

        if (handleoptions && opt[0] == '-' && opt[1] != '\0') { // Begin with '-'
            if (opt[1] == '-' && opt[2] == '\0') { // Begin with '--'
                handleoptions = 0;
                continue;
            }

			// Drop the first character '-'
            opt++;

            if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)
                exit_program(1);
            optindex += ret;
        } else {
	        // 此时opt的值为输出文件名
            if (parse_arg_function) // Default handler func: opt_output_file
                parse_arg_function(optctx, opt); // //处理输出文件的相关内容,如 struct OutputFile的初始化
        }
    }
}

#include <windows.h>
/* Will be leaked on exit */
static char** win32_argv_utf8 = NULL;
static int win32_argc = 0;

/**
 * Prepare command line arguments for executable.
 * For Windows - perform wide-char to UTF-8 conversion.
 * Input arguments should be main() function arguments.
 * @param argc_ptr Arguments number (including executable)
 * @param argv_ptr Arguments list.
 */
static void prepare_app_arguments(int *argc_ptr, char ***argv_ptr)
{
    char *argstr_flat;
    wchar_t **argv_w;
    int i, buffsize = 0, offset = 0;

    if (win32_argv_utf8) {
        *argc_ptr = win32_argc;
        *argv_ptr = win32_argv_utf8;
        return;
    }

    win32_argc = 0;
    argv_w = CommandLineToArgvW(GetCommandLineW(), &win32_argc);
    if (win32_argc <= 0 || !argv_w)
        return;

    /* determine the UTF-8 buffer size (including NULL-termination symbols) */
    for (i = 0; i < win32_argc; i++)
        buffsize += WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1,
                                        NULL, 0, NULL, NULL);

    win32_argv_utf8 = av_mallocz(sizeof(char *) * (win32_argc + 1) + buffsize);
    argstr_flat     = (char *)win32_argv_utf8 + sizeof(char *) * (win32_argc + 1);
    if (win32_argv_utf8 == NULL) {
        LocalFree(argv_w);
        return;
    }

    for (i = 0; i < win32_argc; i++) {
        win32_argv_utf8[i] = &argstr_flat[offset];
        offset += WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1,
                                      &argstr_flat[offset],
                                      buffsize - offset, NULL, NULL);
    }
    win32_argv_utf8[i] = NULL;
    LocalFree(argv_w);

    *argc_ptr = win32_argc;
    *argv_ptr = win32_argv_utf8;
}

int parse_option(void *optctx, const char *opt, const char *arg,
                 const OptionDef *options)
{
	// 1. 查找匹配的option
    const OptionDef *po;
    int bool_val = 1;
    int *dstcount;
    void *dst;

    po = find_option(options, opt); //从全局变量options数组中查找opt对应的OptionDef
    if (!po->name && opt[0] == 'n' && opt[1] == 'o') { // Find none && begin with "no"
        /* handle 'no' bool option */
		//不需要传递参数的选项是bool类型的选项,默认为true
    	//如果需要设置为false,则需要加上”no”,以下的if则是处理这种情况
        po = find_option(options, opt + 2); // Skip the prefix-"no"
        if ((po->name && (po->flags & OPT_BOOL)))
            bool_val = 0;
    }
    if (!po->name) // Find none && not begin with "no"
        po = find_option(options, "default"); //寻找默认配置进行处理
    if (!po->name) { // default配置也未找到,报错
        av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'\n", opt);
        return AVERROR(EINVAL);
    }
    if (po->flags & HAS_ARG && !arg) { //如果选项必须有参数但是没有可用的参数,报错
        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*/
     // 2. 
     //以下的代码用于将 void*dst变量赋值。让dst指向需要赋值的选项地址。
    dst = po->flags & (OPT_OFFSET | OPT_SPEC) /* 如果选项在OptionContext中是以偏移量定位或者是 SpecifierOpt*数组的类型 */? (uint8_t *)optctx + po->u.off/* //dst指向从 optctx地址偏移u.off的位置 */
                                              : po->u.dst_ptr; /* 否则直接指向 OptionDef结构中定义的位置 */

	//关于po->u.dst_ptr的指向,在ffmpeg中都是用来设置全局变量使用。

    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指针指向数组新增的SpecifierOpt中的 u地址
        //此时dstcount的值已经变作新数组的长度,亦即原数组长度+1
        dst = &(*so)[*dstcount - 1].u;
    }

	// 3. 选项赋值
    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) {
    /*
		如acodec选项定义:
		{"acodec", HAS_ARG| OPT_AUDIO| OPT_FUNC2,{(void*)opt_audio_codec},"force audio codec ('copy' to copy stream)","codec"},
		在全局变量options中注册的解析方法为:opt_audio_codec。
	*/
        int ret = po->flags & OPT_FUNC2 ? po->u.func2_arg(optctx, opt, arg)
                                        : po->u.func_arg(opt, arg);
        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);
}

//根据name在全局变量options数组中查找OptionDef
static const OptionDef *find_option(const OptionDef *po, const char *name)
{
	//这里先处理参数带有冒号的情况。比如 codec:a codec:v等
    const char *p = strchr(name, ':');
    int len = p ? p - name : strlen(name);

    while (po->name != NULL) {
		//比较option的名称与name是否相符。
        //这里 codec 与 codec:a相匹配
        if (!strncmp(name, po->name, len) && strlen(po->name) == len)
            break;
        po++;
    }
    return po;
}

(3)实例分析:照搬的别人的原文,给游客看的
现在回到文档开头提到的

avconv -i input_file.mp4 -vcodeccopy -acodeccopy -vbsfh264_mp4toannexb –ss00:00:00 –t00:00:10 output_file.ts
的解析上来。下面,将以比较特殊的 -acodec copy 说明。
 
首先,在全局变量options中定义了acodec选项的相关信息:
{"acodec", HAS_ARG| OPT_AUDIO| OPT_FUNC2,{(void*)opt_audio_codec},"force audio codec ('copy' to copy stream)","codec"},
 
可以看到:此选项:
1.      有参数需要传入
2.      处理的是音频数据
3.      解析方式是自定义方法
4.      解析方法为: opt_audio_codec
5.      其功能是:"forceaudio codec ('copy' to copy stream)"
6.      其对应的命令行名称为codec
 
因此,在parse_option的调用中,对于acodec选项,将用opt_audio_codec解析方法进行处理。
opt_audio_codec(optctx,“acodec”,“copy”)
 
方法代码如下:
staticint opt_audio_codec(OptionsContext*o,constchar*opt,constchar*arg)
{
    return parse_option(o,"codec:a", arg, options);
}
 
可以看到,在这里,没有做更多的工作,只是对命令行选项acodec进行了一个转换,使用"codec:a"的解析器进行重新解析:
opt_audio_codec(optctx,“codec:a”,“copy”)
 
这里需要回顾一下方法
staticconst OptionDef*find_option(const OptionDef*po,constchar*name)
此方法是在查找name为“codec:a” 的option 时,实际是寻找的 “codec”
 
{"codec", HAS_ARG| OPT_STRING| OPT_SPEC,{.off= OFFSET(codec_names)},"codec name","codec"},
 
可以看到:此选项:
1.      有参数需要传入
2.      处理的是OPT_SPEC类型的数组(SpecifierOpt*)
3.      SpecifierOpt 结构体存储的是OPT_STRING(char *)
4.      赋值方式是直接赋值,偏移位是:{.off= OFFSET(codec_names)},亦即:
typedefstruct OptionsContext{
    /* input/output options */
   int64_t start_time;
constchar*format;
 
    SpecifierOpt*codec_names;   ?----------------此行位置
    int       nb_codec_names;
 
5.       其功能是:"codec name"
6.      其对应的命令行名称为codec
 
因此,在调用
parse_option(o,"codec:a","copy",options)
之后,获得的结果是:
 
typedefstruct SpecifierOpt{
    //值为”a”
    char*specifier;
    //值为”copy”
    union{
        uint8_t *str;
        int       i;
        int64_t i64;
        float      f;
        double   dbl;
    } u;
} SpecifierOpt;
 
而在OptionsContext中,
typedefstruct OptionsContext{
    /* input/output options */
    int64_t start_time;
    constchar*format;
 
    SpecifierOpt*codec_names;  ?----------------增加一个数组元素
    int       nb_codec_names;?----------------计数器+1

4. 命令选项信息的转储以及使用

【围绕着OptionContext结构继续来看:】

(首先)函数reset_options,在main、opt_input_file、opt_output_file中调用到,用来重置OptionContext。

(1)opt_input_file

if (o->format) { // 手动设置文件格式,"-f", {.off = OFFSET(format)
        if (!(file_iformat = av_find_input_format(o->format))) {
            av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format);
            exit_program(1);
        }
    }
    if (o->nb_audio_sample_rate) { // 设置音频采样率,"-ar", {.off = OFFSET(audio_sample_rate)}
        snprintf(buf, sizeof(buf), "%d", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i);
        av_dict_set(&format_opts, "sample_rate", buf, 0);
    }
    if (o->nb_audio_channels) { // 设置音频声道数,"-ac"
        /* because we set audio_channels based on both the "ac" and
         * "channel_layout" options, we need to check that the specified
         * demuxer actually has the "channels" option before setting it */
        if (file_iformat && file_iformat->priv_class &&
            av_opt_find(&file_iformat->priv_class, "channels", NULL, 0,
                        AV_OPT_SEARCH_FAKE_OBJ)) {
            snprintf(buf, sizeof(buf), "%d",
                     o->audio_channels[o->nb_audio_channels - 1].u.i);
            av_dict_set(&format_opts, "channels", buf, 0);
        }
    }
    if (o->nb_frame_rates) { // 设置视频帧率,"-r"
        av_dict_set(&format_opts, "framerate", o->frame_rates[o->nb_frame_rates - 1].u.str, 0);
    }
    if (o->nb_frame_sizes) { // "-s"
        av_dict_set(&format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0);
    }
    if (o->nb_frame_pix_fmts) // "-pix_fmt"
        av_dict_set(&format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0);

将codec的选项参数设置到codec的上下文中,如果不手动指定参数值,这里不会获取到有效的opts,重要的codec参数要在后面的demux中,解析到.

opt_input_file是遇到-i命令后调用到.

/* Set AVCodecContext options for avformat_find_stream_info */
    opts = setup_find_stream_info_opts(ic, codec_opts);
先来看下这个例子:"-b:a 50000"的解析

void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
                   void (*parse_arg_function)(void *, const char*))
{
...
	// "-b:a 50000"
	opt++;
	
	if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0) // pass in "b:a 50000"
		exit_program(1);
	optindex += ret;
...
}
int parse_option(void *optctx, const char *opt, const char *arg,
                 const OptionDef *options)
{
...
		// 由{ "b", HAS_ARG | OPT_VIDEO | OPT_FUNC2, {(void*)opt_bitrate}, "video bitrate (please use -b:v)", "bitrate" },
		// 中OPT_FUNC2可以看出,下面走po->u.func2_arg(optctx, opt, arg),然后转入opt_bitrate函数
		if (po->u.func_arg) {
        int ret = po->flags & OPT_FUNC2 ? po->u.func2_arg(optctx, opt, arg)  // pass in "b:a 50000"
                                        : po->u.func_arg(opt, arg);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR,
                   "Failed to set value '%s' for option '%s'\n", arg, opt);
            return ret;
        }
...
}
static int opt_bitrate(OptionsContext *o, const char *opt, const char *arg)
{
    if(!strcmp(opt, "b")){
        // 如果传入的是 "-b 50000"
        return parse_option(o, "b:v", arg, options);
    }
    return opt_default(opt, arg);  // pass in "b:a 50000"
}
/*
 *	libavcodec/option_table.h
 *	#define OFFSET(x) offsetof(AVCodecContext,x)
 *	static const AVOption options[]={
 */
#define FLAGS(o) ((o)->type == AV_OPT_TYPE_FLAGS) ? AV_DICT_APPEND : 0
int opt_default(const char *opt, const char *arg)
{
    const AVOption *oc, *of, *os, *oswr = NULL;
    char opt_stripped[128];
    const char *p;
	// 获得static const AVClass av_codec_context_class,options.c
    const AVClass *cc = avcodec_get_class(), *fc = avformat_get_class(), *sc, *swr_class;

    if (!(p = strchr(opt, ':')))
        p = opt + strlen(opt); // Find no ':'
    av_strlcpy(opt_stripped, opt, FFMIN(sizeof(opt_stripped), p - opt + 1)); // Example, opt_stripped == "b"

    if ((oc = av_opt_find(&cc, opt_stripped, NULL, 0,
                         AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ)) ||
        ((opt[0] == 'v' || opt[0] == 'a' || opt[0] == 's') &&
         (oc = av_opt_find(&cc, opt + 1, NULL, 0, AV_OPT_SEARCH_FAKE_OBJ))))// pass in ":a"

		// 向codec_opts加入新的元素,opt: "b:a" arg: "50000"
        av_dict_set(&codec_opts, opt, arg, FLAGS(oc)); // 覆盖or追加
    if ((of = av_opt_find(&fc, opt, NULL, 0,
                          AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ)))
        av_dict_set(&format_opts, opt, arg, FLAGS(of));
...
}
const AVOption *av_opt_find(void *obj, const char *name, const char *unit,
                            int opt_flags, int search_flags)
{
    return av_opt_find2(obj, name, unit, opt_flags, search_flags, NULL);
}
const AVOption *av_opt_find2(void *obj, const char *name, const char *unit,
                             int opt_flags, int search_flags, void **target_obj)
{
    const AVClass  *c;
    const AVOption *o = NULL;

    if(!obj)
        return NULL;

    c= *(AVClass**)obj;

    if (search_flags & AV_OPT_SEARCH_CHILDREN) { // 递归查找
        if (search_flags & AV_OPT_SEARCH_FAKE_OBJ) {
            const AVClass *child = NULL;
            while (child = av_opt_child_class_next(c, child)) // 返回first_avcodec链表中下一个元素的AVClass
                if (o = av_opt_find2(&child, name, unit, opt_flags, search_flags, NULL))
                    return o;
        } else {
            void *child = NULL;
            while (child = av_opt_child_next(obj, child)) // 返回下一级上下文,如上下文第一个成员声明为const AVClass *av_class;
            												//AV_OPT_SEARCH_FAKE_OBJ说明中,也就是这个意思
                if (o = av_opt_find2(child, name, unit, opt_flags, search_flags, target_obj))
                    return o;
        }
    }

	// 遍历obj上的AVOption数组
    while (o = av_opt_next(obj, o)) {
        if (!strcmp(o->name, name) && (o->flags & opt_flags) == opt_flags &&
            ((!unit && o->type != AV_OPT_TYPE_CONST) ||
             (unit  && o->type == AV_OPT_TYPE_CONST && o->unit && !strcmp(o->unit, unit)))) {
            if (target_obj) {
                if (!(search_flags & AV_OPT_SEARCH_FAKE_OBJ))
                    *target_obj = obj;
                else
                    *target_obj = NULL;
            }
            return o;
        }
    }
    return NULL;
}
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags)
{
    AVDictionary      *m = *pm;
    AVDictionaryEntry *tag = av_dict_get(m, key, NULL, flags); // 在字典里查抄以key为键的元素
    char *oldval = NULL;

    if(!m)
        m = *pm = av_mallocz(sizeof(*m));

    if(tag) {
        if (flags & AV_DICT_DONT_OVERWRITE)
            return 0;
        if (flags & AV_DICT_APPEND) // 追加
            oldval = tag->value;
        else
            av_free(tag->value);
        av_free(tag->key);
        *tag = m->elems[--m->count];
    } else { // 无该键,则加入"b:a"
        AVDictionaryEntry *tmp = av_realloc(m->elems, (m->count+1) * sizeof(*m->elems));
        if(tmp) {
            m->elems = tmp;
        } else
            return AVERROR(ENOMEM);
    }
    if (value) {
        if (flags & AV_DICT_DONT_STRDUP_KEY) {
            m->elems[m->count].key   = (char*)(intptr_t)key;
        } else
        m->elems[m->count].key  = av_strdup(key  );
        if (flags & AV_DICT_DONT_STRDUP_VAL) {
            m->elems[m->count].value = (char*)(intptr_t)value;
        } else if (oldval && flags & AV_DICT_APPEND) {
            int len = strlen(oldval) + strlen(value) + 1;
            if (!(oldval = av_realloc(oldval, len)))
                return AVERROR(ENOMEM);
            av_strlcat(oldval, value, len); // append并非添一个元素,而是将newval append到oldval后。
            m->elems[m->count].value = oldval;
        } else
            m->elems[m->count].value = av_strdup(value); // 申请新的内存,存放value值
        m->count++;
    }
    if (!m->count) {
        av_free(m->elems);
        av_freep(pm);
    }

    return 0;
}

最后在,opt_input_file的末尾,调用了参数重置函数,那就是说,接下来的参数解析为opt_output_file来服务了

reset_options(o, 1);
(2)opt_output_file

这里最后也会调用:

if (oc->myStrategy && (oc->myStrategy->disableSignal == DISABLE_sgl))
	{
    	reset_options(o, 0, 0);
	}
这里的代码被修改过,修改的原因是,转码实时流,有时候音频会后面隔好就才会来,

这样就会在avformat_find_stream_info那里卡住,probe_size改小了之后,则让主程序进入只转码视频的节奏,

音频来了怎么处理呢?(flv)

我是这么做的:  我们可以看到

static int flv_read_header(AVFormatContext *s)
{
if(flags & FLV_HEADER_FLAG_HASVIDEO){
        if(!create_stream(s, FLV_STREAM_TYPE_VIDEO))
            return AVERROR(ENOMEM);
    }
    if(flags & FLV_HEADER_FLAG_HASAUDIO){
        if(!create_stream(s, FLV_STREAM_TYPE_AUDIO))
            return AVERROR(ENOMEM);
    }
}
flv_demux中通过流的前几个字节来确定,该流会包含流的类别,并创建AVStream,此时我依据这个flags(假设flags为audio/video both enabled)认为,

只有当音视频都收到,才算完整,这里完整的意图就用

disableSignal
来表示的,当转码启动,经过opt_output_file一次,此flags表示只有视频,则不去reset_options,将参数选项保留着。

同时还需要注意在reset_options的实现中,uninit_opts调用了,它会重置codec_opts全局变量,导致

static OutputStream *new_output_stream(OptionsContext *o, AVFormatContext *oc, enum AVMediaType type, int source_index)
{
	
    choose_encoder(o, oc, ost);
	av_log(NULL, AV_LOG_WARNING, "new_output_stream: ost->enc=%x.\n", ost->enc);
    if (ost->enc) {
        ost->opts  = filter_codec_opts(codec_opts, ost->enc, oc, st);
    }

}
这里有问题,为什么这里会有问题?我又怎么知道音频过来了呢?看

static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    if(  (st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || (stream_type == FLV_STREAM_TYPE_AUDIO)))
       ||(st->discard >= AVDISCARD_BIDIR  &&  ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && (stream_type == FLV_STREAM_TYPE_VIDEO)))
       || st->discard >= AVDISCARD_ALL
       ){
		/*
			fallenink
			2012/12/24

			We detect the signal here.
		*/
		if (s->myStrategy && s->myStrategy->safeIdx[pkt->stream_index]
			//NOTICEs----暂时我们不支持先有音频后来视频。
			&& stream_type == FLV_STREAM_TYPE_AUDIO
			&& (s->myStrategy->disableSignal != DISABLE_sgl))
		{
			s->myStrategy->gotSignal |= st->index;
			av_log(s, AV_LOG_WARNING, "Stream discovered, got a signal.\n");
		}
		else
		{
			if (s->myStrategy) s->myStrategy->gotSignal = 0;
			//av_log(s, AV_LOG_WARNING, "Jump, cause the stream has been discarded, stream type = %d.\n", stream_type);
			avio_seek(s->pb, next, SEEK_SET);
			continue;
		}

		
    }
}
这里,能检测出来新的流,同时在转码主循环中检测gotSignal这个flags,如果有效了,则按照流程,对音频做一次转码链路(demux/codec/mux)的初始化,

这样会调用到new_output_stream就需要保留OptionsContext参数选项,留到此时来对编码上下文做必要的参数设置。


【接下类要围绕InputStream、OutputStream、OutputFile中的AVDictionary *opts参数】

(3)transcode_init

略。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值