java接入海康威视摄像头sdk后,如何推流给前端?
海康提供的demo用的Java Swing开发的GUI程序,怎么把这个视频转到前端vue项目,后端是怎么生成流地址?
之前贴的代码我删了,我重新整理了一下,有懂得大佬来指导一下
开发背景:目前开发环境是摄像头直接连接我本地电脑做开发,springboot框架,不走云视频,本地调海康的SDK得到视频流
我的想法:本地搭建一个流媒体服务ZLMediaKit,海康回调的视频流我通过java代码推到这个流媒体服务上,然后前端vue再去从这个流媒体服务上的rtsp地址直接拉流?
下面是业务类,项目启动时调用,ClientHikVision是从海康demo整理出来的
@Servicepublic 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;
// }
}
}
}
}
上面的数据流写入到管道里,后面代码我不知道写的对不对,走了一个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();
}
}
System.out.println(grabber.toString());grabber.startUnsafe();这两行代码都走了,但是到这就停住阻塞不动了,麻烦大佬帮忙看下
参考资料 转:
大华、海康SDK对接,使用javacv+流媒体服务实现实时播放和回放
JavaCV中FFmpegFrameGrabber调用start()方法时出现阻塞的解决办法
回答:
海康一般都是rtsp的流,后端需要用ffmpeg进行转流为flv格式推给前端
回答:
播放这种功能不都是前台直接对接的吗,从后台转一下的意义是啥
回答:
我也和你一样,我现在也只是拿到了byte[] 但是我不知道该怎么转成前端可以用的RTSP格式 这个怎么弄啊
回答:
前端请求接口,接口使用异步方式不结束,然后使用FFmpegFrameGrabber读取RTSP,结果交给FFmpegFrameRecorder。FFmpegFrameRecorder转码成flv,结果交给下面的类,下面的类会解析byte数组,将flv包源源不断写给响应流,就像文件下载形式一样,前端flv播放器拿到flv包就会解析播放,斗鱼虎牙都是http-flv这种方案。
以上是 java接入海康威视摄像头sdk后,如何推流给前端? 的全部内容, 来源链接: utcz.com/p/935312.html