一、前言
在推流范畴,尤其是监控行业,现在主流装备根本上都是265格式的视频流,想要在网页上直接表现监控流,之前的方案是,要么转成hls,要么魔改支持265格式的flv,要么265转成264,如果要追求实时性,那就只有一种方案,就是转码,逼迫转成264,然后用webrtc表现。固然,如果用户以为后台修改摄像头设置改成264可以接受,那又是另外一回事了。
为什么webrtc不支持265?据说是由于专利的原因,近些年说是解决了专利问题,现在谷歌欣赏器直接内置了265的解码,在新版的欣赏器已经支持了265的webrtc,目前谷歌欣赏器默认还未开启265,需要手动设置启动参数,官网说将来这个参数默认开启。
如何设置呢?找到桌面快捷方式,右键属性,目标加上 --enable-features=PlatformHEVCEncoderSupport,WebRtcAllowH265Receive,WebRtcAllowH265Send --force-fieldtrials=WebRTC-Video-H26xPacketBuffer/Enabled
完整内容如下 “C:\Program Files\Google\Chrome\Application\chrome.exe” --enable-features=PlatformHEVCEncoderSupport,WebRtcAllowH265Receive,WebRtcAllowH265Send --force-fieldtrials=WebRTC-Video-H26xPacketBuffer/Enabled
如何确定设置后是否真正支持265?打开网页 https://jsfiddle.net/v24s8q1f/ 右下角看到列表中有H265表现成功。
二、效果图
三、干系地址
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_push。
四、功能特点
- 支持各种本地音视频文件和网络音视频文件,格式包括mp3、aac、wav、wma、mp4、mkv、rmvb、wmv、mpg、flv、asf等。
- 支持各种网络音视频流,网络摄像头,协议包括rtsp、rtmp、http等。
- 支持本地摄像头装备推流,可指定分辨率、帧率、格式等。
- 支持本地桌面采集推流,可指定屏幕索引、采集区域、起始坐标、帧率等,也支持指定窗口标题进行采集。
- 可实时切换预览视频文件,可切换音视频文件播放进度,切换到那边就推流到那边。预览过程中可以切换静音状态和暂停推流。
- 可指定重新编码推流,任意源头格式可选强转264或265格式。
- 可转换分辨率推流,设置等比例缩放大概指定分辨率进行转换。
- 推流的清晰度、质量、码率都可调,可以节约网络带宽和拉流端的压力。
- 音视频文件主动循环不中断推流。
- 音视频流有主动掉线重连机制,重连成功主动继续推流。
- 支持各种流媒体服务步调,包括但不限于mediamtx、ZLMediaKit、srs、LiveQing、nginx-rtmp、EasyDarwin、ABLMediaServer。
- 通过设置文件主动加载对应流媒体步调的协议和端口,主动天生推流地址和各种协议的拉流地址。可以通过设置文件自己增长流媒体步调。
- 可选rtmp、rtmp格式推流,推流成功后,支持多种格式拉流,包括但不限于rtsp、rtmp、hls、flv、ws-flv、webrtc等。
- 在软件上推流成功后,可以直接单击网页预览,实时预览推流后拉流的画面,多画面网页展示。
- 软件界面上可单击对应按钮,动态添加文件和目录,可手动输入地址。
- 推拉流实时性极高,延迟极低,延迟时间大概在100ms左右。
- 极低CPU资源占用,4路主码流推流只需要占用0.2%CPU。理论上通例平常PC机器推100路毫无压力,重要性能瓶颈在网络。
- 可以推流到外网服务器,然后通过手机、电脑、平板等装备播放对应的视频流。
- 每路推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照计谋随机天生hash值。也支持主动按照指定标识后面加数字的方式递增定名。好比设置标识为字母v,计谋为标识递增,则每添加一个对应的推流码定名依次是v1、v2、v3等。
- 根据推流协议主动转码格式,默认计谋按照选择的推流协议,好比rtsp支持265而rtmp不支持,如果是265的文件而选择rtmp推流,则主动转码成264格式再推流。
- 音视频同步推流,在拉流和采集的时间就会主动处理好同步,同步后的数据再推流。
- 表格中实时表现每一起推流的分辨率和音视频数据状态,灰色表现没有输入流,黑色表现没有输出流,绿色表现原数据推流,赤色表现转码后的数据推流。
- 主动重连视频源,主动重连流媒体服务器,包管启动后,推流地址和打开地址都实时重连,只要恢复后立即连上继续采集和推流。
- 根据差别的流媒体服务器类型,主动天生对应的rtsp、rtmp、hls、flv、ws-flv、webrtc拉流地址,用户可以直接复制该地址到播放器大概网页中预览检察。
- 添加的推流地址等信息主动存储到文件,可以手动打开进行修改,默认启动后主动加载历史记录。
- 可以指定天生的网页文件保存位置,方便作为网站网页发布,可以直接在欣赏器中输入网址进行访问,发布后可以直接在局域网其他装备好比手机大概电脑打开对应网址访问。
- 可选是否开机启动、后台运行等。网络推流添加的rtsp地址可勾选是否隐蔽地址中的用户信息。
- 自带装备推流模块,主动识别本地装备,包括本地的摄像头和桌面,可以手动选择差别的是视频和音频采集装备进行推流。
- 自带文件点播模块,添加文件后用户可以拉取地址点播,用户端可以任意切换播放进度。支持各种欣赏器(谷歌chromium、微软edge、火狐firefox等)、各种播放器(vlc、mpv、ffplay、potplayer、mpchc等)打开哀求。
- 文件点播模块实时统计表现每个文件对应的访问数量、总访问数量、差别IP地址访问数量。
- 文件点播模块采用纯QTcpSocket通讯,不依靠流媒体服务步调,焦点源码不到500行,解释详细,功能完整。
- 支持任意Qt版本(Qt4、Qt5、Qt6),支持任意系统(windows、linux、macos、android、嵌入式linux等)。
五、干系代码
- #include "netpushclient.h"
- #include "ffmpegthread.h"
- #include "ffmpegsave.h"
- #include "videohelper.h"
- #include "osdgraph.h"
- bool NetPushClient::checkB = false;
- bool NetPushClient::recordInteger = false;
- int NetPushClient::recordDuration = 0;
- int NetPushClient::encodeVideo = 0;
- float NetPushClient::encodeVideoRatio = 1;
- QString NetPushClient::encodeVideoScale = "1";
- NetPushClient::NetPushClient(QObject *parent) : QObject(parent)
- {
- ffmpegThread = NULL;
- ffmpegSave = NULL;
- //定时器控制多久录制一个文件
- timerRecord = new QTimer(this);
- timerRecord->setInterval(1000);
- connect(timerRecord, SIGNAL(timeout()), this, SLOT(checkRecord()));
- }
- NetPushClient::~NetPushClient()
- {
- this->stop();
- }
- QString NetPushClient::getMediaUrl()
- {
- return this->mediaUrl;
- }
- QString NetPushClient::getPushUrl()
- {
- return this->pushUrl;
- }
- FFmpegThread *NetPushClient::getVideoThread()
- {
- return this->ffmpegThread;
- }
- void NetPushClient::checkRecord()
- {
- //0. 时长单位分钟/触发条件自动重新录像/recordDuration=0/表示禁用录像
- //1. recordInteger参数控制是否整数倍数录像/recordDuration参数控制录制文件时长/整数倍录像下时长为对应的模数
- //2. 一般监控行业会按照整点录像/比如30分钟60分钟一个视频文件/这样录制的文件起始时间和结束时间整整齐齐
- //3. 整点录像情况下除了第一个和最后一个录像文件可能时长不一样/中间的文件肯定时长都一样
- //4. 非整点录像就按照录像总时长计时/所有保存的文件都是按照时长保存的
- //5. recordInteger=true/recordDuration=5/表示每到5分钟的时候录制一个文件
- //6. 上面录制结果: 11:01开始录制/11:05结束上一个录制并重新录制/第一个文件时长4分钟
- //7. recordInteger=false/recordDuration=5/表示每过5分钟的时候录制一个文件
- //8. 上面录制结果: 11:01开始录制/11:06结束上一个录制并重新录制/第一个文件时长5分钟
- bool ok = false;
- QDateTime now = QDateTime::currentDateTime();
- qint64 offset = recordTime.msecsTo(now);
- if (recordInteger && recordDuration > 1) {
- QTime time = now.time();
- int min = time.minute();
- int sec = time.second();
- min = (min == 0 ? 60 : min);
- ok = ((min % recordDuration == 0) && sec >= 0 && sec <= 2);
- //qDebug() << TIMEMS << min << sec << (min % recordDuration == 0) << offset << ok;
- } else {
- ok = (offset >= (recordDuration * 60 * 1000));
- //qDebug() << TIMEMS << recordDuration << offset << ok;
- }
- if (ok && offset >= 5000) {
- this->record();
- }
- }
- void NetPushClient::record()
- {
- if (ffmpegSave) {
- //取出推流码
- QString flag = pushUrl.split("/").last();
- //文件名不能包含特殊字符/需要替换成固定字母
- QString pattern("[\\\\/:|*?"<>]|[cC][oO][mM][1-9]|[lL][pP][tT][1-9]|[cC][oO][nM]|[pP][rR][nN]|[aA][uU][xX]|[nN][uU][lL]");
- #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
- QRegularExpression rx(pattern);
- #else
- QRegExp rx(pattern);
- #endif
- flag.replace(rx, "X");
- //文件名加上时间结尾
- QString path = QString("%1/video/%2").arg(qApp->applicationDirPath()).arg(QDATE);
- QString name = QString("%1/%2_%3.mp4").arg(path).arg(flag).arg(STRDATETIME);
- //目录不存在则新建
- QDir dir(path);
- if (!dir.exists()) {
- dir.mkpath(path);
- }
- //先停止再打开重新录制
- ffmpegSave->stop();
- ffmpegSave->open(name);
- recordTime = QDateTime::currentDateTime();
- }
- }
- void NetPushClient::receivePlayStart(int time)
- {
- //演示添加OSD后推流
- #ifdef betaversion
- int height = ffmpegThread->getVideoHeight();
- QList<OsdInfo> osds = OsdGraph::getTestOsd(height);
- ffmpegThread->setOsdInfo(osds);
- #endif
- //打开后才能启动录像
- ffmpegThread->recordStart(pushUrl);
- //推流以外还单独存储
- if (!ffmpegSave && recordDuration > 0) {
- //源头保存没成功就不用继续
- FFmpegSave *saveFile = ffmpegThread->getSaveFile();
- if (!saveFile->getIsOk()) {
- return;
- }
- ffmpegSave = new FFmpegSave(this);
- //重新编码过的则取视频保存类的对象
- AVStream *videoStreamIn = saveFile->getVideoEncode() ? saveFile->getVideoStream() : ffmpegThread->getVideoStream();
- AVStream *audioStreamIn = saveFile->getAudioEncode() ? saveFile->getAudioStream() : ffmpegThread->getAudioStream();
- ffmpegSave->setSavePara(ffmpegThread->getMediaType(), SaveVideoType_Mp4, videoStreamIn, audioStreamIn);
- this->record();
- timerRecord->start();
- }
- }
- void NetPushClient::receivePacket(AVPacket *packet)
- {
- if (ffmpegSave && ffmpegSave->getIsOk()) {
- ffmpegSave->writePacket2(packet);
- }
- FFmpegHelper::freePacket(packet);
- }
- void NetPushClient::recorderStateChanged(const RecorderState &state, const QString &file)
- {
- int width = 0;
- int height = 0;
- int videoStatus = 0;
- int audioStatus = 0;
- if (ffmpegThread) {
- width = ffmpegThread->getVideoWidth();
- height = ffmpegThread->getVideoHeight();
- FFmpegSave *saveFile = ffmpegThread->getSaveFile();
- if (saveFile->getIsOk()) {
- if (saveFile->getVideoIndexIn() >= 0) {
- if (saveFile->getVideoIndexOut() >= 0) {
- videoStatus = (saveFile->getVideoEncode() ? 3 : 2);
- } else {
- videoStatus = 1;
- }
- }
- if (saveFile->getAudioIndexIn() >= 0) {
- if (saveFile->getAudioIndexOut() >= 0) {
- audioStatus = (saveFile->getAudioEncode() ? 3 : 2);
- } else {
- audioStatus = 1;
- }
- }
- }
- }
- //只有处于录制中才表示正常推流开始
- bool start = (state == RecorderState_Recording);
- emit pushStart(mediaUrl, width, height, videoStatus, audioStatus, start);
- }
- void NetPushClient::receiveSaveStart()
- {
- emit pushChanged(mediaUrl, 0);
- }
- void NetPushClient::receiveSaveFinsh()
- {
- emit pushChanged(mediaUrl, 1);
- }
- void NetPushClient::receiveSaveError(int error)
- {
- emit pushChanged(mediaUrl, 2);
- }
- void NetPushClient::setMediaUrl(const QString &mediaUrl)
- {
- this->mediaUrl = mediaUrl;
- }
- void NetPushClient::setPushUrl(const QString &pushUrl)
- {
- this->pushUrl = pushUrl;
- }
- void NetPushClient::start()
- {
- if (ffmpegThread || mediaUrl.isEmpty() || pushUrl.isEmpty()) {
- return;
- }
- //实例化视频采集线程
- ffmpegThread = new FFmpegThread;
- //关联播放开始信号用来启动推流
- connect(ffmpegThread, SIGNAL(receivePlayStart(int)), this, SLOT(receivePlayStart(int)));
- //关联录制信号变化用来判断是否推流成功
- connect(ffmpegThread, SIGNAL(recorderStateChanged(RecorderState, QString)), this, SLOT(recorderStateChanged(RecorderState, QString)));
- //设置播放地址
- ffmpegThread->setMediaUrl(mediaUrl);
- //设置视频模式
- #ifdef openglx
- ffmpegThread->setVideoMode(VideoMode_Opengl);
- #else
- ffmpegThread->setVideoMode(VideoMode_Painter);
- #endif
- //设置通信协议(如果是rtsp视频流建议设置tcp)
- //ffmpegThread->setTransport("tcp");
- //设置硬解码(和推流无关/只是为了加速显示/推流只和硬编码有关)
- //ffmpegThread->setHardware("dxva2");
- //设置缓存大小(如果分辨率帧率码流很大需要自行加大缓存)
- ffmpegThread->setCaching(8192000);
- //设置解码策略(推流的地址再拉流建议开启最快速度)
- //ffmpegThread->setDecodeType(DecodeType_Fastest);
- //设置读取超时时间超时后会自动重连
- ffmpegThread->setReadTimeout(10 * 1000);
- //设置连接超时时间(0表示一直连)
- ffmpegThread->setConnectTimeout(0);
- //设置重复播放相当于循环推流
- ffmpegThread->setPlayRepeat(true);
- //设置默认不播放音频(界面上切换到哪一路就开启)
- ffmpegThread->setPlayAudio(false);
- //设置默认不预览视频(界面上切换到哪一路就开启)
- ffmpegThread->setPushPreview(false);
- //设置保存视频类将数据包信号发出来用于保存文件
- FFmpegSave *saveFile = ffmpegThread->getSaveFile();
- saveFile->setProperty("checkB", checkB);
- saveFile->setSendPacket(recordDuration > 0, false);
- connect(saveFile, SIGNAL(receivePacket(AVPacket *)), this, SLOT(receivePacket(AVPacket *)));
- connect(saveFile, SIGNAL(receiveSaveStart()), this, SLOT(receiveSaveStart()));
- connect(saveFile, SIGNAL(receiveSaveFinsh()), this, SLOT(receiveSaveFinsh()));
- connect(saveFile, SIGNAL(receiveSaveError(int)), this, SLOT(receiveSaveError(int)));
- //如果是本地设备或者桌面录屏要取出其他参数
- VideoHelper::initVideoPara(ffmpegThread, mediaUrl, encodeVideoRatio, encodeVideoScale);
- //设置视频编码格式/视频压缩比率/视频缩放比例
- ffmpegThread->setEncodeVideo((EncodeVideo)encodeVideo);
- ffmpegThread->setEncodeVideoRatio(encodeVideoRatio);
- ffmpegThread->setEncodeVideoScale(encodeVideoScale);
- //启动播放
- ffmpegThread->play();
- }
- void NetPushClient::stop()
- {
- //停止推流和采集并彻底释放对象
- if (ffmpegThread) {
- ffmpegThread->recordStop();
- ffmpegThread->stop();
- ffmpegThread->deleteLater();
- ffmpegThread = NULL;
- }
- //停止录制
- if (ffmpegSave) {
- timerRecord->stop();
- ffmpegSave->stop();
- ffmpegSave->deleteLater();
- ffmpegSave = NULL;
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |