javacv对rtsp轮询抓图实现与踩坑

最近搞了个项目,各种接视频,踩了各种坑。今天记一个搞RTSP的思路,过几天有时间再记一下直接搞大华和海康SDK的思路。

需求

简单地说,用户在系统里接几路RTSP视频,需要定期(10秒左右)从RTSP抓图回来保存到本地。

技术方案

其实前期没论证其他的技术方案,上来直接磕javacv了。但是中间遇到好多问题,每次感觉搞不定了都去搜其他的解决方案,常见的方案大概有:
1、进程调用ffmpeg
这思路太虎了,传参太费劲,控制进程也太费劲,直接pass。
2、jcodec
搞了一晚上,没搞通它的接口。也是时间紧张没来得及细读源码,单从接口参数上看只能给本地文件路径,给rtsp地址直接报错了。如果有搞过的小伙伴可以留言交流一下,搞个示例代码给我学习学习。总之查了很多jcodec的实例,没一个好使的。
3、第三个叫啥玩意儿忘了,过去时间太久。在mvnrepository上看它的最后更新好像2013年,maven引过来发现和网上示例代码完全对不上,自己尝试调用接口咣咣报错,一脑门汗。

所以最后还是接着磕javacv吧…… 抓图源码直接放上来:

首先是javacv的引用,整个javacv全都引用过来太过巨大,打完包900多M,直接给我干蒙了。根据实际情况我们引用精简版(注意,因为在windows开发,在linux部署,所以引用两个版本,如果只引用windows的就更精简了,自己根据需要去引用)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <properties>
        <javacv.version>1.5.8</javacv.version>
        <system.windows64>windows-x86_64</system.windows64>
        <system.linux64>linux-x86_64</system.linux64>
    </properties>
	
	<dependencies>
		<dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>${javacv.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp-platform</artifactId>
            <version>${javacv.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>5.1.2-${javacv.version}</version>
            <classifier>${system.windows64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>5.1.2-${javacv.version}</version>
            <classifier>${system.linux64}</classifier>
        </dependency>

        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>opencv</artifactId>
            <version>4.6.0-${javacv.version}</version>
            <classifier>${system.windows64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>openblas</artifactId>
            <version>0.3.21-${javacv.version}</version>
            <classifier>${system.windows64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>flycapture</artifactId>
            <version>2.13.3.31-${javacv.version}</version>
            <classifier>${system.windows64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>opencv</artifactId>
            <version>4.6.0-${javacv.version}</version>
            <classifier>${system.linux64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>openblas</artifactId>
            <version>0.3.21-${javacv.version}</version>
            <classifier>${system.linux64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>flycapture</artifactId>
            <version>2.13.3.31-${javacv.version}</version>
            <classifier>${system.linux64}</classifier>
        </dependency>
	</dependencies>
</project>

抓图代码:

public class CameraUtil {
	private static Java2DFrameConverter java2DFrameConverter;

    static {
        java2DFrameConverter = new Java2DFrameConverter();
        avutil.av_log_set_level(avutil.AV_LOG_QUIET);
    }
    
	public static void screenShot(String rtspUrl) {
        FFmpegFrameGrabber grabber = null;
        try {
            grabber = FFmpegFrameGrabber.createDefault(rtspUrl);
            grabber.setOption("rtsp_transport", "tcp");
            grabber.setOption("stimeout", "3000000");
            grabber.setOption("timeout", "3000000");
            grabber.setImageWidth(1280);
            grabber.setImageHeight(720);
            grabber.startUnsafe();

            String imgBase64 = null;
            while (true) {
                Frame frame = grabber.grabImage();
                if (frame != null) {
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    synchronized (CameraUtil.class) {
                        ImageIO.write(java2DFrameConverter.getBufferedImage(frame), "jpg", outputStream);
                    }
                    imgBase64 = Base64Utils.imageToBase64ByLocalByte(outputStream.toByteArray());
                    break;
                }
            }

        } catch (Exception e){
            log.error(rtspUrl);
            log.error(e.getMessage(), e);
        } finally {
            try {
                grabber.stop();
            } catch (FrameGrabber.Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                try {
                    grabber.close();
                } catch (FrameGrabber.Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return captureResult;
    }
}

  1. 调用程序期间,出现大量javacv日志,影响日志的正常分析了。解决办法在这一行:
avutil.av_log_set_level(avutil.AV_LOG_QUIET);
  1. 多线程调用screenShot接口,其中一个线程因为某种原因卡住无法及时返回的时候,其他线程全都卡住了。解决办法是:
grabber.startUnsafe();

这里有些示例代码用的是grabber.start()方法,start方法是一个同步方法,里面synchronized加锁调用startUnsafe()方法。所以如果你直接调用startUnsafe()就可以避免其他线程等待同步方法结束。

  1. startUnsafe() 方法卡住不动,一直不返回,也不响应,也不报错,解决办法在:
grabber.setOption("timeout", "3000000");

设置timeout参数,可以使grabber在连接超时的时候返回。
这个timeout参数有点神奇,首先他是个微秒单位,不是毫秒,所以看起来很大(1秒=110001000微秒)。
其次实际测试的时候,发现设置了3秒超时,但它实际9秒左右抛异常返回;如果设置2秒超时,实际6秒左右抛异常返回;设置4秒的时候实际12秒左右抛异常返回,总会有个3倍左右的差值不知道是因为什么。

  1. startUnsafe() 方法卡住后抛出异常,异常信息为avformat_open_input() error xxx : Could not open input xxx . (Has setFormat() been called?)

实际上,上面连接超时的时候就会抛出这个异常。程序运行过程中发现有很多种情况都会抛这个异常,当时没有一一去记录,但每次都尝试从异常中发现问题和解决问题。总结下来大概有这几个方向来解决这个问题:
第一是超时问题,rtsp本身或者网络原因造成的超时;
第二是一个狗屎原因,用户帮我们发现的——rtsp地址带密码,他们在密码中设置了#符号,这种的需要urlEncode转义,要不然拿过来进行连接又连不上;
第三个可能的原因,你的摄像头没有采集声音,但是在grabber这里疯狂要音轨——其实这是播放的时候可能发生的问题,而非截图的时候;
第四个可能的原因,我没遇到过,之前看有其他人说的,采样率设置得不对也会报这个问题。

时间紧张也顾不上好好写,也顾不上好好排版了。有不对的或者有问题的欢迎指正留言,共同探讨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值