海康视频 rtnp转 flv

小豆·2022-01-14 17:07·1080 次阅读

海康视频 rtnp转 flv

1.海康视频实时播放视频#

1.1 了解海康视频sdk
1.1.1 获取海康视频sdk和SDK文档

可以去官网下载也可以去百度网盘下载
链接:https://pan.baidu.com/s/1a5tmB1lwmb0ON_BRGB4XxQ
提取码:1111

1.1.2 查看sdk播放视频全流程
  • 初始化SDK
Copy
static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
  • 注册设备,获取返回的IUser
Copy
lUserID = hCNetSDK.NET_DVR_Login_V30("182.104.192.XX", (short) 8002, "admin", "密码", m_strDeviceInfo);
  • 根据获取到的lUserID 调用实时预览的接口
Copy
//初始化客户端信息数据 m_strClientInfo = new HCNetSDK.NET_DVR_CLIENTINFO(); //获取视频通道 m_strClientInfo.lChannel = new NativeLong(iChannelNum); //根据通道编号以及注册设备返回的UserId调用视频实时预览的API,返回值是窗口的句柄号 lPreviewHandle = hCNetSDK.NET_DVR_RealPlay_V30(lUserID, m_strClientInfo, null, null, true);
  • 通过回调参数获取流数据
Copy
NET_DVR_RealPlay_V30的第三个参数可以自定义实时预览的回调方法,回调方法的调用如下

代码例子如下

Copy
class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 { //预览回调 public void invoke(NativeLong lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) { HWND hwnd = new HWND(Native.getComponentPointer(panelRealplay)); switch (dwDataType) { case HCNetSDK.NET_DVR_SYSHEAD: //系统头 if (!playControl.PlayM4_GetPort(m_lPort)) //获取播放库未使用的通道号 { break; } if (dwBufSize > 0) { if (!playControl.PlayM4_SetStreamOpenMode(m_lPort.getValue(), PlayCtrl.STREAME_REALTIME)) //设置实时流播放模式 { break; } if (!playControl.PlayM4_OpenStream(m_lPort.getValue(), pBuffer, dwBufSize, 1024 * 1024)) //打开流接口 { break; } if (!playControl.PlayM4_Play(m_lPort.getValue(), hwnd)) //播放开始 { break; } } case HCNetSDK.NET_DVR_STREAMDATA: //码流数据 if ((dwBufSize > 0) && (m_lPort.getValue().intValue() != -1)) { if (!playControl.PlayM4_InputData(m_lPort.getValue(), pBuffer, dwBufSize)) //输入流数据 { break; } } } } }

pBuffer.getValue()就是流数据

1.2 海康视频转码#

1.2.1 思路1 将byte[]转成视频文件(.flv),再在前端进行播放
Copy
/** * 将字节流转换成文件 * @param filename * @param data * @throws Exception */ public static void saveFile(String filename,byte [] data)throws Exception{ if(data != null){ String filepath ="D:\\" + filename; File file = new File(filepath); if(file.exists()){ file.delete(); } FileOutputStream fos = new FileOutputStream(file); fos.write(data,0,data.length); fos.flush(); fos.close(); } }

优点:不依赖其他中间件,纯代码实现,部署简单,代码编写也简单
缺点:
这样虽然能播放视频,但是直播流的视频都是实时的,而不是一个完整的flv视频文件,如果要实现直播流,要分割成很多个文件,然后一次播放,而且视频延时很大,前端要以此播放转好的视频,前端代码实现麻烦

1.2.2 思路2:byte[] —> PipedOutputStream —> PipedInputStream —>FFmpegFrameGrabber

byte{} 转为 PipedOutputStream

Copy
//byte{} 转为 PipedOutputStream PipedInputStream inputStream = new PipedInputStream(byte); PipedOutputStream outputStream = new PipedOutputStream(); inputStream.connect(outputStream); // PipedInputStream —> FFmpegFrameGrabber FFmpegFrameGrabber ff = new MyFFmpegFrameGrabber(InputStream inputStream)

参考博客:https://blog.csdn.net/weixin_40777510/article/details/105840823
缺点:播放3路视频以上cpu占用率过高,过于占用服务器资源,而且长时间播放 JavaCV 经常出现 OOM ,暂无法解决

1.2.3 思路3 获取海康视频的rtnp地址,通过JavaCV推送到视频流

步骤:拉取rtnp视频流—>FFmpeg将视频流转成FLV流视频—>推送到nginx服务器
——>返回播放地址给前端

具体流程图如下
image

前端就可以用Flv.js进行播放了

Copy
grabber = new MyFFmpegFrameGrabber(pojo.getRtsp()); grabber.setOption("rtsp_transport", "tcp"); // 设置采集器构造超时时间 grabber.setOption("stimeout", "2000000"); if ("sub".equals(pojo.getStream())) { grabber.start(videoPushConfig.getSub_code()); } else if ("main".equals(pojo.getStream())) { grabber.start(videoPushConfig.getMain_code()); } else { grabber.start(videoPushConfig.getMain_code()); } // 部分监控设备流信息里携带的帧率为9000,如出现此问题,会导致dts、pts时间戳计算失败,播放器无法播放,故出现错误的帧率时,默认为25帧 if (grabber.getFrameRate() > 0 && grabber.getFrameRate() < 100) { framerate = grabber.getFrameRate(); } else { framerate = 25.0; } int width = grabber.getImageWidth(); int height = grabber.getImageHeight(); // 若视频像素值为0,说明拉流异常,程序结束 if (width == 0 && height == 0) { log.error(pojo.getRtsp() + " 拉流异常!"); grabber.stop(); grabber.close(); release(); return; } recorder = new FFmpegFrameRecorder(pojo.getRtmp(), grabber.getImageWidth(), grabber.getImageHeight()); recorder.setInterleaved(true); // 关键帧间隔,一般与帧率相同或者是视频帧率的两倍 recorder.setGopSize((int) framerate * 2); // 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏) recorder.setFrameRate(framerate); // 设置比特率 recorder.setVideoBitrate(grabber.getVideoBitrate()); // 封装flv格式 recorder.setFormat("flv"); // h264编/解码器 recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); Map<String, String> videoOption = new HashMap<>(); // 该参数用于降低延迟 videoOption.put("tune", "zerolatency"); /** ** 权衡quality(视频质量)和encode speed(编码速度) values(值): * * ultrafast(终极快),superfast(超级快), veryfast(非常快), faster(很快), fast(快), * * medium(中等), slow(慢), slower(很慢), veryslow(非常慢) * * ultrafast(终极快)提供最少的压缩(低编码器CPU)和最大的视频流大小;而veryslow(非常慢)提供最佳的压缩(高编码器CPU)的同时降低视频流的大小 */ videoOption.put("preset", "ultrafast"); // 画面质量参数,0~51;18~28是一个合理范围 videoOption.put("crf", "28"); recorder.setOptions(videoOption); AVFormatContext fc = grabber.getFormatContext(); recorder.start(fc); log.debug("开始推流 设备信息:[ip:" + pojo.getIp() + " channel:" + pojo.getChannel() + " stream:" + pojo.getStream() + " starttime:" + pojo.getStarttime() + " endtime:" + pojo.getEndtime() + " rtsp:" + pojo.getRtsp() + " url:" + pojo.getUrl() + "]"); // 清空探测时留下的缓存 grabber.flush(); AVPacket pkt = null; long dts = 0; long pts = 0; int timebase = 0; for (int no_frame_index = 0; no_frame_index < 10 && err_index < 5;) { long time1 = System.currentTimeMillis(); if (exitcode == 1) { break; } pkt = grabber.grabPacket(); if (pkt == null || pkt.size() == 0 || pkt.data() == null) { // 空包记录次数跳过 log.warn("JavaCV 出现空包 设备信息:[ip:" + pojo.getIp() + " channel:" + pojo.getChannel() + " stream:" + pojo.getStream() + " starttime:" + pojo.getStarttime() + " endtime:" + " rtsp:" + pojo.getRtsp() + pojo.getEndtime() + " url:" + pojo.getUrl() + "]"); no_frame_index++; continue; } // 过滤音频 if (pkt.stream_index() == 1) { av_packet_unref(pkt); continue; } // 矫正sdk回调数据的dts,pts每次不从0开始累加所导致的播放器无法续播问题 pkt.pts(pts); pkt.dts(dts); err_index += (recorder.recordPacket(pkt) ? 0 : 1); // pts,dts累加 timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den(); pts += timebase / (int) framerate; dts += timebase / (int) framerate; // 将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间。 av_packet_unref(pkt); long endtime = System.currentTimeMillis(); if ((long) (1000 /framerate) - (endtime - time1) > 0) { Thread.sleep((long) (1000 / framerate) - (endtime - time1)); }

参考这篇博客
https://blog.csdn.net/weixin_40777510/article/details/103764198

利用Nginx搭建视频流服务器#

1.安装nginx
此此过程可以看我之前的博客
https://www.cnblogs.com/xiaodou00/p/13470548.html
2.安装rtmp视频流插件以及flv视频插件
链接:https://pan.baidu.com/s/1a5tmB1lwmb0ON_BRGB4XxQ
提取码:1111
3,把现在好的插件上传的linux服务器,执行动态添加模块的指令./configure
configure是nginx下载下来以后自带的一个脚本文件,如果发现nginx目录下没有configure这个脚本,重新下载一个nginx,添加该脚本
eg:

Copy
./configure --add-module=../nginx-rtmp-module-master

4,编译nginx

Copy
make

5.备份sbin中的nginx主文件,复制新编译的nginx文件到sbin中

Copy
cp /opt/nginx-1.9.5/sbin/nginx /opt/nginx-1.9.5/sbin/nginx.bak # cp ./objs/nginx /opt/nginx-1.9.5/sbin/

6.重新运行nginx

Copy
nginx -s stop nginx

7.查看nginx中的插件

Copy
nginx -v

看看有没有之前上面添加的两个模块
8.修改nginx配置文件
添加rtmp应用

Copy
rtmp { server { listen 1935; # 监听端口 chunk_size 4000; application live33 { live on; gop_cache on; } } }

9.添加flv的http监听路径

Copy
server { listen 8090; server_name test72.qtopay.cn 127.0.0.1; location /stat { #第二处添加的location字段。 rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { #第二处添加的location字段。 root /usr/local/nginx/nginx-rtmp-module-master/; } location /rtmpLive { flv_live on; chunked_transfer_encoding on; #open 'Transfer-Encoding: chunked' response add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header add_header Access-Control-Allow-Headers X-Requested-With; add_header Access-Control-Allow-Methods GET,POST,OPTIONS; add_header 'Cache-Control' 'no-cache'; } }
posted @   0小豆0  阅读(1080)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
隐藏
对话
对话
点击右上角即可分享
微信分享提示
目录