27

树莓派视频监控平台实现录制归档

 3 years ago
source link: http://www.cnblogs.com/itqn/p/13281792.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

上一次用 树莓派搭建了视频监控平台 ,成功实现了利用树莓派当监控摄像头,但是只能在线监控没有存档功能,这次针对上次的监控平台进行了改造,实现了录制归档功能。

这次主要针对上次的平台做以下几点改造:

  1. 新增视频流录制模块
  2. 调整监控管理页面
  3. 新增录制归档列表页面

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. 改造监控管理页面

这里直接改造上次的监控管理页面,将布局调整为左右模式,并新增了“开启录制”和“停止录制”按钮、以及“录制归档列表”的入口跳转,整体页面效果如下:

ym67J3Z.png!web

需要注意的是:要实现录制,必须开启监控,只有开启了监控才可以录制。

3. 开发录制控制接口

跟上次开发监控控制接口一样,在IndexController中新增两个接口用于控制“开启录制”和“停止录制”。

public void startRecord() {
  StreamManager.INSTANCE.startRecord();
  redirect("/");
}
public void stopRecord() {
  StreamManager.INSTANCE.stopRecord();
  redirect("/");
}

上面的 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播放已录制的视频画面:

AjYvemu.png!web

至此,视频监控平台就实现了录制归档功能。

5. 开发视频归档列表页面

为了方便查看树莓派中录制的视频列表,可以开发一个简单的页面用于显示已经录制的视频,实现这个功能只需要简单的两步即可。

  1. 视频列表显示页面开发
<body>
  <a href="/"> 查看视频监控 >>> </a> 
  <br>
  <br>
  <div>
    <table border="1">
      <tr>
        <td>视频名称</td>
        <td>视频大小</td>
        <td>录制时间</td>
      </tr>
      #for(v : fList)
      <tr>
        <td>#(v.name)</td>
        <td>#(v.size)</td>
        <td>#(v.time)</td>
      </tr>
      #end
    </table>
  </div>
</body>
  1. 归档视频列表接口开发
public void index() {
  List<VideoVO> fList = new ArrayList<>(20);
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
  File dir = new File(Const.RECORD_DIR);
  File[] fArray = dir.listFiles(f -> f.getName().endsWith(".flv"));
  if (fArray != null) {
    for (File f : fArray) {
      VideoVO vo = new VideoVO();
      vo.setName(f.getName());
      vo.setSize(f.length());
      vo.setTime(sdf.format(new Date(Long.parseLong(f.getName().replace(".flv", "")))));
      fList.add(vo);
    }
  }
  setAttr("fList", fList);
  render("index.html");
}

最终效果如下:

qiYfium.png!web

6. 拓展玩法

虽然这个视频监控平台已经实现了监控和录制功能,但仍有部分缺陷,如果有兴趣可以进行拓展。

比如:

  1. 录制视频使用ffmpeg进行转码,然后使用HTML5-video标签进行播放回看。
  2. 录制视频提供删除和定期清理功能。
  3. 录制视频提供下载功能。

=========================================================

项目源码可 关注 公众号 “HiIT青年” 发送 “raspi-record” 获取。

BzIjYnv.jpg!web关注公众号 ,阅读更多文章。

由于上次发表的文章,被人盗用发布在头条上,这里我在文章的图片加了LOGO水印,不便之处请多包涵。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK