【JAVA】使用vlcj获取网络摄像头(rtsp)图像,支持H.265和H.264

近期项目中需要抽取rtsp流图像进行图像处理,目前网上可以找到的教程中绝大部分使用的是opencv或者javacv,我一开始是采用的javacv方案,但是javacv不支持h265格式,而且对于长时间抽流的逻辑来说,监控抽流状态也比较麻烦,因为网络波动等一些问题导致的抽流中断,也不是很好监控,于是改用了vlcj库重构了抽帧逻辑。

VLCJ简介

官网:http://capricasoftware.co.uk/
vlcj是libvlc的java实现,著名的开源播放器VLC就是基于libvlc实现的。VLC是个特别强悍的播放器,尤其是播放各种协议的流媒体,几乎没有vlc不支持的格式,而且稳定性也特别好。

优点

相对于传统的opencv和javacv方案,vlcj主要有如下几个优点:

  • 易于集成
    vlcj内置了多种播放器组件, 核心代码只需一行: player.playMedia(rtsp, PLAY_OPTIONS);
  • 支持H.265在内的多种编码方式
    对于rtsp协议,vlcj原生支持H264和H265编码。除此之外还可以播放rtmp等其他协议的视频流。
  • 稳定
    使用javacv播放rtsp视频流的时候,如果长时间链接不上,会自动中断,需要额外的附加一些逻辑用来判断是否链接正常,我当时的做法记录下最后一次抽帧的时间戳,用一个定时器循环检测这个时间戳,如果长时间未变动则说明其链接失败,虽然说着挺简单,但是具体的细节部分还是很难处理。使用vlcj的话,播放器在连接不上视频流的时候会自动重连,遇到断网等情况,处理起来也非常方便。
  • 原生支持GPU解码

使用VLCJ

先说一下我的业务需求:同时抽取多个海康摄像头的rtsp流,每隔一段时间提取其中一帧,然后进行图像处理。最主要的一点是,仅获取图像数据用来计算,不做前端展示。 下边的示例代码是以这个业务需求仅为基础编写的。

环境准备(windows)

需要先安装libvlc环境, 具体方法可以百度。还有个比较省事的做法, 下载VLC播放器, 安装的时候勾选全部组件即可(安装位置不限,无需配置环境变量)。

快速开始

添加maven依赖,vlcj有3.x和4.x两个版本,两个版本的api相差很大,这里使用的是vlcj3

   <dependency>
        <groupId>uk.co.caprica</groupId>
        <artifactId>vlcj</artifactId>
        <version>3.12.1</version>
    </dependency>

示例代码

public class VLCJTest {
    private BufferedImage image;
   	private  MediaPlayerFactory mediaPlayerFactory
   	private  HeadlessMediaPlayer mediaPlayer
    //--live-caching 0设置播放器缓存为0,保证获取到的都是实时画面,第二个参数可以不加,暂时没看出啥效果
    static String options[] = new String[]{"--live-caching 0", "--avcodec-hr=vaapi_drm"};	
    //这两个参数可加可不加,如果想要通过窗口展示视频画面,就不加, 如果不想显示视频画面,就加上
    static String[] VLC_ARGS = {  "--vout", "dummy" };        
            
    static String videoSources = "rtsp://admin:[email protected]:554/h265/ch0/main/av_stream";
    
    public static void main(String[] args) {
    	new NativeDiscovery().discover();	//自动搜索libvlc路径并初始化,这行代码一定要加,且libvlc要已经安装,否则会报错
		// 创建播放器工厂
		mediaPlayerFactory = new MediaPlayerFactory(VLC_ARGS);	//这样写的话则不展示视频图像, 要想展示图像的话则直接new MediaPlayerFactory();
		// 创建一个HeadlessMediaPlayer ,在不需要展示视频画面的情况下,使用HeadlessMediaPlayer 是最合适的(尤其是在服务器环境下)
		mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer();
		String url = videoSources;
		mediaPlayer.playMedia(url, options);	//开始播放视频,这里传入的是rtsp连接, 传入其他格式的链接也是可以的,网络链接、本地路径都行
		
		//开始播放之后,可以另起一个线程来获取视频帧 (这里使用的hutool框架来开启线程)
		 ThreadUtil.execAsync(()->{
	         while (true){
		         if (mediaPlayer.isPlaying()){
		             image = mediaPlayer.getSnapshot();
		             // 具体计算逻辑省略
		         }
	         }
         });

   }

其中有几点需要注意的地方(敲黑板):

  • 关于视频输出窗口
    代码中使用mediaPlayerFactory = new MediaPlayerFactory(VLC_ARGS ); 来创建player工厂,VLC_ARGS = { “–vout”, “dummy” }; 定义了两个参数, 这两个参数是用来隐藏视频输出窗口的,如果直接使用 new MediaPlayerFactory()无参构造函数,则可以生成带有视频输出框的player。 这个只是为了测试用的窗口,实际项目中如果要专门显示图像的话,可以使用 mediaPlayerFactory.newEmbeddedMediaPlayer()获取EmbeddedMediaPlayer的实例。
  • 小心GC
    官方文档中有这么一段话
    Your application must keep hard references to the media player instances that you create. If you do not do this, your media player will become unexpectedly garbage collected at some future indterminate time and you will see a fatal crash eventually or immediately depending on how lucky you are.
    大体意思是说在程序中我们必须注意player实例的引用必须是强引用(按照我的理解就是要在对象的字段中声明player,在方法中进行初始化),否则player很容易被GC掉。我就碰到过这个问题,开始播放之后,刚开始正常,一会就自动退出,也不会抛出异常,后来才发现是player被gc了。
  • 释放资源
    player必须被手动释放,上述代码并没有做处理,这样其实是很危险的。要在合适的时候调用player.release()来释放资源。对于factory也一样
  • MediaPlayerFactory的复用问题
    按照常理思考,MediaPlayerFactory只需实例化一个即可。 但是我在使用的过程中,实例化一个MediaPlayerFactory后,通过这个MediaPlayerFactory获取多个player实例, 每个player实例抽取一个rtsp流,然后每隔五秒钟保存一帧图片。不知道为什么五个player保存的是同一个摄像头的图片,而且还不一定是哪个摄像头,修改代码,一个MediaPlayerFactory只生成一个player后,这个问题就解决了。 暂时不清楚是什么原因。
  • RuntimeException
    mediaPlayer.getSnapshot(); 获取图像帧的时候,偶尔会抛出RuntimeException异常,不影响播放,但是不容易发现,所以在代码中调用这个方法的时候最好是try catch一下。

你可能感兴趣的:(程序员,java,服务器)