如何用nginx+ffmpeg实现苹果HLS协议

我请问一下,如何用nginx+ffmpeg实现苹果HLS协议
最新回答
孤独似把刀

2024-06-26 00:10:38

avconv -i input_file.mp4 -vcodeccopy -acodeccopy -vbsfh264_mp4toannexb –ss00:00:00 –t00:00:10 output_file.ts

为例说明ffmpeg如何将命令行参数解析处理。

int main(int argc,char**argv)

{

//初始化参数容器

OptionsContext o={0};

//重置参数

reset_options(&o);

//解析参数

parse_options(&o, argc, argv, options,opt_output_file);

}

1.重置参数
staticvoid reset_options(OptionsContext*o)

依次进行了以下步骤:

1.1第一步:释放特殊类型
释放所有的 OPT_SPEC(对应struct SpecifierOpt)和 OPT_STRING (对应 char*)类型的 OptionDef

代码如下:

//指向全局变量options

const OptionDef*po= options;

//遍历options

while(po->name){

//dest指针指向当前option对应的OptionContext中的位置

void*dst=(uint8_t*)o+ po->u.off;

//判断是否是SpecifierOpt类型

if(po->flags& OPT_SPEC){

//so指向SpecifierOpt*的首地址

SpecifierOpt **so= dst;

//获得数组长度

int i,*count=(int*)(so+1);

//循环遍历SpecifierOpt*数组

for(i=0; i<*count; i++){

//释放SpecifierOpt的specifier(char*类型)

av_freep(&(*so)[i].specifier);

//如果OPT类型是字符串,释放SpecifierOpt的u.str(char*类型)

if(po->flags& OPT_STRING)

av_freep(&(*so)[i].u.str);

}

//释放SpecifierOpt*指针数组

av_freep(so);

//重置计数器

*count=0;

}

//判断是否是char*类型

elseif(po->flags& OPT_OFFSET&& po->flags& OPT_STRING)

av_freep(dst);

po++;

}

这里需要对OptionContext的内容做一些说明:

OptionContext 包含了在视频编转码过程中需要用到的参数,这些参数来自于命令行的输入。

参数在OptionContext中的存储形式有:

#defineOPT_INT 0x0080

#defineOPT_FLOAT 0x0100

#defineOPT_INT64 0x0400

#defineOPT_TIME 0x10000

#defineOPT_DOUBLE 0x20000

等,详情参见 structOptionDef

在上述代码中,主要循环释放的是OPT_SPEC(对应struct SpecifierOpt)和 OPT_STRING

在OptionContext中,OPT_SPEC类型是成对出现的,如下:

typedefstructOptionsContext{

int64_t start_time;

constchar*format;

SpecifierOpt *codec_names;

int nb_codec_names;

SpecifierOpt *audio_channels;

int nb_audio_channels;

即:

SpecifierOpt *xxx_vars;

int nb_xxx_vars; //nb_读作number_意思是xxx_vars数组的长度

然后我们来分析对SpecifierOpt*数组的遍历:

SpecifierOpt **so= dst;

int i,*count=(int*)(so+1);

for(i=0; i<*count; i++){

这里可以这么理解:

so —指向—> SpecifierOpt *xxx_vars;

so+1—指向—> int nb_xxx_vars;

so+1 的含义:so是个SpecifierOpt指针,指针+1则移动了sizeof(SpecifierOpt)的位置,即跳到nb_xxx_vars的位置。

1.2释放其他类型
av_freep(&o->stream_maps);

av_freep(&o->meta_data_maps);

av_freep(&o->streamid_map);

这里说一下 av_freep 的用法。

void av_freep(void*arg)

{

void**ptr=(void**)arg;

av_free(*ptr);

*ptr=NULL;

}

相比传统的free方法,这里主要多做了一步工作:将释放free之后,指针设置为NULL

同时,要注意到:

Object *obj;

free(obj);

等价用法为:

av_freep(&obj);

在ffmpeg中,封装了对应free的方法为:

void av_free(void*ptr)

{

#ifCONFIG_MEMALIGN_HACK

if(ptr)

free((char*)ptr-((char*)ptr)[-1]);

#else

free(ptr);

#endif

}

这里除了考虑内存对齐之外,跟传统的free方法没有任何变化。

1.3第三步:设置初始值
memset(o,0,sizeof(*o));

o->mux_max_delay =0.7;

o->recording_time= INT64_MAX;

o->limit_filesize= UINT64_MAX;

o->chapters_input_file= INT_MAX;

不需要过多解释。

o->mux_max_delay =0.7;

这一行内容以后在视频切片中会用到。可以调整到更小。

1.4重新初始化特殊参数
uninit_opts();

init_opts();

这两行代码对应cmdutils.c 文件中的代码段:

struct SwsContext*sws_opts;

AVDictionary*format_opts,*codec_opts;

void init_opts(void)

{

#if CONFIG_SWSCALE

sws_opts= sws_getContext(16,16,0,16,16,0, SWS_BICUBIC,

NULL,NULL,NULL);

#endif

}

void uninit_opts(void)

{

#ifCONFIG_SWSCALE

sws_freeContext(sws_opts);

sws_opts=NULL;

#endif

av_dict_free(&format_opts);

av_dict_free(&codec_opts);

}

主要进行: SwsContext*sws_opts,AVDictionary*format_opts,*codec_opts三个全局变量的创建和释放工作。

2.解析命令行参数
void parse_options(void*optctx,int argc,char**argv,const OptionDef *options,void(*parse_arg_function)(void*,constchar*))

void*optctx,——OptionContext

int argc,——命令行参数个数

char**argv,——命令行参数列表

const OptionDef*options,——选项列表

void(*parse_arg_function)(void*,constchar*)——自定义的解析方法

2.1总览
constchar*opt;

int optindex, handleoptions=1, ret;

//处理window的情况

prepare_app_arguments(&argc,&argv);

optindex=1;

//循环处理命令行参数

while(optindex< argc){

opt = argv[optindex++];

//如果传入的参数是“-”打头

if(handleoptions&& opt[0]=='-'&& opt[1]!='\0'){

//如果传入的参数是“--”打头

if(opt[1]=='-'&& opt[2]=='\0'){

handleoptions =0;

//略过

continue;

}

//丢弃第一个字符”-”

opt++;

//解析命令行参数

//eg–acodec copy

//对应的 opt和 argv[optindex]为 “acodec” “copy”

if((ret= parse_option(optctx, opt, argv[optindex], options))<0)

exit_program(1);

optindex += ret;

}else{

//此时 opt的值为输出文件名如 test.ts

if(parse_arg_function)

//处理输出文件的相关内容,如 struct OutputFile的初始化

parse_arg_function(optctx, opt);

}

}

在此,ffmpeg 默认的处理输出文件名参数为:

staticvoid opt_output_file(void*optctx,constchar*filename)

2.2处理命令行参数
int parse_option(void*optctx,constchar*opt,constchar*arg, const OptionDef*options)

2.2.1查找匹配的Option
const OptionDef*po;

int bool_val=1;

int*dstcount;

void*dst;

//从全局变量options数组中查找opt对应的OptionDef

po = find_option(options, opt);

//如果未找到且以”no”打头

//不需要传递参数的选项是bool类型的选项,默认为true

//如果需要设置为false,则需要加上”no”,以下的if则是处理这种情况

if(!po->name&& opt[0]=='n'&& opt[1]=='o'){

//去掉开头的”no”重新查找

po = find_option(options, opt +2);

//如果仍未找到或者找到的选项不是bool类型

if(!(po->name&&(po->flags& OPT_BOOL)))

//报错

goto unknown_opt;

bool_val =0;

}

//如果未找到且不是以上的”no”打头情况

if(!po->name)

//寻找默认配置进行处理

po = find_option(options,"default");

//default配置也未找到,报错

if(!po->name){

unknown_opt:

av_log(NULL, AV_LOG_ERROR,"Unrecognizedoption '%s'\n", opt);

return AVERROR(EINVAL);

}

//如果选项必须有参数但是没有可用的参数,报错

if(po->flags& HAS_ARG&&!arg){

av_log(NULL, AV_LOG_ERROR,"Missingargument for option '%s'\n", opt);

return AVERROR(EINVAL);

}

现在来查看一下find_option方法的实现:

staticconst OptionDef*find_option(const OptionDef*po,constchar*name)

根据name在全局变量options数组中查找OptionDef

//这里先处理参数带有冒号的情况。比如 codec:a codec:v等

constchar*p= strchr(name,':');

int len= p? p- name: strlen(name);

//遍历options

while(po->name!=NULL){

//比较option的名称与name是否相符。

//这里 codec 与 codec:a相匹配

if(!strncmp(name, po->name, len)&& strlen(po->name)== len)

break;

po++;

}

return po;

2.2.2寻找选项地址
以下的代码用于将 void*dst变量赋值。让dst指向需要赋值的选项地址。

//如果选项在OptionContext中是以偏移量定位或者是 SpecifierOpt*数组的类型

dst= po->flags&(OPT_OFFSET| OPT_SPEC)?

//dst指向从 optctx地址偏移u.off的位置

(uint8_t*)optctx+ po->u.off:

//否则直接指向 OptionDef结构中定义的位置

po->u.dst_ptr;

//如果选项是SpecifierOpt*数组

if(po->flags& OPT_SPEC){

//数组首地址

SpecifierOpt **so= dst;

char*p= strchr(opt,':');

//这里是取得数组的当前长度+1

//请回顾 1.1中的描述:

//SpecifierOpt *xxx;

//int nb_xxx;

//当so指向xxx时刻,so+1指向nb_xxx

dstcount =(int*)(so+1);

//动态增长数组

*so = grow_array(*so,sizeof(**so), dstcount,*dstcount+1);

//将创建的SpecifierOpt结构体中的specifier赋值

//如codec:v 则specifier值为 “v”

(*so)[*dstcount-1].specifier= av_strdup(p? p+1:"");

//dst指针指向数组新增的SpecifierOpt中的 u地址

//此时dstcount的值已经变作新数组的长度,亦即原数组长度+1

dst =&(*so)[*dstcount-1].u;
温情绝傲

2024-06-26 01:49:17

我知道的是要解压音频流,用ffmpeg来解压