大华、海康SDK对接,使用javacv+流媒体服务实现实时播放和回放

最近需要对接大华和海康摄像头,用SDK的方式,实现登录、OSD设置、预览、回放等功能,其他的功能都还好,实时预览和回放,里面涉及的东西太多了,对于刚接触摄像头开发相关的小伙伴来说,简直要崩溃,而且这方面的资料少之又少,我也是被这么折磨过来的,查阅各种资料、博客、包括QQ群都加了不少,最后总算是有点成果了,所以就把成果记录下来,希望这篇文章能帮助大家吧。

首先,小的功能点(登录,加载SDK文件,获取通道信息等等)就不讲了,有问题可以评论留言,主要讲讲预览和回放,其实预览和回放功能是一样的,我就讲一下预览吧,讲之前有个点需要注意,海康的视频回调流是可以直接用的,大华的需要设置一下回调流的格式,SDK中有具体的方法,可以直接用。调用播放函数以后,会有一个回调,我们来看看回调方法:

public class HuaRealPlayCallBack implements NetSDKLib.fRealDataCallBackEx {

    public HuaRealPlayCallBack() {
    }

    private static class CallBackHolder {
        private static HuaRealPlayCallBack instance = new HuaRealPlayCallBack();
    }

    public static HuaRealPlayCallBack getInstance() {
        return HuaRealPlayCallBack.CallBackHolder.instance;
    }

    @Override
    public void invoke(NetSDKLib.LLong lRealHandle, int dwDataType, Pointer pBuffer,
                       int dwBufSize, int param, Pointer dwUser) {
        // 指定回调流为PS格式
        if (dwDataType == 1001) {
            try {
                HuaSdkHandle huaSdkHandle = (HuaSdkHandle) HuaSdkMapCache.getCache(lRealHandle);
                if (huaSdkHandle!=null) {
                    huaSdkHandle.onMediaStream(pBuffer.getByteArray(0, dwBufSize), true);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这里我用的是大华的,所以判断了接收的回调流格式,不然默认是私有流,在回调方法中,通过Map获取到当前播放的句柄,播放时put进去的,然后泛型指定为一个单独的处理类 : HuaSdkHandle,在回调中调用这个类的一个方法,就把回调的流数据传到那个方法中了,然后在那个方法中接收到流以后,要结合管道流PIpedInputStream,方便使用FFmpegGrabber拉流,因为要使用管道流作为入参进行拉流,然后在使用FFmpegRecord进行推流,关键代码如下:

    FFmpegFrameGrabber grabber = null;
    FFmpegFrameRecorder recorder = null;
    PipedInputStream inputStream;
    PipedOutputStream outputStream;
    String pushAddress;


   /**
     * 异步接收海康/大华/宇视设备sdk回调实时视频裸流数据
     *
     * @param data
     * @param size
     */
    public void push(byte[] data, int size) {

        try {
            outputStream.write(data, 0, size);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }



       grabber = new FFmpegFrameGrabber(inputStream, 0);
        grabber.setOption("rtsp_transport", "tcp");
        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();
        // 检测回调函数书否有数据流产生,防止avformat_open_input函数阻塞
        do {
            Thread.sleep(100);
            if (System.currentTimeMillis() - stime > 2000) {
                log.info("-----SDK回调无视频流产生------");
                return;
            }
        } while (inputStream.available() != 2048);

        // 只打印错误日志
        avutil.av_log_set_level(avutil.AV_LOG_QUIET);
        FFmpegLogCallback.set();

            grabber.start();
            log.info("--------开始推送视频流---------");
            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.start();
            int count = 0;
            Frame frame;
            while (grabber.hasVideo() && (frame = grabber.grab()) != null) {
                count++;
                if (count % 100 == 0) {
                    log.info("推送视频帧次数:{}", count);
                }
                if (frame.samples != null) {
                    log.info("检测到音频");
                }
                recorder.record(frame);
            }
            if (grabber != null) {
                grabber.stop();
                grabber.release();
            }
            if (recorder != null) {
                recorder.stop();
                recorder.release();
            }

其中pushAddress为流媒体服务的地址,流媒体服务用的是:ZLMediaKit,可以去看看相关文档,部署很简单的,我这里用的是rtmp协议推送的,到流媒体服务上以后,会自动注册上mp4 flv m3u8等播放格式,使用流媒体服务对应格式的播放地址就可以播放了,下面来看看回放的效果:

有问题可以加我Q咨询:2921843100。大概思路就是这样,直播是没任何问题的,当然这种方式很占用服务的网络和带宽,需要根据具体场景做优化,同时播放多少路,或者自动推流的机制等等,也踩了不少坑,大家多研究研究就会实现的。

猜你喜欢

转载自blog.csdn.net/ygl_csdn/article/details/123181204