12个文件已修改
218 ■■■■■ 已修改文件
README_H264_CHECK.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/aidl/com/anyun/h264/model/ResourceInfo.aidl 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService2.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264Encoder.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/model/ResourceInfo.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/model/WatermarkInfo.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/util/FileUtil.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
check_h264.py 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
多进程方案使用说明.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
如何检查test.h264文件.md 1 ●●●● 补丁 | 查看 | 原始文档 | 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 @@