樹莓派視訊監控平臺實現錄製歸檔
阿新 • • 發佈:2020-07-11
上一次用[樹莓派搭建了視訊監控平臺](https://www.cnblogs.com/itqn/p/13268916.html),成功實現了利用樹莓派當監控攝像頭,但是隻能線上監控沒有存檔功能,這次針對上次的監控平臺進行了改造,實現了錄製歸檔功能。
這次主要針對上次的平臺做以下幾點改造:
1. 新增視訊流錄製模組
2. 調整監控管理頁面
3. 新增錄製歸檔列表頁面
### 1. 開發視訊流錄製模組
視訊錄製模組不像視訊推流模組那樣,可以一直不停止的工作(推流),因為錄製模組需要考慮錄製視訊的大小和斷流等因素,所以在必要的時候需要錄製流程進行處理。
針對斷流的情況,視訊錄製模組使用一個監控執行緒,當超過兩分鐘未錄製視訊幀時,停止當前錄製,錄製器通過呼叫ping方法來實現心跳:
```java
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();
}
```
當視訊持續錄製是,需要限制視訊的大小,這裡視訊最長只錄制一小時,當錄製時長超過一小時後,歸檔重新錄製。
```java
if (System.currentTimeMillis() - startTime > MAX_RECORD_TIME) {
destroy();
}
if (recorder == null) {
init(frame.imageWidth, frame.imageHeight);
}
```
這裡的錄製模組是單例,所以當物件建立的時候,就建立監聽執行緒並啟動它,以下是完成的實現:
```java
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. 改造監控管理頁面
這裡直接改造上次的監控管理頁面,將佈局調整為左右模式,並新增了“開啟錄製”和“停止錄製”按鈕、以及“錄製歸檔列表”的入口跳轉,整體頁面效果如下:
![stream-record.png](https://img2020.cnblogs.com/blog/2083963/202007/2083963-20200710220424274-552463272.png)
需要注意的是:要實現錄製,必須開啟監控,只有開啟了監控才可以錄製。
### 3. 開發錄製控制介面
跟上次開發監控控制介面一樣,在IndexController中新增兩個介面用於控制“開啟錄製”和“停止錄製”。
```java
public void startRecord() {
StreamManager.INSTANCE.startRecord();
redirect("/");
}
public void stopRecord() {
StreamManager.INSTANCE.stopRecord();
redirect("/");
}
```
上面的`StreamManager`是視訊流管控中心,在這裡往推流器註冊一個視訊幀消費者,然後將視訊幀塞給錄製器實現錄製。
```java
private void registerFrameConsumer() {
if (sender == null) {
return;
}
sender.registerFrameConsumer(f -> {
if (record) {
StreamRecorder.INSTANCE.record(f);
}
});
}
```
所以當開啟錄製時,只需要將`record`置為true即可。
```java
public void startRecord() {
record = true;
}
```
而停止錄製時則將`record`置為false,同時關閉錄製。
```java
public void stopRecord() {
record = false;
StreamRecorder.INSTANCE.destroy();
}
```
### 4. 播放錄製視訊
視訊錄製後會以開始錄製時間戳為名稱存放在錄製目錄中(程式設定的是:/home/pi/RevVideo),錄製的視訊格式是FLV,採用JavaCV錄製FLV無法直接使用HTML5的video播放,要播放錄製的視訊,可以用樹莓派自帶的媒體播放工具VLC, 下面視訊VLC播放已錄製的視訊畫面:
![stream-record-play](https://img2020.cnblogs.com/blog/2083963/202007/2083963-20200710222133377-1837093013.png)
至此,視訊監控平臺就實現了錄製歸檔功能。
### 5. 開發視訊歸檔列表頁面
為了方便檢視樹莓派中錄製的視訊列表,可以開發一個簡單的頁面用於顯示已經錄製的視訊,實現這個功能只需要簡單的兩步即可。
1. 視訊列表顯示頁面開發
```html
檢視視訊監控 >>>
#for(v : fList) #end
```
2. 歸檔視訊列表介面開發
```java
public void index()
#for(v : fList) #end
視訊名稱 | 視訊大小 | 錄製時間 |
#(v.name) | #(v.size) | #(v.time) |