zoukankan      html  css  js  c++  java
  • 海康视频 rtnp转 flv

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

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

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

    1.1.2 查看sdk播放视频全流程
    • 初始化SDK
    static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
    
    • 注册设备,获取返回的IUser
    lUserID = hCNetSDK.NET_DVR_Login_V30("182.104.192.XX",
    (short) 8002, "admin", "密码", m_strDeviceInfo);
    
    • 根据获取到的lUserID 调用实时预览的接口
    //初始化客户端信息数据
    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);
    
    • 通过回调参数获取流数据
    NET_DVR_RealPlay_V30的第三个参数可以自定义实时预览的回调方法,回调方法的调用如下
    

    代码例子如下

    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),再在前端进行播放
    /** 
    * 将字节流转换成文件 
    * @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

    //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进行播放了

    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:

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

    4,编译nginx

    make
    

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

    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

    nginx -s stop
    nginx
    

    7.查看nginx中的插件

    nginx -v
    

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

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

    9.添加flv的http监听路径

    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';
    	}
     }
    
  • 相关阅读:
    软件测试人员的要求
    冒烟测试和回归测试的区别
    [go]struct
    [go]socket编程
    [go]gorhill/cronexpr用go实现crontab
    [go]os/exec执行shell命令
    [go]time包
    [go]etcd使用
    [go]redis基本使用
    [go]go操作mysql
  • 原文地址:https://www.cnblogs.com/xiaodou00/p/15802679.html
Copyright © 2011-2022 走看看