通过海康sdk捕获码流数据实现抓图功能
程序员文章站
2022-07-05 10:18:54
...
通过海康sdk捕获码流数据实现抓图功能
个人博客:banmajio’s blog
海康sdk二次开发系列文章
海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP)
海康sdk进行历史回放时,码流数据回调过快问题的解决方法
海康sdk项目(java)部署Linux环境相关问题总结
海康sdk部署Linux环境下无法播放子码流的问题
海康sdk项目部署Linux系统时出现java.lang.UnstisfiedLinkError:jnidispatch(xxx)not found in resource path错误
通过海康sdk实现指定时间段内的录像文件下载
问题描述
在对监控直播或回放进行抓图操作时,大概有三种方式。
- 直接使用播放器抓图,例如video.js、easyplayer.js等播放器大多提供了抓图按钮,可以直接截取播放器画面到客户端。但是缺点就是如果服务需要定时截图就无法满足了。
- 使用海康sdk二次开发,调用海康自己的接口如NET_DVR_PlayBackCaptureFile等接口实现抓图功能。但是这些接口无一例外,需要搭配海康的播放库才能实现,也就是需要在调用预览接口或者回放接口是传入窗口句柄,否则调用该接口无效,或返回错误码23(调用次序出错)
- 依旧是海康sdk二次开发,可以在预览和回放接口中传入回调函数,捕获ps封装的h264数据,通过opencv时间抓图操作
实现方式
本篇文章以直播的抓图为例作为教程,回放的抓图与之同理。
首先需要明白海康sdk接口调用的流程:
如图所示,根据流程参考海康sdk文档调用接口,我们的抓图操作是要在启动预览之后,注册了回调函数,并且回调函数中有码流数据回调时进行的。
将回调函数中的码流数据写入到管道流中
首先确保直播预览接口调用无误,并且回调函数可以正常执行,当调用了抓图接口,回调函数中得到了抓图开始的标志 开始将回调的码流数据copy一份到管道流中。管道流的使用请参考海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP)
package com.banmajio.callback;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import com.junction.sdk.HCNetSDK.FRealDataCallBack_V30;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.ByteByReference;
/**
* @Title RealDataCallBack.java
* @description 实时预览回调函数
* @time 2020年3月17日 下午2:45:08
* @author banmajio
**/
public class RealDataCallBack implements FRealDataCallBack_V30 {
private PipedOutputStream outputStream;// 管道输出流
private PipedOutputStream picOutputStream;// 抓图管道流
public static boolean playbackcapture = false;// 开始抓图标志 true:开始抓图 false:结束抓图
public RealDataCallBack(PipedOutputStream outputStream) {
this.outputStream = outputStream;
}
public void setPicOutputStream(PipedOutputStream picOutputStream) {
this.picOutputStream = picOutputStream;
}
@Override
public void invoke(NativeLong lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
try {
if (playbackcapture) {
// 将数据同时写入抓图管道流中
picOutputStream.write(pBuffer.getPointer().getByteArray(0, dwBufSize));
}
outputStream.write(pBuffer.getPointer().getByteArray(0, dwBufSize));
} catch (IOException e) {
// logger.error(e.getMessage());
}
}
}
读取管道流获取AVFrame帧,转为图片保存到本地
从管道流中取出数据,喂到javacv的帧抓取器FFmpegFrameGrabber中,获取码流数据的AVFrame帧,通过opencv的函数,将帧转为图片输出。代码如下:
package com.banmajio.play;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Date;
import javax.imageio.ImageIO;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber.Exception;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.opencv_core.IplImage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @ClassName: PlayBackCapture
* @Description:抓图
* @author: banmajio
* @date: 2020-11-13
*/
public class PlayBackCapture {
private final static Logger logger = LoggerFactory.getLogger(PlayBackCapture.class);
private PipedInputStream picInputStream;// 抓图输入流
private PipedOutputStream picOutputStream;// 抓图输出流
private FFmpegFrameGrabber grabber;// 抓流器
private ArrayList<String> picturePaths = new ArrayList<>();
public PlayBackCapture(PipedInputStream picInputStream, PipedOutputStream picOutputStream) {
this.picInputStream = picInputStream;
this.picOutputStream = picOutputStream;
}
public void setPicturePath(String picturepath) {
picturePaths.add(picturepath);
}
public void playBackCapture(String token) throws IOException, InterruptedException {
try {
picInputStream.connect(picOutputStream);
grabber = new FFmpegFrameGrabber(picInputStream, 0);
//检测管道流中是否存在数据,如果2s后依然没有写入1024的数据,则认为管道流中无数据,避免grabber.start();发生阻塞
long stime = new Date().getTime();
while (true) {
Thread.sleep(100);
if (new Date().getTime() - stime > 2000) {
return;
}
if (picInputStream.available() == 1024) {
break;
}
}
grabber.start();
String rotate = grabber.getVideoMetadata("rotate");// 视频的旋转角度
Frame frame = null;
int frameIndex = 0;
int pictureIndex = 0;
while (frameIndex < 10) {
// 获取image帧
frame = grabber.grabImage();
if (null != frame && null != frame.image) {
IplImage src = null;
if (null != rotate && rotate.length() > 1) {
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
src = converter.convert(frame);
frame = converter.convert(rotate(src, Integer.valueOf(rotate)));
}
doExecuteFrame(frame, picturePaths.get(pictureIndex), frameIndex);
logger.info("hcsdk " + " 抓图完成 保存路径为:" + picturePaths.get(pictureIndex));
pictureIndex++;
if (pictureIndex < picturePaths.size()) {
continue;
} else {
break;
}
}
frameIndex++;
}
} catch (Exception e) {
logger.info("hcsdk " + " 抓图失败");
grabber.stop();
grabber.close();
picInputStream.close();
picOutputStream.close();
e.printStackTrace();
}
grabber.stop();
grabber.close();
picInputStream.close();
picOutputStream.close();
}
private void doExecuteFrame(Frame frame, String picturepath, int frameIndex) throws IOException {
if (null == frame || null == frame.image) {
return;
}
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bi = converter.getBufferedImage(frame);
File output = new File(picturepath);
try {
ImageIO.write(bi, "jpg", output);
} catch (IOException e) {
e.printStackTrace();
}
}
private IplImage rotate(IplImage src, Integer angle) {
IplImage img = IplImage.create(src.height(), src.width(), src.depth(), src.nChannels());
opencv_core.cvTranspose(src, img);
opencv_core.cvFlip(img, img, angle);
return img;
}
}
推荐阅读