送个小纪念品吧(2)

结构介绍

续接上集,本期主要给咱的相机进行了一些升级,除了增加2k视频录制功能之外,还要有配套的APP,所以本期博客就是在完成升级任务:

  • 视频录制
  • ffmpeg解码
  • Android13软件开发

视频录制

这里选择了树莓派zero 2W进行视频与图像处理,至于opencv可有可无,opencv录制直接输出mp4文件,而用picamera录制会输出.h264文件,可以通过ffmpeg解码编码为mp4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import serial
from picamera import PiCamera
import os
import datetime

ser = serial.Serial("/dev/ttyAMA0", 115200)
camera = PiCamera()

path = "/var/www/html/"
filename_gb = ""

def savePhoto(filename):
camera.resolution = (3280, 2460)
print("Photo OK")
camera.capture(f'{filename}.jpg')
return 3

def startVideo(filename):
print("Video Start")
camera.resolution = (1920, 1080)
camera.framerate = 30
camera.video_stabilization = True
global filename_gb
filename_gb = filename
camera.start_recording(f'{filename}.h264')
return 8

def stopVideo():
print("Video Stop")
global filename_gb
camera.stop_recording()
cmd = f'ffmpeg -i {filename_gb}.h264 -vcodec copy -acodec copy {filename_gb}.mp4'
os.system(cmd)
cmd = f'rm {filename_gb}.h264'
os.system(cmd)
return 4

def readData():
# ??????
recv_buffer = ser.read(1)
# ??????????
return recv_buffer

def main():
data = readData()
filename = path + datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
if data == b'1':
savePhoto(filename)
elif data == b'2':
startVideo(filename)
elif data == b'0':
stopVideo()
else:
pass
ser.flushInput()
ser.flushOutput()




if __name__ == "__main__":
if os.path.isdir(path):
while True:
main()
pass
else:
print("Disk Error")

UI布局

本来打算在Android内完成的,但是想了一下发现自己对Android的掌握度不高,尤其是kotlin,只能用java写,所以倒不如把相册保存到apache2网站目录后,用php显示,所以下面是显示的php代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<?php
$path = "./albums";
$imgType = array('png','jpg','mp4');
$show_begin = 0;
$step = 20;

$handle = opendir($path);
while(($file = readdir($handle)) != false){
list($filename , $kzm) = explode('.', $file);
if (in_array($kzm, $imgType)) {
if (!is_dir('/'.$file)){
$filelist[] = $file;
}
}
}
if(sizeof($filelist) > 0)
$total_page = ceil(sizeof($filelist)/$step);
else $total_page = 0;

if(isset($_GET["p"])) $page = $_GET["p"];
else $page = 1;


function getFolderSize($dir) {
$size = 0;
if (is_dir($dir)){
$dh = opendir($dir);
while(false !== ($file = @readdir($dh))){
if($file !='.' and $file !='..'){
$path = $dir.'/'.$file;
if(is_dir($path)){
$size += dir_size($path);
}else if(is_file($path)){
$size += filesize($path);
}
}
}
closedir($dh);
}else{
echo "路径不是文件夹路径";
}
return $size;
}

$kb = 1024;
$mb = 1024 * 1024;
$gb = 1024 * 1024 * 1024;
$limitSize = "64GB";

$size = getFolderSize($path);
$r = "B";

$pre = $size / ($gb*$limitSize);
$pre = round($pre, 2, PHP_ROUND_HALF_UP);

if($size > $kb && $size < $mb){
$size = $size / $kb;
$size = round($size, 2, PHP_ROUND_HALF_UP);
$r = "KB";
}else if($size > $mb && $size < $gb){
$size = $size / $mb;
$size = round($size, 2, PHP_ROUND_HALF_UP);
$r = "MB";
}else{
$size = $size / $gb;
$size = round($size, 2, PHP_ROUND_HALF_UP);
$r = "GB";
}
?>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Albums</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="mdui/css/mdui.min.css">
<?php if($page<$total_page) echo '<link rel="preload" href="/?p=' .$page+1 . '" as="style" />'; ?>
<script>var total = <?php echo $total_page;?>; var pagenow = <?php echo $page;?>;</script>
<script src="mdui/js/index.js"></script>
<script src="mdui/js/mdui.min.js"></script>
</head>
<body class="mdui-bottom-nav-fixed">
<div class="mdui-appbar mdui-color-grey-700">
<div class="mdui-toolbar mdui-color-theme">
<a href="https://blog.minloha.cn" class="mdui-typo-title">Micamera</a>
<a href="javascript:new mdui.Dialog('#pre_pre').open();" class="mdui-typo-title"><?php echo $size . $r; ?>/<?php echo $limitSize; ?></a>
</div>
</div>
<div class="mdui-container-fluid">
<?php
if(sizeof($filelist) != 0)
for($i = ($page-1)*$step; $i < $page*$step; $i++){
if($i < sizeof($filelist)){
$f = $filelist[$i];
list($filename , $type_file) = explode('.', $f);
echo '<div class="mdui-card">';
echo '<div class="mdui-card-media">';
if($type_file == "jpg")
echo '<a href="./albums/'.$f.'"><img src="./albums/'.$f.'" style="width=100%;"/></a>';
else
echo '<a href="./albums/'.$f.'"><video><source src="./albums/'.$f.'" type="video/mp4"></video></a>';
echo '<div class="mdui-card-media-covered">';
echo '<div class="mdui-card-primary">';
echo '<div class="mdui-card-primary-title">'.$f.'</div>';
echo '</div>';
echo '</div>';
echo '</div>';
echo '<div class="mdui-card-actions">';
echo '<button class="mdui-btn mdui-ripple" onclick="remove(\'' . $f . '\')">删除</button>';
echo '<a style="text-decoration: none;color: black;" href="./albums/'.$f.'" download="/albums/' . $f . '"><button class="mdui-btn mdui-ripple" >保存</button></a>';
echo '</div>';
echo '</div>';
}
}
else{
echo '<div class="mdui-dialog" id="dialog">';
echo '<div class="mdui-dialog-title">Micamera提示</div>';
echo '<div class="mdui-dialog-content">相册是空的,没有什么可以展示的呢~</div>';
echo '<div class="mdui-dialog-actions">';
echo '<button class="mdui-btn mdui-ripple">好吧</button>';
echo '</div>';
echo '</div>';
echo "<script>new mdui.Dialog('#dialog').open()</script>";
}
?>
<?php
if($pre > 0.9){
echo '<div class="mdui-dialog" id="dialog">';
echo '<div class="mdui-dialog-title">Micamera警告</div>';
echo '<div class="mdui-dialog-content">内存空间不足,请删除照片!</div>';
echo '<div class="mdui-dialog-actions">';
echo '<button class="mdui-btn mdui-ripple">收到</button>';
echo '</div>';
echo '</div>';
echo "<script>new mdui.Dialog('#dialog').open();</script>";
}
?>
</div>
<div class="mdui-bottom-nav mdui-bottom-nav-text-auto mdui-color-grey-700">
<a onclick="sub()" class="mdui-ripple mdui-ripple-white mdui-bottom-nav-active">
<i class="mdui-icon material-icons">keyboard_arrow_left</i>
<label>上一页</label>
</a>
<p>第<?php echo $page; ?> / <?php echo $total_page;?>页</p>
<a onclick="add()" class="mdui-ripple mdui-ripple-white mdui-bottom-nav-active">
<i class="mdui-icon material-icons">keyboard_arrow_right</i>
<label>下一页</label>
</a>
</div>
<div class="mdui-dialog" id="pre_pre">
<div class="mdui-dialog-title">内存消耗情况:</div>

<div class="mdui-p-a-2">
<p>使用空间:<?php echo $size . $r; ?></p>
<p>使用占比:</p>
</div>
<div class="mdui-progress">
<div class="mdui-progress-determinate" style="width: <?php echo round($pre*100,0,PHP_ROUND_HALF_UP);?>%;"></div>
</div>

<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple">我看懂了</button>
</div>
</div>
</body>
</html>

除此之外还要有删除接口,只需要实现一个GET参数的文件删除即可:

1
2
3
4
5
6
7
8
<?php
$filename = $_GET['f'];
if(file_exists("./albums/" .$filename)){
unlink("./albums/" . $filename);
echo '1';
}else{
echo '0';
}

APP部分

这里因为视频文件放在apache2的网页二级目录,所以可以实现一个WebView视图即可,因为WebView无法直接获取返回状态码,所以需要用HttpURLConnection类进行测试连接,如果测试连接成功后WebView再进行连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package cn.minloha.micamera;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Environment;
import android.os.StrictMode;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.*;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.core.content.FileProvider;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

import static android.os.Environment.DIRECTORY_DCIM;
import static android.os.Environment.DIRECTORY_DOWNLOADS;

public class MainActivity extends AppCompatActivity {
private WebView browser;

@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);

browser = (WebView)findViewById(R.id.Toweb);
browser.getSettings().setDomStorageEnabled(true);
browser.getSettings().setDatabaseEnabled(true);
browser.getSettings().setJavaScriptEnabled(true);
browser.getSettings().setSupportZoom(true);
browser.getSettings().setBuiltInZoomControls(true);
String path = "http://10.0.4.1/";
try {
URL url = new URL(path);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(3000);
httpURLConnection.connect();
int code = httpURLConnection.getResponseCode();
if (code == 200) browser.loadUrl(path);
else{
browser.loadUrl("file:///android_asset/html/non.html");
Log.d("MicameraInfo","Http");
}
} catch (IOException e) {
browser.loadUrl("file:///android_asset/html/502.html");
Log.d("MicameraInfo","HttpError");
}

browser.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
int statusCode = errorResponse.getStatusCode();
Log.d("MINFO","Code is:" + statusCode);
}

@SuppressLint("WebViewClientOnReceivedSslError")
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
if (handler != null) {
handler.proceed();
}
}
});

browser.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
// 指定下载地址
String Filetype = url.substring(url.length()-3);
String filename = String.valueOf(System.currentTimeMillis()) + url.substring(url.length()-4);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setAllowedOverMetered(true);
request.setAllowedOverRoaming(true);
String info = "";
if(Filetype.equals("pdf")) {
filename = "产品说明书.pdf";
info = "在文件管理器中的文档中查看\"产品说明书.pdf\"";
request.setDestinationInExternalPublicDir(DIRECTORY_DOWNLOADS, filename);
}else {
request.setDestinationInExternalPublicDir(DIRECTORY_DCIM, filename);
info = "在相册中的DCIM下查看";
}
final DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
long downloadId = downloadManager.enqueue(request);
new AlertDialog.Builder(this).setTitle("下载成功!").setMessage(info).setPositiveButton("我知道了",null).show();
});

}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
WebView browser=(WebView)findViewById(R.id.Toweb);
if ((keyCode == KeyEvent.KEYCODE_BACK) && browser.canGoBack()) {
browser.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}

}

总结

没啥难度,在外壳上花费的时间比较多,软件很快就可以调通的。目前还差面板没有3D打印,剩下就是打胶和组装了!


送个小纪念品吧(2)
https://blog.minloha.cn/posts/070111538e4ed2024020125.html
作者
Minloha
发布于
2024年2月1日
更新于
2024年4月9日
许可协议