项目中涉及网络摄像头的相关内容,主要是视频流的解析送后端做各种处理,需要CS方式、BS方式等各种展现方式。
通常调用方法分为设备厂家SDK解析调用和RTSP协议读流解析调用两种大方法。
其中海康设备RTSP协议地址为:
rtsp://admin:[email protected]:554/h265/ch1/sub/av_stream
主要关心参数为sub、main(区分主辅流)
云视通RTSP地址:
rtsp://192.168.1.203:8554/live1.265
主要关心参数为live0、live1(区分主辅流) 264、265区分协议 根据设备自定
还可以用ONVIF协议去discover,这里就不累赘了。
本片主要介绍RTSP协议的集中调用方法:
1、javacv 方式调用RTSP格式视频流 适合低并发CS模式
http://mvnrepository.com/artifact/org.bytedeco.javacpp-presets/opencv/3.2.0-1.3
package cn.xsw.rtsp;
import java.awt.Image;
import java.io.IOException;
import javax.swing.ImageIcon;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter;
import cn.xsw.tools.Config;
import cn.xsw.view.ATvMain;
/**
* 通过rstp方式获取视频流的帧
*
* @author xswsoft 2018年3月22日15:40:54
*/
public class RtspUtils implements Runnable {
static Java2DFrameConverter java2dFrameConverter = new Java2DFrameConverter();
static OpenCVFrameConverter.ToIplImage converterImage = new OpenCVFrameConverter.ToIplImage();
public static boolean startCap = false;
static int frameDate = 1000000;
private static FFmpegFrameGrabber grabber;
public static void main(String[] args) throws InterruptedException, IOException {
}
public RtspUtils() {
super();
init();
// TODO 自动生成的构造函数存根
}
private static void init() {
String rstpAdd = Config.getPropertyByName("view_video_source");
grabber = new FFmpegFrameGrabber(rstpAdd);
// grabber.setOption("rtsp_transport", "tcp");
// grabber.setImageHeight(704);
// grabber.setImageWidth(576);
}
private static void capFrameImg(FFmpegFrameGrabber grabber) {
try {
if (startCap == false) {
grabber.start();
startCap = true;
}
Frame frame = null;
while ((frame = grabber.grabFrame()) != null) {
ImageIcon imgc2 = new ImageIcon(java2dFrameConverter.convert(frame));
imgc2.setImage(imgc2.getImage().getScaledInstance(ATvMain.lbl_video.getWidth(),
ATvMain.lbl_video.getHeight(), Image.SCALE_DEFAULT));
ATvMain.lbl_video.setIcon(imgc2);
}
} catch (FrameGrabber.Exception e) {
e.printStackTrace();
System.out.println("抓图报错!");
}
}
@Override
public void run() {
// TODO 自动生成的方法存根
capFrameImg(grabber);
}
}
抓到图后咋处理就看自己了。
需要的jar包如下图:
优点:延迟低 <2s
2、webcam 方式 适合低并发CS模式
https://github.com/sarxos/webcam-capture
package cn.xsw.rtspview;
import java.awt.Dimension;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.JFrame;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamResolution;
import cn.xsw.utils.Config;
import uk.co.caprica.vlcj.medialist.MediaListItem;
public class WebcamRtspExample {
static {
String name = "Big Buck Bunny";
String rtsp = Config.getPropertyByName("view_video_source");
Webcam.setDriver(new VlcjDriver(Arrays.asList(new MediaListItem(name, rtsp, new ArrayList()))));
}
public static void main(String[] args) throws InterruptedException, IOException {
// 自定义分辨率
Dimension[] nonStandardResolutions = new Dimension[] {
// 640 * 480
WebcamResolution.VGA.getSize(),
// 960 * 640
WebcamResolution.DVGA.getSize(),
// WXGA1 1366 * 768
WebcamResolution.WXGA1.getSize(),
// 自定义分辨率
new Dimension(2000, 1000) };
Dimension dimension = WebcamResolution.WXGA1.getSize();
Webcam webcam = Webcam.getWebcams().get(0);
webcam.setCustomViewSizes(nonStandardResolutions);
webcam.setViewSize(dimension);
WebcamPanel panel = new WebcamPanel(webcam);
JFrame window = new JFrame("Webcam Panel");
window.add(panel);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
}
package cn.xsw.rtspview;
import java.awt.Dimension;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.sarxos.webcam.WebcamDevice;
import com.github.sarxos.webcam.WebcamDevice.FPSSource;
import com.github.sarxos.webcam.WebcamException;
import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.util.OsUtils;
import com.sun.jna.Memory;
import uk.co.caprica.vlcj.medialist.MediaListItem;
import uk.co.caprica.vlcj.player.MediaPlayer;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.direct.BufferFormat;
import uk.co.caprica.vlcj.player.direct.BufferFormatCallback;
import uk.co.caprica.vlcj.player.direct.DirectMediaPlayer;
import uk.co.caprica.vlcj.player.direct.RenderCallback;
import uk.co.caprica.vlcj.player.direct.format.RV32BufferFormat;
/**
* This is capture driver which uses vlcj library to
* access webcam hardware.
*
* @author Bartosz Firyn (SarXos)
*/
public class VlcjDevice implements WebcamDevice, BufferFormatCallback, RenderCallback, FPSSource {
/**
* This class is to convert vlcj {@link Memory} to {@link BufferedImage}.
*
* @author Bartosz Firyn (sarxos)
*/
private static class Converter {
/**
* Converters are cached and created on demand for every width-height tuple.
*/
private static final Map CONVERTERS = new HashMap();
/**
* The buffer data type (buffer consist of 4 byte values for every pixel).
*/
final int dataType = DataBuffer.TYPE_BYTE;
/**
* Number of bytes per pixel.
*/
final int pixelStride = 4;
/**
* Number of bytes per line.
*/
final int scanlineStride;
/**
* Band offsets for BGR components (B = 2, G = 1, R = 0).
*/
final int[] bgrBandOffsets = new int[] { 2, 1, 0 };
/**
* Number of bits per component.
*/
final int[] bits = { 8, 8, 8 };
/**
* Offset between pixels.
*/
final int[] offsets = new int[] { 0 };
/**
* Transparency type (opaque since there is no transparency in the image).
*/
final int transparency = Transparency.OPAQUE;
/**
* Color space, a standard default color space for the Internet - sRGB.
*/
final ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
/**
* Image sample model.
*/
final ComponentSampleModel sampleModel;
/**
* Image color model.
*/
final ComponentColorModel colorModel = new ComponentColorModel(colorSpace, bits, false, false, transparency, dataType);
private Converter(int width, int height) {
this.scanlineStride = width * pixelStride;
this.sampleModel = new ComponentSampleModel(dataType, width, height, pixelStride, scanlineStride, bgrBandOffsets);
}
/**
* Get memory converter for given width-height tuple.
*
* @param width the image width
* @param height the image height
* @return Converter
*/
public static Converter getConverter(int width, int height) {
String key = key(width, height);
Converter converter = CONVERTERS.get(key);
if (converter == null) {
converter = new Converter(width, height);
CONVERTERS.put(key, converter);
}
return converter;
}
/**
* Use width and height to create map key.
*
* @param width the image width
* @param height the image height
* @return Map key
*/
private static String key(int width, int height) {
return width + "x" + height;
}
/**
* Converts {@link Memory} into {@link BufferedImage}.
*
* @param buffers the {@link Memory} buffers
* @param format the image format
* @return {@link BufferedImage} created from {@link Memory}
*/
public BufferedImage convert(Memory[] buffers, BufferFormat format) {
// sanity, check if buffers is not empty
if (buffers.length == 0) {
throw new RuntimeException("No memory elements found!");
}
// sanity check if buffer is not null
final Memory memory = buffers[0];
if (memory == null) {
throw new RuntimeException("Null memory!");
}
// transfer bytes into array
final byte[] bytes = new byte[scanlineStride * format.getHeight()];
final byte[][] data = new byte[][] { bytes };
memory
.getByteBuffer(0, memory.size())
.get(bytes);
// create image
DataBufferByte dataBuffer = new DataBufferByte(data, bytes.length, offsets);
WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, null);
BufferedImage image = new BufferedImage(colorModel, raster, false, null);
// flush reconstructable resources to free memory
image.flush();
// return image
return image;
}
}
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(VlcjDevice.class);
/**
* Artificial view sizes. The vlcj is not able to detect resolutions supported by the webcam. If
* you would like to detect resolutions and have high-quality with good performance images
* streaming, you should rather use gstreamer or v4lvj capture drivers.
*/
private final static Dimension[] RESOLUTIONS = new Dimension[] {
WebcamResolution.QQVGA.getSize(),
WebcamResolution.QVGA.getSize(),
WebcamResolution.VGA.getSize(),
};
/**
* VLC args by Andrew Davison:
* http://fivedots.coe.psu.ac.th/~ad/jg/nui025/snapsWithoutJMF.pdf
*/
private final static String[] VLC_ARGS = {
"--no-video-title-show", // do not display title
"--no-stats", // no stats
"--no-sub-autodetect-file", // no subtitles
"--no-snapshot-preview", // no snapshot previews
"--live-caching=50", // reduce capture lag/latency
"--quiet", // turn off warnings
};
/**
* Used to calculate FPS.
*/
private long t1 = -1;
/**
* Used to calculate FPS.
*/
private long t2 = -1;
/**
* Image exchange reference.
*/
private final AtomicReference imageRef = new AtomicReference();
/**
* Current FPS.
*/
private final AtomicReference fps = new AtomicReference((double) 0);
private final MediaListItem item;
private final MediaListItem sub;
/**
* Factory for media player instances.
*/
private MediaPlayerFactory factory;
/**
* Specification for a media player that provides direct access to the video frame data.
*/
private DirectMediaPlayer player;
/**
* Is in opening phase?
*/
private final AtomicBoolean opening = new AtomicBoolean();
/**
* Is open?
*/
private final AtomicBoolean open = new AtomicBoolean();
/**
* Is disposed?
*/
private final AtomicBoolean disposed = new AtomicBoolean();
/**
* Current resolution.
*/
private Dimension resolution = null;
protected VlcjDevice(MediaListItem item) {
if (item == null) {
throw new IllegalArgumentException("Media list item cannot be null!");
}
this.item = item;
this.sub = item.subItems().isEmpty() ? item : item.subItems().get(0);
LOG.trace("New device created {}", this);
}
/**
* Get capture device protocol. This will be:
*
* dshow://
for Windows
* qtcapture://
for Mac
* v4l2://
for linux
*
*
* @return Capture device protocol
* @throws WebcamException in case when there is no support for given operating system
*/
public String getCaptureDevice() {
switch (OsUtils.getOS()) {
case WIN:
return "dshow://";
case OSX:
return "qtcapture://";
case NIX:
return "v4l2://";
default:
throw new WebcamException("Capture device not supported on " + OsUtils.getOS());
}
}
public MediaListItem getMediaListItem() {
return item;
}
public MediaListItem getMediaListItemSub() {
return sub;
}
@Override
public String getName() {
return sub.name();
}
public String getMRL() {
return sub.mrl();
}
public String getVDevice() {
return getMRL().replace(getCaptureDevice(), "");
}
@Override
public String toString() {
return String.format("%s[%s (%s)]", getClass().getSimpleName(), getName(), getMRL());
}
@Override
public Dimension[] getResolutions() {
return RESOLUTIONS;
}
@Override
public Dimension getResolution() {
return resolution;
}
@Override
public void setResolution(Dimension resolution) {
this.resolution = resolution;
}
@Override
public BufferedImage getImage() {
if (!open.get()) {
throw new WebcamException("Cannot get image, webcam device is not open");
}
BufferedImage image = null;
// wait for image
while ((image = imageRef.getAndSet(null)) == null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return null;
}
}
return image;
}
@Override
public synchronized void open() {
if (disposed.get()) {
LOG.warn("Cannot open device because it has been already disposed");
return;
}
if (open.get()) {
return;
}
if (!opening.compareAndSet(false, true)) {
return;
}
factory = new MediaPlayerFactory(VLC_ARGS);
player = factory.newDirectMediaPlayer(this, this);
LOG.info("Opening webcam device");
String[] options = null;
switch (OsUtils.getOS()) {
case WIN:
LOG.debug("Open VLC device {}", getName());
options = new String[] {
":dshow-vdev=" + getName(),
":dshow-size=" + resolution.width + "x" + resolution.height,
":dshow-adev=none", // no audio device
};
break;
case NIX:
LOG.debug("Open VLC device {}", getVDevice());
options = new String[] {
":v4l2-vdev=" + getVDevice(),
":v4l2-width=" + resolution.width,
":v4l2-height=" + resolution.height,
":v4l2-fps=30",
":v4l2-adev=none", // no audio device
};
break;
case OSX:
LOG.debug("Open VLC device {}", getVDevice());
options = new String[] {
":qtcapture-vdev=" + getVDevice(),
":qtcapture-width=" + resolution.width,
":qtcapture-height=" + resolution.height,
":qtcapture-adev=none", // no audio device
};
break;
}
player.startMedia(getMRL(), options);
// wait for the first image
long wait = 100; // ms
int max = 100;
int count = 0;
while (imageRef.get() == null) {
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
return;
}
if (count++ > max) {
LOG.error("Unable to open in {} ms", wait * max);
opening.set(false);
return;
}
}
open.set(true);
opening.set(false);
}
@Override
public synchronized void close() {
LOG.info("Closing device {}", this);
if (open.compareAndSet(true, false)) {
player.stop();
}
}
@Override
public synchronized void dispose() {
if (!disposed.compareAndSet(false, true)) {
return;
}
LOG.debug("Release resources (player={}, factory={})", player, factory);
player.release();
factory.release();
}
@Override
public boolean isOpen() {
return open.get();
}
public MediaPlayer getPlayer() {
return player;
}
@Override
public BufferFormat getBufferFormat(int width, int height) {
return new RV32BufferFormat(width, height);
}
@Override
public void display(DirectMediaPlayer player, Memory[] buffers, BufferFormat format) {
LOG.trace("Direct media player display invoked with format {}", format);
// convert memory to image
Converter converter = Converter.getConverter(format.getWidth(), format.getHeight());
BufferedImage image = converter.convert(buffers, format);
imageRef.set(image);
// calculate fps
if (t1 == -1 || t2 == -1) {
t1 = System.currentTimeMillis();
t2 = System.currentTimeMillis();
}
t1 = t2;
t2 = System.currentTimeMillis();
fps.set((4 * fps.get() + 1000 / (t2 - t1 + 1)) / 5);
}
@Override
public double getFPS() {
return fps.get();
}
}
package cn.xsw.rtspview;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.sarxos.webcam.WebcamDevice;
import com.github.sarxos.webcam.WebcamDiscoverySupport;
import com.github.sarxos.webcam.WebcamDriver;
import com.github.sarxos.webcam.util.OsUtils;
import uk.co.caprica.vlcj.discovery.NativeDiscovery;
import uk.co.caprica.vlcj.medialist.MediaList;
import uk.co.caprica.vlcj.medialist.MediaListItem;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.discoverer.MediaDiscoverer;
/**
* This is capture driver which uses vlcj
library to gain access to the camera device.
* The library can be found at:
*
* http://www.capricasoftware.co.uk/projects/vlcj/index.html
*
* @author Bartosz Firyn (SarXos)
*/
public class VlcjDriver implements WebcamDriver, WebcamDiscoverySupport {
/**
* I'm the logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(VlcjDriver.class);
static {
if ("true".equals(System.getProperty("webcam.debug"))) {
System.setProperty("vlcj.log", "DEBUG");
}
}
/**
* Are natives initialized.
*/
private static final AtomicBoolean initialized = new AtomicBoolean();
/**
* Native library discoverer.
*/
private static NativeDiscovery nativeDiscovery;
/**
* The scan interval.
*/
private long scanInterval = -1;
/**
* Preconfigured media list items.
*/
private final List mediaListItems;
public VlcjDriver() {
this(null);
}
public VlcjDriver(List mediaListItems) {
this.mediaListItems = mediaListItems;
initialize();
}
/**
* Initialize natives.
*/
protected static void initialize() {
initialize(true);
}
/**
* Initialize natives. If argument is true the natives are being loaded. In case of false this
* method do nothing. It's used mostly in unit tests.
*
* @param load the control to decide whether to load natives or ignore them
*/
protected static void initialize(boolean load) {
if (load && initialized.compareAndSet(false, true)) {
boolean nativeFound = getNativeDiscovery().discover();
if (!nativeFound) {
throw new IllegalStateException("The libvlc native library has not been found");
}
// Native.loadLibrary(RuntimeUtil.getLibVlcLibraryName(), LibVlc.class);
}
}
@Override
public List getDevices() {
LOG.debug("Searching devices");
if (OsUtils.getOS() == OsUtils.WIN) {
System.err.println("WARNING: VLCj does not support webcam devices discovery on Windows platform");
}
List devices = new ArrayList();
if (mediaListItems != null) {
for (MediaListItem item : mediaListItems) {
devices.add(mediaListItemToDevice(item));
}
} else {
MediaPlayerFactory mediaPlayerFactory = createMediaPlayerFactory();
MediaDiscoverer videoMediaDiscoverer = mediaPlayerFactory.newVideoMediaDiscoverer();
MediaList videoDeviceList = videoMediaDiscoverer.getMediaList();
List videoDevices = videoDeviceList.items();
for (MediaListItem item : videoDevices) {
LOG.debug("Found item {}", item);
devices.add(mediaListItemToDevice(item));
}
videoDeviceList.release();
videoMediaDiscoverer.release();
mediaPlayerFactory.release();
}
return devices;
}
/**
* Converts media list itemn into webcam device.
*
* @param item the item to be converted to webcam device instance
* @return Webcam device created from media list item
*/
protected WebcamDevice mediaListItemToDevice(MediaListItem item) {
return new VlcjDevice(item);
}
/**
* Creates media player factory.
*
* @return New media player factory
*/
protected MediaPlayerFactory createMediaPlayerFactory() {
return new MediaPlayerFactory();
}
@Override
public boolean isThreadSafe() {
return false;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
@Override
public long getScanInterval() {
if (scanInterval <= 0) {
return DEFAULT_SCAN_INTERVAL;
}
return scanInterval;
}
/**
* Set new scan interval. Value must be positive number. If negative or zero is used, then the
* corresponding getter will return default scan interval value.
*
* @param scanInterval the new scan interval in milliseconds
* @see VlcjDriver#DEFAULT_SCAN_INTERVAL
*/
public void setScanInterval(long scanInterval) {
this.scanInterval = scanInterval;
}
@Override
public boolean isScanPossible() {
return OsUtils.getOS() != OsUtils.WIN;
}
protected static NativeDiscovery getNativeDiscovery() {
if (nativeDiscovery == null) {
nativeDiscovery = new NativeDiscovery();
}
return nativeDiscovery;
}
}
jar包:
优点:简洁、跨平台好。 但延迟大3s左右
3、流媒体服务器接流转退RTMP、HLS等方式 适合BS 收费,便捷
https://www.linkingvision.com/
这个就看自己如何使用了,可以使用iframe嵌软件自带的页面,也可以自己写页面调用。