欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

通过海康sdk捕获码流数据实现抓图功能

程序员文章站 2022-07-05 10:18:54
...

个人博客: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实现指定时间段内的录像文件下载

问题描述

在对监控直播或回放进行抓图操作时,大概有三种方式。

  1. 直接使用播放器抓图,例如video.js、easyplayer.js等播放器大多提供了抓图按钮,可以直接截取播放器画面到客户端。但是缺点就是如果服务需要定时截图就无法满足了。
  2. 使用海康sdk二次开发,调用海康自己的接口如NET_DVR_PlayBackCaptureFile等接口实现抓图功能。但是这些接口无一例外,需要搭配海康的播放库才能实现,也就是需要在调用预览接口或者回放接口是传入窗口句柄,否则调用该接口无效,或返回错误码23(调用次序出错)
  3. 依旧是海康sdk二次开发,可以在预览和回放接口中传入回调函数,捕获ps封装的h264数据,通过opencv时间抓图操作

实现方式

本篇文章以直播的抓图为例作为教程,回放的抓图与之同理。
首先需要明白海康sdk接口调用的流程:
通过海康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;
	}
}