java接入海康威视摄像头sdk后,如何推流给前端?

海康提供的demo用的Java Swing开发的GUI程序,怎么把这个视频转到前端vue项目,后端是怎么生成流地址?


之前贴的代码我删了,我重新整理了一下,有懂得大佬来指导一下
开发背景:目前开发环境是摄像头直接连接我本地电脑做开发,springboot框架,不走云视频,本地调海康的SDK得到视频流
我的想法:本地搭建一个流媒体服务ZLMediaKit,海康回调的视频流我通过java代码推到这个流媒体服务上,然后前端vue再去从这个流媒体服务上的rtsp地址直接拉流?
下面是业务类,项目启动时调用,ClientHikVision是从海康demo整理出来的

@Service
public class EquipmentHikVisionServiceImpl implements EquipmentHikVisionService {

    @Override
    @PostConstruct
    public void register() {
        ClientHikVision clientHikVision = new ClientHikVision();
        clientHikVision.initPipedStream();
        clientHikVision.clientInit();

        clientHikVision.action();
    }
}

ClientHikVision太长了,我贴出部分,这里基本都是初始化,连接设备都没问题

public class ClientHikVision {

    ExecutorService executor = Executors.newFixedThreadPool(5);

    FFmpegFrameGrabber grabber = null;
    FFmpegFrameRecorder recorder = null;
    PipedInputStream inputStream = new PipedInputStream();
    PipedOutputStream outputStream = new PipedOutputStream();

    String pushAddress = "rtsp://127.0.0.1:554/live/1";
   public void initData(){
        m_lPort= new IntByReference(-1);
        fRealDataCallBack = new FRealDataCallBack();
        fExceptionCallBack = new FExceptionCallBack_Imp();
    }
    public void initPipedStream(){
//        inputStream = new PipedInputStream();
//        outputStream = new PipedOutputStream();
        try {
            inputStream.connect(outputStream);
            System.out.println("Piped设置连接成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
public void clientInit() {
        if (hCNetSDK == null && playControl == null) {
            if (!CreateSDKInstance()) {
                System.out.println("Load SDK fail");
                return;
            }
            if (!CreatePlayInstance()) {
                System.out.println("Load PlayCtrl fail");
                return;
            }
        }
        //linux系统建议调用以下接口加载组件库
        if (osSelect.isLinux()) {
            HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
            HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
            //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
            String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";
            String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";

            System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
            ptrByteArray1.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());

            System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
            ptrByteArray2.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());

            String strPathCom = System.getProperty("user.dir") + "/lib/";
            HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
            System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
            struComPath.write();
            hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
        }

        boolean initSuc = hCNetSDK.NET_DVR_Init();
        if (!initSuc) {
            throw new CommonException(9001, "初始化失败");
        }
        if (fExceptionCallBack == null) {
            fExceptionCallBack = new FExceptionCallBack_Imp();
        }
        Pointer pUser = null;
        if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
            return;
        }
        System.out.println("设置告警回调成功");
        hCNetSDK.NET_DVR_SetLogToFile(3, "./sdklog", false);
        register();

    }
public void register() {
        //注册之前先注销已注册的用户,预览情况下不可注销
        if (bRealPlay) {
            throw new CommonException(9001, "注册新用户请先停止当前预览!");
        }

        if (lUserID > -1) {
            //先注销
            hCNetSDK.NET_DVR_Logout_V30(lUserID);
            lUserID = -1;
        }
        //注册
        m_sDeviceIP = EquipmentConstants.IP;//设备ip地址
        m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();
        int iPort = EquipmentConstants.PORT;
        lUserID = hCNetSDK.NET_DVR_Login_V30(m_sDeviceIP,
                (short) iPort, EquipmentConstants.ACCOUNT, EquipmentConstants.PSW, m_strDeviceInfo);

        long userID = lUserID;
        if (userID == -1) {
            m_sDeviceIP = "";//登录未成功,IP置为空
            int error;
            error = hCNetSDK.NET_DVR_GetLastError();
            throw new CommonException(9001, "注册失败,错误码:" + error);
        } else {
            initData();
        }
    }
public void action(){
        if (lUserID == -1) {
            throw new CommonException(9001,"请先注册");
        }

        //如果预览窗口没打开,不在预览
        if (bRealPlay == false) {
            //获取窗口句柄
//            W32API.HWND hwnd = new W32API.HWND(Native.getComponentPointer(panelRealplay));

            //获取通道号
            int iChannelNum = 1;//通道号

//            m_strClientInfo = new HCNetSDK.NET_DVR_CLIENTINFO();
//            m_strClientInfo.lChannel = new NativeLong(iChannelNum);
            HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
            strClientInfo.read();
//            strClientInfo.hPlayWnd = null;  //窗口句柄,从回调取流不显示一般设置为空
            strClientInfo.lChannel = iChannelNum;  //通道号
            strClientInfo.dwStreamType=0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
            strClientInfo.dwLinkMode=0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
            strClientInfo.bBlocked=1;  //0- 非阻塞取流,1- 阻塞取流

            //在此判断是否回调预览,0,不回调 1 回调
            //回调------------
            strClientInfo.hPlayWnd = null;
            strClientInfo.write();
            lPreviewHandle = hCNetSDK.NET_DVR_RealPlay_V40(lUserID,
                    strClientInfo, fRealDataCallBack, null);
            if (lPreviewHandle<=-1)
            {
                int error;
                error=hCNetSDK.NET_DVR_GetLastError();
                throw new CommonException(9001,"预览失败,错误码:"+error);
            }
            //------------

            long previewSucValue = lPreviewHandle;

            //预览失败时:
            if (previewSucValue == -1) {
                int error;
                error=hCNetSDK.NET_DVR_GetLastError();
                throw new CommonException(9001,"预览失败,错误码:"+error);
            }

            //预览成功的操作
            bRealPlay = true;

            //显示云台控制窗口
        }

        //如果在预览,停止预览,关闭窗口
        else {
            hCNetSDK.NET_DVR_StopRealPlay(lPreviewHandle);
            bRealPlay = false;
            if (m_lPort.getValue() != -1) {
                playControl.PlayM4_Stop(m_lPort.getValue());
                m_lPort.setValue(-1);
            }

            panelRealplay.repaint();
        }
    }

下面是海康demo里预览回调的方法,这个方法也在ClientHikVision类里,我单独贴出来了,System.out.println(Arrays.toString(bytes));这行也打印出来了,这个应该是原始的流数据吧

/******************************************************************************
     * 内部类:   FRealDataCallBack
     * 实现预览回调数据
     ******************************************************************************/
    class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
        //预览回调
        public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
//            W32API.HWND hwnd = new W32API.HWND(Native.getComponentPointer(panelRealplay));
//            System.out.println("【dwDataType:】"+dwDataType);
            if (dwDataType == HCNetSDK.NET_DVR_STREAMDATA) {   //码流数据
//                System.out.println("【dwBufSize:】"+dwBufSize);
                if (dwBufSize > 0) {
                    long offset = 0;
                    ByteBuffer buffers = pBuffer.getPointer().getByteBuffer(offset, dwBufSize);
                    byte[] bytes = new byte[dwBufSize];
                    buffers.rewind();
                    buffers.get(bytes);
                    System.out.println(Arrays.toString(bytes));
                    executor.execute(() -> {
                        push(bytes,dwBufSize);
                    });
//                        if (!playControl.PlayM4_InputData(m_lPort.getValue(), pBuffer, dwBufSize))  //输入流数据
//                        {
//                            break;
//                        }
                }
            }
        }
    }

image.png
上面的数据流写入到管道里,后面代码我不知道写的对不对,走了一个handle方法,grabber = new FFmpegFrameGrabber(inputStream,0),这里应该是消费管道中的数据流。这里用的ZLMediaKit做流媒体服务,我已经编译好启动了,但是不知道该怎么用,这个pushAddress地址我不知道写的对不对。里面的grabber.startUnsafe()方法每次调用都会阻塞,本来是grabber.start()方法的,两种方式都会阻塞,

public void push(byte[] data, int size) {
        try {
            outputStream.write(data, 0, size);
            handle();
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
public void handle() throws InterruptedException, IOException {
        System.out.println("-----开始处理流数据-----");
        grabber = new FFmpegFrameGrabber(inputStream,0);
        grabber.setOption("rtsp_transport", "tcp");
        grabber.setOption("stimeout", "2000000");
        grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        grabber.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        grabber.setAudioStream(Integer.MIN_VALUE);
        grabber.setFormat("mpeg");
        long stime = System.currentTimeMillis();
        System.out.println("------1111111111------");
        System.out.println(inputStream.available());
        // 检测回调函数书否有数据流产生,防止avformat_open_input函数阻塞
        do {
            Thread.sleep(100);
            if (System.currentTimeMillis() - stime > 2000) {
                System.out.println(("-----SDK回调无视频流产生------"));
                return;
            }
        }while (inputStream.available() <= 0);
        System.out.println("------222222222------");
//        do {
//            Thread.sleep(100);
//            if (System.currentTimeMillis() - stime > 2000) {
//                System.out.println(("-----SDK回调无视频流产生------"));
//                return;
//            }
//        } while (inputStream.available() != 2048);

        // 只打印错误日志
        avutil.av_log_set_level(avutil.AV_LOG_QUIET);
        FFmpegLogCallback.set();
        System.out.println(grabber.toString());
        grabber.startUnsafe();
        System.out.println(("--------开始推送视频流---------"));
        recorder = new FFmpegFrameRecorder(pushAddress, grabber.getImageWidth(),grabber.getImageHeight(), grabber.getAudioChannels());
        recorder.setInterleaved(true);
        // 画质参数
        recorder.setVideoOption("crf", "28");
        // H264编/解码器
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setVideoBitrate(grabber.getVideoBitrate());
        // 封装flv格式
        recorder.setFormat("flv");
        // 视频帧率,最低保证25
        recorder.setFrameRate(25);
        // 关键帧间隔 一般与帧率相同或者是帧率的两倍
        recorder.setGopSize(50);
        // yuv420p
        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        recorder.startUnsafe();
        int count = 0;
        Frame frame;
        while (grabber.hasVideo() && (frame = grabber.grab()) != null) {
            count++;
            if (count % 100 == 0) {
                System.out.println("推送视频帧次数:"+count);
            }
            if (frame.samples != null) {
                System.out.println("检测到音频");
            }
            recorder.record(frame);
        }
        if (grabber != null) {
            grabber.stop();
            grabber.release();
        }
        if (recorder != null) {
            recorder.stop();
            recorder.release();
        }
    }

image.png
System.out.println(grabber.toString());grabber.startUnsafe();这两行代码都走了,但是到这就停住阻塞不动了,麻烦大佬帮忙看下
image.png

参考资料 转:
大华、海康SDK对接,使用javacv+流媒体服务实现实时播放和回放
JavaCV中FFmpegFrameGrabber调用start()方法时出现阻塞的解决办法

阅读 1.2k
4 个回答

前端请求接口,接口使用异步方式不结束,然后使用FFmpegFrameGrabber读取RTSP,结果交给FFmpegFrameRecorder。FFmpegFrameRecorder转码成flv,结果交给下面的类,下面的类会解析byte数组,将flv包源源不断写给响应流,就像文件下载形式一样,前端flv播放器拿到flv包就会解析播放,斗鱼虎牙都是http-flv这种方案。
image.png
image.png

海康一般都是rtsp的流,后端需要用ffmpeg进行转流为flv格式推给前端

播放这种功能不都是前台直接对接的吗,从后台转一下的意义是啥

新手上路,请多包涵

我也和你一样,我现在也只是拿到了byte[] 但是我不知道该怎么转成前端可以用的RTSP格式 这个怎么弄啊

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏