推流(RTSP/RTMP)
我的一篇博文《如何用FFmpeg API采集摄像头视频和麦克风音频。。。》已经介绍了如何从视音频采集设备获取数据,并且编码、保存文件到本地。但是,有些应用并不是把流保存成文件,而是需要发送到网络的,比如现在很典型的一种应用场景:把流推送到RTSP、RTMP、HLS服务器,由服务器转发给其他用户观看。很多开发者也是调用FFmpeg API来实现推流的,用FFmpeg 做一个推流器很简单,调用流程跟输出文件的基本相同,基于前面博文的例子稍微修改就可以做出一个采集+编码+推流的软件。这里,我先假设读者已经会用FFmpeg API保存或录制文件,但没有实现过推流功能,我将给大家说一下做推流跟录制文件的区别,还有说一下要注意的几个问题,希望能帮助大家在开发推流功能时减少一些问题的出现。
首先,做推流和录制文件都需要调用到封装器对象的接口,我们需要定义一个封装器(或叫混合器):
AVFormatContext* m_outputAVFormatCxt;
创建封装器对象,根据输入的协议类型生成对应的封装器。
比如,对于RTSP,我们生成如下的推流封装器:
res = avformat_alloc_output_context2(&m_outputAVFormatCxt, NULL, "rtsp", m_outputUrl.c_str());
对于RTMP,生成封装器的代码如下:
res = avformat_alloc_output_context2(&m_outputAVFormatCxt, NULL, "flv", m_outputUrl.c_str());
其中,上面的m_outputUrl是推流地址。
然后,向封装器添加要发送的流(视频、音频),设置每个流的属性。假如我们要推送的流来源于一个文件,那就要先把文件的流枚举出来,获得每个流的信息,然后把这几个流“插入”到封装器里面,这样封装器才能识别这些流的格式。下面是从文件提取流的信息并添加到封装器的代码:
AVOutputFormat* fmt = m_outputAVFormatCxt->oformat;// fmt->video_codec = AV_CODEC_ID_H264;// fmt->audio_codec = AV_CODEC_ID_AAC;for (int i = 0; i < m_inputAVFormatCxt->nb_streams; i++){AVStream *in_stream = m_inputAVFormatCxt->streams[i];if(in_stream->codec->codec_type != AVMEDIA_TYPE_VIDEO && in_stream->codec->codec_type != AVMEDIA_TYPE_AUDIO) //忽略掉不是视频和音频的流{ continue; } AVStream *out_stream = avformat_new_stream(m_outputAVFormatCxt, in_stream->codec->codec);if (!out_stream){TRACE("can not new out stream");}res = avcodec_copy_context(out_stream->codec, in_stream->codec);if (res < 0){string strError = "can not copy context, filepath: " + m_filePath + ",errcode:" + to_string(res) + ",err msg:" + av_make_error_string(m_tmpErrString, AV_ERROR_MAX_STRING_SIZE, res);TRACE("%s \n", strError.c_str());}out_stream->codec->codec_tag = 0; if (m_outputAVFormatCxt->oformat->flags & AVFMT_GLOBALHEADER){out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;}}
我们调用avformat_new_stream生成一个新的流,然后调用avcodec_copy_context将文件的视频流或音频流的上下文属性拷贝到新流的目标上下文中,这样新的流就具有了和输入流同样的属性了(编码格式、分辨率、采样率等)。
发送数据到网络可能因为网络阻塞而发送超时,超时有可能发生在连接或中间数据传输的时候,如果连接超时,用户程序就会很久阻塞在连接函数里。解决这个问题的方法是:我们设置一个回调函数,FFmpeg在发送数据或连接超时会调用到该回调,如果超过某个时间,我们在回调里返回某个标志,让IO函数马上返回,用户的程序就不会一直卡在IO函数里。设置回调函数的代码如下:
m_outputAVFormatCxt->flags |= AVFMT_FLAG_NONBLOCK;av_dump_format(m_outputAVFormatCxt, 0, m_outputUrl.c_str(), 1);if (!(fmt->flags & AVFMT_NOFILE)){ AVIOInterruptCB icb = {interruptCallBack,this};m_dwStartConnectTime = GetTickCount();// res = avio_open(&m_outputAVFormatCxt->pb, m_outputUrl.c_str(), AVIO_FLAG_WRITE);res = avio_open2(&m_outputAVFormatCxt->pb, m_outputUrl.c_str(), AVIO_FLAG_WRITE, &icb, NULL);if (res < 0){string strError = "can not open output io, URL:" + m_outputUrl;TRACE("%s \n", strError.c_str());return FALSE;}}
用户定义的回调函数如下所示:
static int interruptCallBack(void *ctx)
{FileStreamPushTask * pSession = (FileStreamPushTask*) ctx;//once your preferred time is out you can return 1 and exit from the loopif(pSession->CheckTimeOut(GetTickCount())){return 1;}//continue return 0;}BOOL FileStreamPushTask::CheckTimeOut(DWORD dwCurrentTime)
{if(dwCurrentTime < m_dwLastSendFrameTime) //CPU时间回滚{return FALSE;}if(m_stop_status)return TRUE;if(m_bInited){if(m_dwLastSendFrameTime > 0){if((dwCurrentTime - m_dwLastSendFrameTime) > 15000) //发送过程中超时{return TRUE;}}}else{if((dwCurrentTime - m_dwStartConnectTime) > 5000) //连接超时{TRACE("Connect timeout! \n");m_stop_status = true;return TRUE;}}return FALSE;
}
接着,就开始连接服务器,并与服务器进行握手交互。
AVDictionary* options = NULL;if(bIsRTSP)av_dict_set(&options, "rtsp_transport", "tcp", 0); av_dict_set(&options, "stimeout", "8000000", 0); //设置超时时间 res = avformat_write_header(m_outputAVFormatCxt, &options);TRACE("avformat_write_header() return: %d\n", res);if (res < 0){string strError = "can not write outputstream header, URL:" + m_outputUrl + ",errcode:" + to_string(res);TRACE("%s \n", strError.c_str());m_bInited = FALSE;return FALSE;}m_bInited = TRUE;
如果执行到这一步而没有错误,表示已经成功连接服务器,并可以向服务器推流了。
而发送数据就简单了,流程跟输出文件的一样。我们调用av_interleaved_write_frame 函数往封装器写入一个帧,让封装器打包数据和发送到服务器。但是,有个地方要注意,就是从文件读出来的帧(输入帧)的时间戳要转换为封装器对应输出流的时基。比如对于视频流,我们需要这样处理:
if(in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) //视频{if(pkt.pts == AV_NOPTS_VALUE) //没有时间戳{AVRational time_base1 = out_stream->time_base; //Duration between 2 frames (us) int64_t calc_duration =(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate); pkt.pts = (double)(nVideoFramesNum*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE); pkt.dts = pkt.pts; pkt.duration = (double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE); }else{pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);pkt.pos = -1;}nVideoFramesNum++;}// write the compressed frame to the output formatint nError = av_interleaved_write_frame(m_outputAVFormatCxt, &pkt);
推流完毕,我们要关闭推流器,其实是关闭封装器对象,我们一般会这样关闭:
int res = av_write_trailer(m_outputAVFormatCxt); if (!(m_outputAVFormatCxt->oformat->flags & AVFMT_NOFILE)){if(m_outputAVFormatCxt->pb){avio_close(m_outputAVFormatCxt->pb);m_outputAVFormatCxt->pb = nullptr;}}
但是这样写还不完善。经测试,如果前面的连接握手失败了,则强行调用av_write_trailer会出错的。所以,还需要加个标志变量判断一下连接是否已经初始化成功(我用布尔变量m_bInited表示),修改后的代码如下:
if (m_outputAVFormatCxt){if(m_bInited){int res = av_write_trailer(m_outputAVFormatCxt); }if (!(m_outputAVFormatCxt->oformat->flags & AVFMT_NOFILE)){if(m_outputAVFormatCxt->pb){avio_close(m_outputAVFormatCxt->pb);m_outputAVFormatCxt->pb = nullptr;}}avformat_free_context(m_outputAVFormatCxt);m_outputAVFormatCxt = nullptr;}
推流(RTSP/RTMP)相关推荐
- ffmpeg c++代码推流RTSP/RTMP(命令行推流)
由于ffmpeg推出的rtsp或者rtmp端口需要Listen,所以需要开启端口TCP/UDP,之后采用ffmpeg向端口推送数据 第一,安装rtsp-simple-server release下载地 ...
- Android 内置RTSP/RTMP服务器,实现局域网内视频推流与播放
1. 背景 工作中有一个需求,在同一个局域网内, 需要将Android平板端(车机)上的摄像头上的画面,实时传输到手机上进行播放. 对于这个需求,我们想到了用RTSP/RTMP进行推流,然后在手机端拉 ...
- 【音视频】使用FFMPEG进行RTSP|RTMP|HLS推流(3-3)
前言:我最近在用ffmpeg研究各种网络推流,小有成果,所以写了一篇推流合集的文章记录一下最近关于推流的研究的进展情况. 我在之前搭建了RTMP和RTSP服务器的基础上(参考<[音视频]RTSP ...
- Windows远程桌面实现之五(FFMPEG实现桌面屏幕RTSP,RTMP推流及本地保存)
by fanxiushu 2018-07-10 转载或引用请注明原始作者. 前面文章分别阐述了,如何抓取电脑屏幕数据,如何采集电脑声音, 如何实现在现代浏览器中通过HTML5和WebSocket直接进 ...
- 海康大华等网络摄像机监控视频RTSP/RTMP推流网页播放/直播无需插件低延迟解决方案研究
市面上常见监控视频推流方案简介 当前如果想要将监控视频在浏览器中播放,有几种常见的办法如下: 1.获取摄像头RTSP流,使用FFmpeg或者程序如JavaCV或者其他方式,将其推流成RTMP,通过服务 ...
- 【音视频开发系列】盘点音视频直播RTSP/RTMP推流一定会遇到的各种坑,教你快速解决
聊聊RTSP/RTMP推流那些坑 1.推流架构分析 2.推流缓存队列的设计 3.FFmpeg函数阻塞问题分析 [音视频开发系列]盘点音视频直播一定会遇到的各种坑,教你快速解决 更多精彩内容包括:C/C ...
- 基于c++实现RTSP/RTMP推流组件PushStream简介
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 原文:https://blog.csdn.net/hiwubihe/article/details/84639975 [本系列相关文章] 基于c ...
- java摄像头推流_悄摸直播(一)—— 推流器的实现(获取笔记本摄像头画面,转流推流到rtmp服务器)...
推流器 一.功能说明 获取pc端的摄像头流数据 + 展示直播效果 + 推流到rtmp服务器 二.代码实现 /** * 推流器 * @param devicePath 摄像头的地址.可以是摄像头rtsp ...
- 如何实现Android端获取RTSP|RTMP流转推RTMP
技术背景 最近不少开发者找到我们,他们在做智能家居等传统行业时,希望实现在Android板件拉取本地的RTSP或RTMP流,然后对外推送RTMP出去,亦或内部启个轻量级RTSP服务,提供个对外对接的媒 ...
- 几款优秀的点播、RTSP/RTMP直播播放器介绍
1.ijkplayer 项目地址: https://github.com/Bilibili/ijkplayer 介绍:Ijkplayer 是Bilibili发布的基于 FFplay 的轻量级 Andr ...
最新文章
- 使用进度条,让Python学习更加轻松快乐吧
- ARM的嵌入式Linux移植体验之操作系统
- 如何使用grup制作U盘多重启动盘
- java rabbitmq 工具类_RabbitMq通用管理工具类
- 打开黑色_表哥出差带回来一箱苹果,打开后发现是黑色的,大家表示都没见过...
- 怎么撤销定时说说_已注册商标遇到撤三申请怎么办
- powershell 查看系统设备\device status
- ArcGIS 10.6字段计算器(Field Calculator)字段任意填充编码序列(奇数、偶数序列、自定义间隔)
- python自动化测试开发_基于python的selenium2自动化测试从基础到实战(Python3、selenium2、自动化测试、web测试)...
- linux检测硬件驱动,linux查看硬件信息及驱动设备相关整理
- 为什么我的U盘传到一半速度会变成0然后过一会儿才回继续
- Adaptive Feature Recombination and Recalibration for Semantic Segmentation: Application to Brain Tum
- 数学建模5 代码论文降重 Excel表处理数据
- 传输线理论 1/4波长阻抗变换器的分析匹配
- 计算两个时间相差几年几月
- Ubuntu Server 安装Nginx 实例
- 【美团滑块】猫眼下单、点评
- VIP邮箱套餐对比,163、TOM、新浪哪家VIP邮箱最全能?
- 【Redis 如何实现库存扣减操作】
- 邮箱要钱吗?注册邮箱要钱吗?怎么申请注册邮箱?