| README_H264_CHECK.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/build.gradle | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/aidl/com/anyun/h264/model/ResourceInfo.aidl | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/H264EncodeService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/H264EncodeService2.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/H264Encoder.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/model/ResourceInfo.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/model/WatermarkInfo.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/util/FileUtil.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| check_h264.py | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| 多进程方案使用说明.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| 如何检查test.h264文件.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
README_H264_CHECK.md
@@ -120,3 +120,4 @@ app/build.gradle
@@ -14,8 +14,8 @@ applicationId "com.anyun.h264" minSdk 21 targetSdk 35 versionCode 1 versionName "1.0.0" versionCode 2 versionName "1.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } app/src/main/aidl/com/anyun/h264/model/ResourceInfo.aidl
@@ -7,3 +7,4 @@ app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -558,6 +558,7 @@ // 设置 Contextï¼ç¨äºæ¸ ç TF å¡æä»¶ï¼ h264Encoder.setContext(this); h264Encoder.setUseTFCard(config.useTFCard); // 设置ç¼ç åæ°ï¼ä½¿ç¨é ç½®ä¸çåæ°ï¼ int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH; @@ -906,7 +907,7 @@ return resourceList; } File[] files = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".h264")); File[] files = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".h264")&&!name.contains("camera2")); //è¿æ»¤æä¸æ¯.h264æä»¶åP2æå头çè§é¢æä»¶ if (files == null || files.length == 0) { Timber.d("No H264 files found in directory: %s", directoryPath); return resourceList; app/src/main/java/com/anyun/h264/H264EncodeService2.java
@@ -289,6 +289,7 @@ // 设置 Contextï¼ç¨äºæ¸ ç TF å¡æä»¶ï¼ h264Encoder.setContext(this); // 设置ç¼ç åæ°ï¼ä½¿ç¨é ç½®ä¸çåæ°ï¼ int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH; @@ -298,6 +299,7 @@ // è·åè¾åºæä»¶ç®å½ï¼æ ¹æ®useTFCardé ç½®ï¼ boolean useTFCard = config != null && config.useTFCard; h264Encoder.setUseTFCard(useTFCard); String outputDir = getOutputFileDirectory(useTFCard); // 设置è¾åºæä»¶ç®å½ï¼H264Encoderä¼èªå¨ç®¡çæä»¶åå»ºï¼æ¯åéä¸ä¸ªæä»¶ï¼ app/src/main/java/com/anyun/h264/H264Encoder.java
@@ -88,6 +88,7 @@ // Context 忏 çé ç½® private Context context; // Context 对象ï¼ç¨äºæ¸ ç TF 塿件 private long maxH264TotalSizeGB = 100; // æå¤§ H264 æä»¶æ»å¤§å°ï¼GBï¼ï¼é»è®¤ 100GB private boolean useTFCard = false; // æ¯å¦ä½¿ç¨ TF å¡è¾åº // ç½ç»ä¼ è¾æ§å¶ private boolean enableNetworkTransmission = true; // æ¯å¦å¯ç¨TCP/UDPç½ç»ä¼ è¾ @@ -95,6 +96,11 @@ // SPS/PPS ç¼åï¼ç¨äºç½ç»ä¼ è¾ï¼ private byte[] spsBuffer = null; // SPS ç¼å private byte[] ppsBuffer = null; // PPS ç¼å // processCamera å¤±è´¥è®¡æ° private int consecutiveFailureCount = 0; // è¿ç»å¤±è´¥æ¬¡æ° private int logPrintCount = 0; // æ¥å¿æå°æ¬¡æ°ï¼æå¤5æ¬¡ï¼ private static final int MAX_CONSECUTIVE_FAILURES = 5; // æå¤§è¿ç»å¤±è´¥æ¬¡æ° // ç¼ç åè° public interface OnFrameEncodedCallback { @@ -169,6 +175,13 @@ */ public void setContext(Context context) { this.context = context; } /** * 设置æ¯å¦ä½¿ç¨ TF å¡ */ public void setUseTFCard(boolean useTFCard) { this.useTFCard = useTFCard; } /** @@ -454,6 +467,16 @@ fileOutputStream = null; } // å¦æä½¿ç¨å é¨ Flashï¼é TF å¡ï¼ï¼å¨åå»ºæ°æä»¶åæ£æ¥å¹¶æ¸ çç©ºé´ if (!useTFCard && context != null) { int result = FileUtil.ensureInternalFlashSpaceForH264(context); if (result == -1) { Timber.e("Insufficient internal flash space (<800MB) even after cleanup, stop file output"); enableFileOutput = false; return false; } } // æ£æ¥å¹¶æ¸ ç TF å¡ä¸ç h264 æä»¶ï¼å¦æéè¦ï¼ if (context != null && outputFileDirectory != null && !outputFileDirectory.isEmpty()) { try { @@ -623,16 +646,47 @@ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); // é置失败计æ°å¨ consecutiveFailureCount = 0; logPrintCount = 0; while (isRunning.get()) { try { // processCamera - 读åä¸å¸§ int processResult = usbCamera.processCamera(); if (processResult != 0) { Timber.w("processCamera returned: " + processResult); // å¢å è¿ç»å¤±è´¥è®¡æ° consecutiveFailureCount++; // éå¶æ¥å¿æå°æ¬¡æ°ï¼æå¤5æ¬¡ï¼ if (logPrintCount < MAX_CONSECUTIVE_FAILURES) { Timber.w("processCamera returned: " + processResult); logPrintCount++; } // 妿è¿ç»5次失败ï¼ç»æ¢ç¼ç if (consecutiveFailureCount >= MAX_CONSECUTIVE_FAILURES) { Timber.e("processCamera failed %d times consecutively, stopping encoding", MAX_CONSECUTIVE_FAILURES); // 忢ç¼ç isRunning.set(false); // å ³éæä»¶è¾åº closeFileOutput(); // ç¦ç¨æä»¶è¾åºåç½ç»ä¼ è¾ enableFileOutput = false; if (enableNetworkTransmission && protocolHelper != null) { protocolHelper.closeSocket(); } break; } Thread.sleep(10); continue; } // æåè·åå¸§æ°æ®ï¼é置失败计æ°å¨ consecutiveFailureCount = 0; logPrintCount = 0; // éç½®æ¥å¿è®¡æ°ï¼å è®¸ä¸æ¬¡å¤±è´¥æ¶éæ°æå° // è·åRGBAæ°æ® (type=1 è¡¨ç¤ºæ¨æµï¼è¾åºYUV420Pæ ¼å¼) usbCamera.rgba(0, yuvBuffer); app/src/main/java/com/anyun/h264/model/ResourceInfo.java
@@ -158,3 +158,4 @@ app/src/main/java/com/anyun/h264/model/WatermarkInfo.java
@@ -140,3 +140,4 @@ app/src/main/java/com/anyun/h264/util/FileUtil.java
@@ -179,6 +179,137 @@ } /** * æ£æ¥å é¨ Flashï¼é TF å¡ï¼å©ä½ç©ºé´ï¼å¦æå°äº 800MBï¼åææ¶é´é¡ºåºå é¤ h264_*.h264 æä»¶ * ç®å½ï¼context.getExternalFilesDir(null).getAbsolutePath() * æä»¶åæ ¼å¼ç¤ºä¾ï¼h264_1735023032000.h264ãh264_camera2_1735023032000.h264 * * å é¤è§åï¼ * - ææä»¶åä¸çæ¶é´æ³ä»å°å°å¤§ï¼è¶æ©è¶å å ï¼ä¾æ¬¡å é¤ * - æ¯å é¤ä¸æ¬¡åéæ°è®¡ç®å©ä½ç©ºé´ï¼ç´å° ⥠800MB ææä»¶å å® * * è¿åå¼ï¼ * - 0ï¼æç»å©ä½ç©ºé´ ⥠800MB ææ éå é¤ * - -1ï¼å é¤å®ææç¬¦åè§åçæä»¶åï¼å©ä½ç©ºé´ä»ç¶ < 800MB */ public static int ensureInternalFlashSpaceForH264(Context context) { if (context == null) { Timber.w("ensureInternalFlashSpaceForH264: context is null"); return 0; } File externalDir = context.getExternalFilesDir(null); if (externalDir == null) { Timber.w("ensureInternalFlashSpaceForH264: external files dir is null"); return 0; } String basePath = externalDir.getAbsolutePath(); long minFreeBytes = 800L * 1024L * 1024L; // 800MB long freeBytes = getFreeSpaceBytes(basePath); Timber.d("ensureInternalFlashSpaceForH264: freeBytes=%d, minRequired=%d", freeBytes, minFreeBytes); if (freeBytes >= minFreeBytes) { // 空é´å è¶³ï¼æ éå¤ç return 0; } // æ¶é符åå½åè§åç h264 æä»¶ File[] files = externalDir.listFiles(); if (files == null || files.length == 0) { Timber.w("ensureInternalFlashSpaceForH264: no files in dir -> %s", basePath); // å·²ç»æ²¡æå¯å çæä»¶ï¼å¦æä»å°äº 800MBï¼åç´æ¥è¿å -1 return freeBytes >= minFreeBytes ? 0 : -1; } class H264FileInfo { File file; long timestamp; H264FileInfo(File file, long timestamp) { this.file = file; this.timestamp = timestamp; } } List<H264FileInfo> h264Files = new ArrayList<>(); for (File file : files) { if (!file.isFile()) { continue; } String name = file.getName(); // åªå¤ç .h264 ç»å°¾ï¼ä¸ä»¥ h264_ å¼å¤´çæä»¶ if (!name.toLowerCase(Locale.CHINA).endsWith(".h264")) { continue; } if (!name.startsWith("h264_") && !name.startsWith("h264_camera2_")) { continue; } // æåæ¶é´æ³é¨å String timePart = null; if (name.startsWith("h264_camera2_")) { // åç¼é¿åº¦ 13ï¼"h264_camera2_" timePart = name.substring("h264_camera2_".length(), name.length() - ".h264".length()); } else if (name.startsWith("h264_")) { // åç¼é¿åº¦ 5ï¼"h264_" timePart = name.substring("h264_".length(), name.length() - ".h264".length()); } if (timePart == null || timePart.isEmpty()) { continue; } try { long ts = Long.parseLong(timePart); h264Files.add(new H264FileInfo(file, ts)); } catch (NumberFormatException e) { // æä»¶åä¸ç¬¦åæ¶é´æ³æ ¼å¼ï¼è·³è¿ Timber.w("ensureInternalFlashSpaceForH264: invalid timestamp in file name -> %s", name); } } if (h264Files.isEmpty()) { Timber.w("ensureInternalFlashSpaceForH264: no matched h264 files in -> %s", basePath); return freeBytes >= minFreeBytes ? 0 : -1; } // ææ¶é´æ³ååºæåï¼è¶æ©çè¶å å ï¼ Collections.sort(h264Files, new Comparator<H264FileInfo>() { @Override public int compare(H264FileInfo o1, H264FileInfo o2) { return Long.compare(o1.timestamp, o2.timestamp); } }); int deletedCount = 0; for (H264FileInfo info : h264Files) { if (freeBytes >= minFreeBytes) { break; } File f = info.file; long size = f.length(); Timber.d("ensureInternalFlashSpaceForH264: deleting file -> %s, size=%d, ts=%d", f.getAbsolutePath(), size, info.timestamp); if (f.delete()) { deletedCount++; // å é¤åéæ°è·åå©ä½ç©ºé´ï¼æ´åç¡® freeBytes = getFreeSpaceBytes(basePath); Timber.d("ensureInternalFlashSpaceForH264: after delete, freeBytes=%d", freeBytes); } else { Timber.e("ensureInternalFlashSpaceForH264: failed to delete file -> %s", f.getAbsolutePath()); } } Timber.i("ensureInternalFlashSpaceForH264: deleted %d files, final freeBytes=%d", deletedCount, freeBytes); return freeBytes >= minFreeBytes ? 0 : -1; } /** * 计ç®ç®å½ä¸ææ .h264 æä»¶çæ»å¤§å°ï¼åèï¼ */ private static long calculateH264FilesSize(File dir) { @@ -211,6 +342,21 @@ } /** * è·åæå®è·¯å¾çå©ä½ç©ºé´ï¼åèï¼ */ private static long getFreeSpaceBytes(String path) { try { StatFs statFs = new StatFs(path); long blockSize = statFs.getBlockSizeLong(); long availableBlocks = statFs.getAvailableBlocksLong(); return availableBlocks * blockSize; } catch (Exception e) { Timber.e(e, "Error getting free space (bytes) for path: %s", path); return 0; } } /** * éå½å é¤ç®å½åå ¶ææå 容 */ private static boolean deleteDirectory(File dir) { check_h264.py
@@ -210,3 +210,4 @@ ¶à½ø³Ì·½°¸Ê¹ÓÃ˵Ã÷.md
@@ -118,3 +118,4 @@ ÈçºÎ¼ì²étest.h264Îļþ.md
@@ -184,3 +184,4 @@