基于ffmpeg的推流示例 流媒体(streaming media)是指将一连串的媒体数据压缩后,经过网上分段发送数据,在网上即时传输影音以供观赏的一种技术与过程,此技术使得数据包得以像流水一样发送;如果不使用此技术,就必须在使用前下载整个媒体文件。流式传输可传送现场影音或预存于服务器上的影片,当观看者在收看这些影音文件时,影音数据在送达观看者的计算机后立即由特定播放软件播放。
rtmp是real time messaging protocol(实时消息传输协议)的首字母缩写。该协议基于tcp,是一个协议族,包括rtmp基本协议及rtmpt/rtmps/rtmpe等多种变种。rtmp是一种设计用来进行实时数据通信的网络协议,主要用来在flash/air平台和支持rtmp协议的流媒体/交互服务器之间进行音视频和数据通信。支持该协议的软件包括adobe media server/ultrant media server/red5等。rtmp与http一样,都属于tcp/ip四层模型的应用层。
rtmp 是一种基于 tcp 的、用于数据、音频和视频传输的双向通信协议。大部分具备行业标准的编码器(如 encoding.com、bitmovin、harmonic 和 aws elemental 等)都能够生产 rtmp 数据源。同样,twitch、youtube、facebook live 等流媒体服务和 dacast、ant media、wowza 等直播平台都能接收 rtmp 推流。
利用ffmpeg进行视频编码,通过rtmp将视频流推送到nginx服务端。ffmpeg推流过程和视频编码过程是一样的,视频流本身也是属于文件的一种,所以只需要正常实现视频编码即可。
ffmpeg视频编码示例:https://blog.csdn.net/weixin_44453694/article/details/123885112
nginx服务器搭建示例:https://blog.csdn.net/weixin_44453694/article/details/126511614
ffmpeg视频解码示例:https://blog.csdn.net/weixin_44453694/article/details/127098011
推流示例#include #include #include #include #include #include #include #include #include #include libavcodec/avcodec.h#include libavformat/avformat.h#include libswscale/swscale.h#include #include #include #include #include #include #include #include #include #include #include #define video_dev /dev/video0char file_name[100]=rtmp://124.21.108.66:8088/live/ashui;//视频编码文件名#define stream_frame_rate 25 /*每秒25帧*/#define stream_pix_fmt av_pix_fmt_yuv420p /*图像格式yuv420p*/#define stream_duration 10.0 /*录制时间10s*/pthread_mutex_t fastmutex = pthread_mutex_initializer;//互斥锁pthread_cond_t cond = pthread_cond_initializer;//条件变量int width;int height;int size;int mp4_decode_stat=0;unsigned char *rgb_buff=null;unsigned char video_flag=1;void *video_collectimage(void *arg);void *video_savemp4(void*arg);typedef enum{ false=0, true,}bool;typedef struct outputstream{ avstream *st; avcodeccontext *enc; int64_t next_pts;/*将生成的下一帧的pts*/ avframe *frame;/*保存编解码数据*/ avframe *tmp_frame; struct swscontext *sws_ctx; struct swrcontext *swr_ctx;}outputstream;typedef struct intputdev{ avcodeccontext *pcodecctx; avcodec *pcodec; avformatcontext *v_ifmtctx; int videoindex;//视频帧id struct swscontext *img_convert_ctx; avpacket *in_packet; avframe *pframe,*pframeyuv;}intputdev;intputdev video_input={0};//视频输入流/*添加一个输出流*/static void add_stream(outputstream *ost, avformatcontext *oc, avcodec **codec, enum avcodecid codec_id){ avcodeccontext *c; int i; /*查找编码器*/ *codec=avcodec_find_encoder(codec_id); if(*codec==null) { printf(could not find encoder for ' %s' n,avcodec_get_name(codec_id)); exit(1); } /*向媒体文件添加新流。*/ ost->st=avformat_new_stream(oc,null); if(ost->st==null) { printf(could not allocate stream n); exit(1); } ost->st->id=oc->nb_streams-1; /*分配avcodecontext并将其字段设置为默认值*/ c=avcodec_alloc_context3(*codec); if(c==null) { printf(avcodec_alloc_context3 failed n); } ost->enc=c; switch((*codec)->type) { case avmedia_type_audio: break; case avmedia_type_video:/*视频流*/ c->codec_id=codec_id; c->bit_rate=2500000;//比特率 /*分辨率必须是2的倍数。*/ c->width=width; c->height=height; /*时基:这是时间的基本单位(秒) 其中帧时间戳被表示。对于固定fps内容,时基应为1/帧率,时间戳增量应与1相同*/ ost->st->time_base=(avrational){1,stream_frame_rate}; c->time_base=ost->st->time_base; c->gop_size=12;/*最多每12帧发射一帧*/ c->pix_fmt=stream_pix_fmt;/*图像格式*/ if(c->codec_id == av_codec_id_mpeg2video) { /* 为了测试,我们还添加了b帧 */ c->max_b_frames=2; } if(c->codec_id == av_codec_id_mpeg1video) { /* 需要避免使用某些系数溢出的宏块。 这种情况不会发生在普通视频中,只会发生在这里 色度平面的运动与亮度平面不匹配。*/ c->mb_decision=2; } break; default: break; } /*有些格式希望流头是分开的。*/ if(oc->oformat->flags & avfmt_globalheader) { c->flags|=av_codec_flag_global_header; }}/*视频输出*/static avframe *alloc_picture(enum avpixelformat pix_fmt, int width, int height){ avframe *picture; int ret; /* 分配avframe并将其字段设置为默认值。结果 必须使用av_frame_free()释放结构。 */ picture=av_frame_alloc(); if(picture==null) { return null; } picture->format=pix_fmt; picture->width=width; picture->height=height; /*为帧数据分配缓冲区*/ ret=av_frame_get_buffer(picture,32);/*缓冲区以32位对齐*/ if(retenc; avdictionary *opt=null; av_dict_copy(&opt,opt_arg, 0); /*初始化avcodecontext以使用给定的avcodec*/ ret=avcodec_open2(c, codec,&opt); /*释放为avdictionary结构分配的所有内存*以及所有键和值。*/ av_dict_free(&opt); if(retframe=alloc_picture(av_pix_fmt_yuv420p,c->width,c->height); if(ost->frame==null) { printf(could not allocate video framen);/*无法分配视频帧*/ exit(1); } printf(ost->frame alloc success fmt=%d w=%d h=%dn,c->pix_fmt,c->width,c->height); ost->tmp_frame=null; if(c->pix_fmt!=av_pix_fmt_yuv420p) { ost->tmp_frame=alloc_picture(av_pix_fmt_yuv420p,c->width, c->height);/*视频帧格式*/ if(ost->tmp_frame==null) { printf(conld not allocate temporary picturen);/*无法分配临时帧*/ exit(1); } } /*根据提供的编解码器上下文中的值填充参数结构。*/ ret=avcodec_parameters_from_context(ost->st->codecpar,c); if(ret) { printf(could not copy the stream parametersn);/*无法复制流参数*/ exit(1); }}static void log_packet(const avformatcontext * fmt_ctx, const avpacket * pkt){ avrational *time_base=&fmt_ctx->streams[pkt->stream_index]->time_base; printf(pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%dn, av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base), av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base), av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base), pkt->stream_index); }/*写入数据*/static int write_frame(avformatcontext *fmt_ctx, const avrational *time_base, avstream *st, avpacket *pkt){ /*数据包中的有效时间字段(时间戳/持续时间)从一个时基转换为另一个时基。*/ av_packet_rescale_ts(pkt,*time_base,st->time_base); pkt->stream_index=st->index; /*打印信息*/ //log_packet(fmt_ctx, pkt); return av_interleaved_write_frame(fmt_ctx, pkt);/*将数据包写入输出媒体文件,确保正确的交织。*/}static int write_video_frame(avformatcontext * oc, outputstream * ost,avframe *frame){ int ret; avcodeccontext *c; int got_packet=0; avpacket pkt={0}; if(frame==(void *)-1)return 1; c=ost->enc; /*使用默认值初始化数据包*/ av_init_packet(&pkt); /*对一帧视频进行编码。*/ ret=avcodec_encode_video2(c,&pkt,frame,&got_packet); if(retst->time_base.num, ost->st->time_base.den, c->time_base.num, c->time_base.den); */ if(got_packet) { ret=write_frame(oc,&c->time_base,ost->st,&pkt);/*写入流数据*/ } else { ret=0; } if(retenc; avframe *ret_frame=null; /*在各自的时基中比较两个时间戳。*/ if(av_compare_ts(ost->next_pts,c->time_base,stream_duration, (avrational){1,1})>=0) { //return (void*)-1; } /*确保帧数据可写,尽可能避免数据复制。*/ if(av_frame_make_writable(ost->frame)v_ifmtctx,input->in_packet)>=0) { if(input->in_packet->stream_index == input->videoindex) { /*解码一帧视频数据。输入一个压缩编码的结构体avpacket,输出一个解码后的结构体avframe*/ ret=avcodec_decode_video2(input->pcodecctx, input->pframe,&got_picture,input->in_packet); *got_pic=got_picture; if(retin_packet); return null; } if(got_picture) { sws_scale(input->img_convert_ctx, (const unsigned char * const *)input->pframe->data,input->pframe->linesize,0,input->pcodecctx->height,input->pframeyuv->data,input->pframeyuv->linesize); sws_scale(input->img_convert_ctx, (const unsigned char * const *)input->pframe->data,input->pframe->linesize,0,input->pcodecctx->height,ost->frame->data,ost->frame->linesize); pthread_mutex_lock(&fastmutex);//互斥锁上锁 memcpy(rgb_buff,input->pframeyuv->data[0],size); pthread_cond_broadcast(&cond);//广播唤醒所有线程 pthread_mutex_unlock(&fastmutex);//互斥锁解锁 ost->frame->pts=ost->next_pts++; //水印添加处理 //frame->frame->format=av_pix_fmt_yuv420p; //avframe *frame_out=av_frame_alloc(); //unsigned char *frame_buffer_out; //frame_buffer_out=(unsigned char *)av_malloc(size); //av_image_fill_arrays(frame_out->data,frame_out->linesize,frame_buffer_out,av_pix_fmt_yuv420p,width,height,32); //添加水印,调用libavfilter库实现 //time_t sec; //sec=time(null); //struct tm* today = localtime(&sec); //char sys_time[64]; //strftime(sys_time, sizeof(sys_time), %y/%m/%d %h:%m:%s, today); //watermark(ost->frame,frame_out,width,height,sys_time); //yuv420p,y表示亮度,uv表示像素颜色 //ost->frame=frame_out; //ost->frame->pts=ost->next_pts++; ret_frame=ost->frame; } } av_packet_unref(input->in_packet); } return ret_frame;}static void close_stream(avformatcontext * oc, outputstream * ost){ avcodec_free_context(&ost->enc); av_frame_free(&ost->frame); av_frame_free(&ost->tmp_frame); sws_freecontext(ost->sws_ctx); swr_free(&ost->swr_ctx);}int main(){ /*创建摄像头采集线程*/ pthread_t pthid[2]; pthread_create(&pthid[0],null,video_collectimage, null); pthread_detach(pthid[0]);/*设置分离属性*/ sleep(1); while(1) { if(width!=0 && height!=0 && size!=0)break; if(video_flag==0)return 0; } printf(image:%d * %d,%dn,width,height,size); unsigned char *rgb_data=malloc(size); /*创建mp4视频编码线程*/ pthread_create(&pthid[1],null,video_savemp4, null); pthread_detach(pthid[1]);/*设置分离属性*/ int count=0; mp4_decode_stat=1; pause(); pthread_mutex_destroy(&fastmutex);/*销毁互斥锁*/ pthread_cond_destroy(&cond);/*销毁条件变量*/ free(rgb_buff); free(rgb_data); return 0;}void *video_collectimage(void *arg){ int res=0; avframe *input_pframe=null; avframe *output_pframe=null; printf(pth:%sn,avcodec_configuration()); /*注册设备*/ avdevice_register_all(); /*查找输入格式*/ avinputformat *ifmt=av_find_input_format(video4linux2); if(ifmt==null) { printf(av_find_input_format failedn); video_flag=0; return 0; } /*打开输入流并读取头部信息*/ avformatcontext *ps=null; //分配一个avformatcontext。 ps=avformat_alloc_context(); res=avformat_open_input(&ps,video_dev,ifmt,null); if(res) { printf(open input failedn); video_flag=0; return 0; } /*查找流信息*/ res=avformat_find_stream_info(ps,null); if(res) { printf(find stream failedn); video_flag=0; return 0; } /*打印有关输入或输出格式信息*/ av_dump_format(ps, 0, video4linux2, 0); /*寻找视频流*/ int videostream=-1; videostream=av_find_best_stream(ps,avmedia_type_video,-1,-1,null,0); printf(videostram=%dn,videostream); /*寻找编解码器*/ avcodec *video_avcodec=null;/*保存解码器信息*/ avstream *stream = ps->streams[videostream]; avcodeccontext *context=stream->codec; video_avcodec=avcodec_find_decoder(context->codec_id); if(video_avcodec==null) { printf(find video decodec failedn); video_flag=0; return 0; } /*初始化音视频解码器*/ res=avcodec_open2(context,video_avcodec,null); if(res) { printf(avcodec_open2 failedn); video_flag=0; return 0; } avpacket *packet=av_malloc(sizeof(avpacket));/*分配包*/ avframe *frame=av_frame_alloc();/*分配视频帧*/ avframe *frameyuv=av_frame_alloc();/*申请yuv空间*/ /*分配空间,进行图像转换*/ width=context->width; height=context->height; int fmt=context->pix_fmt;/*流格式*/ size=av_image_get_buffer_size(av_pix_fmt_yuv420p,width,height,16); unsigned char *buff=null; printf(w=%d,h=%d,size=%dn,width,height,size); buff=av_malloc(size); rgb_buff=malloc(size);//保存rgb颜色数据 /*存储一帧图像数据*/ av_image_fill_arrays(frameyuv->data,frameyuv->linesize,buff,av_pix_fmt_yuv420p,width,height, 16); /*转换上下文,使用sws_scale()执行缩放/转换操作。*/ struct swscontext *swsctx=sws_getcontext(width,height, fmt,width,height, av_pix_fmt_yuv420p,sws_bicubic,null,null,null); /*视频输入流信息*/ video_input.img_convert_ctx=swsctx;//格式转换上下文 video_input.in_packet=packet;//数据包 video_input.pcodecctx=context; video_input.pcodec=video_avcodec;/*保存解码器信息*/ video_input.v_ifmtctx=ps;//输入流并读取头部信息 video_input.videoindex=videostream;/*视频流*/ video_input.pframe=frame;/*视频帧*/ video_input.pframeyuv=frameyuv;/*申请yuv空间*/ //水印添加处理 frameyuv->width=width; frameyuv->height=height; frameyuv->format=av_pix_fmt_yuv420p; avframe *frame_out=av_frame_alloc(); unsigned char *frame_buffer_out; frame_buffer_out=(unsigned char *)av_malloc(size); av_image_fill_arrays(frame_out->data,frame_out->linesize,frame_buffer_out,av_pix_fmt_yuv420p,width,height,16); /*读帧*/ char *p=null; int go=0; int framecount=0; time_t sec,sec2; char sys_time[64]; while(video_flag) { if(!mp4_decode_stat) { res=av_read_frame(ps,packet);//读取数据 if(res>=0) { if(packet->stream_index == avmedia_type_video)//视频流 { /*解码一帧视频数据。输入一个压缩编码的结构体avpacket,输出一个解码后的结构体avframe*/ res=avcodec_decode_video2(ps->streams[videostream]->codec,frame,&go,packet); if(resdata,frame->linesize,0,context->height,frameyuv->data,frameyuv->linesize); //添加水印,调用libavfilter库实现 sec=time(null); if(sec!=sec2) { struct tm* today = localtime(&sec); strftime(sys_time, sizeof(sys_time), %y/%m/%d %h:%m:%s, today); } watermark(frameyuv,frame_out,width,height,sys_time); //yuv420p,y表示亮度,uv表示像素颜色 p=frame_buffer_out; memcpy(p,frame_out->data[0],frame_out->height*frame_out->width);//y,占用空间w*h p+=frame_out->width*frame_out->height; memcpy(p,frame_out->data[1],frame_out->height/2*frame_out->width/2);//u,占用空间(w/2)*(h/2) p+=frame_out->height/2*frame_out->width/2; memcpy(p,frame_out->data[2],frame_out->height/2*frame_out->width/2);//v,占用空间(w/2)*(h/2) p+=frame_out->height/2*frame_out->width/2; pthread_mutex_lock(&fastmutex);//互斥锁上锁 memcpy(rgb_buff,frame_buffer_out,size); pthread_cond_broadcast(&cond);//广播唤醒所有线程 pthread_mutex_unlock(&fastmutex);//互斥锁解锁 } } } } } sws_freecontext(swsctx);/*释放上下文*/ av_frame_free(&frameyuv);/*释放yuv空间*/ av_packet_unref(packet);/*释放包*/ av_frame_free(&frame);/*释放视频帧*/ avformat_close_input(&ps);/*关闭流*/ sws_freecontext(video_input.img_convert_ctx); avcodec_close(video_input.pcodecctx); av_free(video_input.pframeyuv); av_free(video_input.pframe); avformat_close_input(&video_input.v_ifmtctx); video_flag=0; pthread_exit(null); }/*mp4格式数据保存*/void *video_savemp4(void*arg){ while(1) { if(mp4_decode_stat) { int res; avformatcontext *oc=null; avdictionary *opt=null; /* 创建的avformatcontext结构体。*/ avformat_alloc_output_context2(&oc,null,flv,null);//通过文件名创建 if(oc==null) { printf(为输出格式分配avformatcontext失败n); avformat_alloc_output_context2(&oc,null,flv,null);//通过文件名创建 return 0; } if(oc==null)return (void*)1; /*输出流信息*/ avoutputformat *ofmt=oc->oformat; printf(ofmt->video_codec=%dn,ofmt->video_codec); int have_video=1; int encode_video=0; outputstream video_st={0}; if(ofmt->video_codec !=av_codec_id_none) { /*添加一个输出流*/ add_stream(&video_st,oc,&video_input.pcodec,ofmt->video_codec); have_video=1; encode_video=1; } printf(w=%d,h=%d,size=%dn,width,height,size); /*视频帧处理*/ if(have_video)open_video(oc,video_input.pcodec,&video_st,opt); printf(打开输出文件成功rn); /*打印有关输入或输出格式信息*/ printf(file_name=%sn,file_name); av_dump_format(oc, 0,file_name,1); if(!(ofmt->flags & avfmt_nofile)) { /*打开输出文件,成功之后创建的avformatcontext结构体*/ res=avio_open(&oc->pb,file_name,avio_flag_write); if(res<0) { printf(%s open failed :%sn,file_name,av_err2str(res)); return (void*)1; } } /*写入流数据头*/ res=avformat_write_header(oc,&opt); if(respb); /*释放avformatcontext及其所有流。*/ avformat_free_context(oc); /*关闭流*/ if(have_video)close_stream(oc, &video_st); mp4_decode_stat=0; } }}
iOS版饿了么使用的开源项目
博通被美国贸易委员会调查 或存在反竞争行为
苹果拟加强汽车医疗移动支付等领域
关于远程医疗行业发展概况趋势以及发展过程中遇到的困难分析详解
LGG6高清拆解图集
基于ffmpeg的推流示例
推拉力测试机也很合适在多个行业中使用
华为鸿蒙系统完成华丽转身,迎来重大更新
创新打造行业领头羊 上海铭控勇攀智能传感器巅峰
艾吉威:智慧物流“刚需”下的推陈出新
MicroLED市场2028年将达14.6亿美元
IBM重磅发布拓展实用量子计算的最新路线图
Softcopy ruler整体图像质量视觉评测新方法
猛玛YM618无线内部通话系统的分组功能有什么用
国产数据中心级固态硬盘深度拆解:采用自研主控
基于工业物联网的电力铁塔状态远程监控系统
雅特思的HiFi蓝牙耳机放大器
日本东京大学团队发现人类磁感应能力
小家电市场报告,及未来规模预测
通信电缆的接续工艺和电缆接头封焊规范要求