–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=0i<*counti++){
//释放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=0i<*counti++){
这里可以这么理解:
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
以Iphone 12,iOS 14为例,HTTP Live Streaming(HLS)是一个由苹果公司提出的基于HTTP的流媒体网络传输协议。HLS协议是苹果推出的解决方案,将视频分成5-10秒的视频小分片,然后用m3u8索引表进行管理,由于客户端下载到的视频都是5-10秒的完整数据,故视频的流畅性很好,但也同样引入了很大的延迟(HLS的一般延迟在10-30s左右)。相比于FLV,HLS在iPhone和大部分android手机浏览器上的支持非常给力。
HLS协议客户端支持简单, 只需要支持 HTTP 请求即可, HTTP 协议无状态, 只需要按顺序下载媒体片段即可,而且网络兼性好, HTTP 数据包也可以方便地通过防火墙或者代理服务器。
但是相比RTMP 这类长连接协议, 用到互动直播场景延时较高。HLS(HTTP Live Streaming)是苹果公司推出的流媒体协议, 用于直播或点播场景, 应该算是当前平台兼du性最好的流媒体协议了。 其他主流的流媒体协议还有RTP(zhidao传输使用UDP)和Adobe的RTMP(基于TCP)。
功能模块概述通过obs客户端推流到nginx流媒体服务器上,对流媒体用ffmpeg将流剪切为若干段ts流文件并保存到临时目录中,通过访问m3u8格式拼接ts流文件段来观看直播。
推流端
采用开源工具OBS客户端进行推流
根据项目的推流地址,填入OBS客户端(下载地址)中,并设置场景,保存后重启,便可开始推流。
为更加稳定的推流,建议使用以上链接中的v0.625稳定版本,按提示安装完成后,打开设定.在广播设定中,伺服器统一填写我们项目的流媒体接收流地址:
rtmp://127.0.0.1:1935/hls/
以上这几个数据都是可以更改的。
127.0.0.1——你的流媒体服务器ip
1935——你的rtmp端口号
hls——你的直播nginx配置模块
具体在下文中也有详细介绍
配置地址
回到主界面,右键来源,选择添加视频捕捉设备或获取窗口等(相关设置默认即可),点击开始串流,便可开始直播。
添加场景
图为添加视频捕捉设备后的直播画面:
直播中
流媒体服务器
Nginx接收推流模块
rtmp_auto_push on
rtmp {
server {
listen 1935
application hls {
live on
hls on
hls_path /tmp/hls
on_publish 项目地址/liveOnPublish
on_publish_done 项目地址/liveOnDone
notify_method get
}
}
}
配上我在word上的注解
注解1
Nginx处理直播流模块
http {
server {
listen 80
server_name localhost
location /hls {
secure_link $arg_st,$arg_e
secure_link_md5 key$arg_e
subs_filter .ts '.ts?st=$arg_st&e=$arg_e&clentip=$remote_addr'
subs_filter_types application/vnd.apple.mpegurl
if ($secure_link = "") {
return 402
}
if ($secure_link = "0") {
return 403
}
# Serve HLS fragments
types {
application/vnd.apple.mpegurl m3u8
video/mp2t ts
}
root /tmp
add_header Cache-Control no-cache
}
}
}
注解2
项目部署服务器
流媒体服务器不通过项目服务器,整个直播过程的推流和处理流都在流媒体服务器上进行。项目服务器主要进行直播地址加密处理意见推流开始和结束触发的方法(liveOnPublish(),liveOnDone())已及对直播地址加密.
播放端
直接通过HTML5中的<video>标签设置src来播放直播流。如:
<video src="pro/live.m3u8?st=UM/L8jdfTlY2b1j1F6XSxA==&e=1468548530
" controls="controls"></video>
存在的问题
延迟待测试(不科学的数据是安卓手机普遍在30-40s,苹果手机在20-30s)
并发待测试
掉帧待测试(网速影响大,网络好几乎不掉帧,网络差掉一半,用户体验差)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)