上一次用树莓派搭建了视频监控平台,成功实现了利用树莓派当监控摄像头,但是只能在线监控没有存档功能,这次针对上次的监控平台进行了改造,实现了录制归档功能。
这次主要针对上次的平台做以下几点改造:
新增视频流录制模块
调整监控管理页面
新增录制归档列表页面
1. 开发视频流录制模块视频录制模块不像视频推流模块那样,可以一直不停止的工作(推流),因为录制模块需要考虑录制视频的大小和断流等因素,所以在必要的时候需要录制流程进行处理。
针对断流的情况,视频录制模块使用一个监控线程,当超过两分钟未录制视频帧时,停止当前录制,录制器通过调用ping方法来实现心跳:
public void run() { while (true) { try { TimeUnit.MINUTES.sleep(2); } catch (InterruptedException ignore) { } if (System.currentTimeMillis() - timestamp > 2 * 60 * 1000) { destroy(); } } } public void ping() { timestamp = System.currentTimeMillis(); }当视频持续录制是,需要限制视频的大小,这里视频最长只录制一小时,当录制时长超过一小时后,归档重新录制。
if (System.currentTimeMillis() - startTime > MAX_RECORD_TIME) { destroy(); } if (recorder == null) { init(frame.imageWidth, frame.imageHeight); }这里的录制模块是单例,所以当对象创建的时候,就创建监听线程并启动它,以下是完成的实现:
public class StreamRecorder { public static final StreamRecorder INSTANCE = new StreamRecorder(); private static final int FPS = 25; private static final int MAX_RECORD_TIME = 60 * 60 * 1000; private long startTime; private FFmpegFrameRecorder recorder; private AtomicBoolean wait = new AtomicBoolean(false); private StreamRecorder() { new Thread(this.new ALiveWatcher()).start(); } public void record(Frame frame) { if (wait.get() || frame == null) { return; } if (System.currentTimeMillis() - startTime > MAX_RECORD_TIME) { destroy(); } if (recorder == null) { init(frame.imageWidth, frame.imageHeight); } long timestamp = 1000 * (System.currentTimeMillis() - startTime); if (timestamp > recorder.getTimestamp()) { recorder.setTimestamp(timestamp); } try { recorder.record(frame); } catch (Exception e) { destroy(); } } private void init(int width, int height) { try { startTime = System.currentTimeMillis(); String f = Const.RECORD_DIR + File.separator + startTime + ".flv"; recorder = new FFmpegFrameRecorder(f, width, height); recorder.setFormat("flv"); recorder.setFrameRate(FPS); recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); recorder.start(); } catch (Throwable e) { throw new RuntimeException(e); } } public void destroy() { if (recorder == null) { return; } try { wait.set(true); TimeUnit.SECONDS.sleep(1); recorder.close(); recorder = null; } catch (Throwable ignore) { } finally { wait.set(false); } } class ALiveWatcher implements Runnable { private long timestamp; @Override public void run() { while (true) { try { TimeUnit.MINUTES.sleep(2); } catch (InterruptedException ignore) { } if (System.currentTimeMillis() - timestamp > 2 * 60 * 1000) { destroy(); } } } public void ping() { timestamp = System.currentTimeMillis(); } } } 2. 改造监控管理页面这里直接改造上次的监控管理页面,将布局调整为左右模式,并新增了“开启录制”和“停止录制”按钮、以及“录制归档列表”的入口跳转,整体页面效果如下:
需要注意的是:要实现录制,必须开启监控,只有开启了监控才可以录制。
3. 开发录制控制接口跟上次开发监控控制接口一样,在IndexController中新增两个接口用于控制“开启录制”和“停止录制”。
public void startRecord() { StreamManager.INSTANCE.startRecord(); redirect("http://www.likecs.com/"); } public void stopRecord() { StreamManager.INSTANCE.stopRecord(); redirect("http://www.likecs.com/"); }上面的StreamManager是视频流管控中心,在这里往推流器注册一个视频帧消费者,然后将视频帧塞给录制器实现录制。
private void registerFrameConsumer() { if (sender == null) { return; } sender.registerFrameConsumer(f -> { if (record) { StreamRecorder.INSTANCE.record(f); } }); }所以当开启录制时,只需要将record置为true即可。
public void startRecord() { record = true; }而停止录制时则将record置为false,同时关闭录制。
public void stopRecord() { record = false; StreamRecorder.INSTANCE.destroy(); } 4. 播放录制视频视频录制后会以开始录制时间戳为名称存放在录制目录中(程序设置的是:/home/pi/RevVideo),录制的视频格式是FLV,采用JavaCV录制FLV无法直接使用HTML5的video播放,要播放录制的视频,可以用树莓派自带的媒体播放工具VLC, 下面视频VLC播放已录制的视频画面: