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
略。