Dana
2025-12-23 bfc959385fd59284fca3e417af217d02d80cb845
1.h264文件写入tfcard
2.tfcard/h264/20251223/h264_时间戳.h264
1个文件已添加
9个文件已修改
187 ■■■■■ 已修改文件
README_H264_CHECK.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/aidl/com/anyun/h264/model/ResourceInfo.aidl 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService2.java 69 ●●●●● 补丁 | 查看 | 原始文档 | 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 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
check_h264.py 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
多进程方案使用说明.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
如何检查test.h264文件.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README_H264_CHECK.md
@@ -118,3 +118,4 @@
   ```
   åº”该看到:`00 00 00 01` æˆ– `00 00 01`(Annex-B起始码)
app/src/main/aidl/com/anyun/h264/model/ResourceInfo.aidl
@@ -5,3 +5,4 @@
 */
parcelable ResourceInfo;
app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -11,6 +11,7 @@
import com.anyun.h264.model.ResourceInfo;
import com.anyun.h264.model.WatermarkInfo;
import com.anyun.h264.util.FileUtil;
import org.json.JSONException;
import org.json.JSONObject;
@@ -123,6 +124,7 @@
     * ç¼–码配置类
     */
    private static class EncodeConfig {
        boolean useTFCard=true;//如果为true,则在tfcard æ ¹ç›®å½•建一个h264文件夹,在h264目录下以当前日期为名字的文件夹(比如20250123),然后h264文件就写入以当前日前为名字的目录下
         boolean enableFileOutput; //是否开启h264文件写入
         boolean enableNetworkTransmit; //开启h264,网络实时推流
        String ip;
@@ -147,6 +149,7 @@
                config.port = 0;
                config.simPhone = null;
                config.cameraId = 1; // é»˜è®¤ä½¿ç”¨ç¬¬ä¸€ä¸ªæ‘„像头
                config.useTFCard = false; // é»˜è®¤ä¸ä½¿ç”¨TF卡
                return config;
            }
            
@@ -159,6 +162,7 @@
            config.ip = json.optString("ip", null);
            config.port = json.optInt("port", 0);
            config.simPhone = json.optString("simPhone", null);
            config.useTFCard = json.optBoolean("useTFCard", false);
            
            // è§£æžcameraId(如果未指定,默认为1)
            if (json.has("cameraId")) {
@@ -396,6 +400,57 @@
    }
    
    /**
     * èŽ·å–è¾“å‡ºæ–‡ä»¶ç›®å½•ï¼ˆæ ¹æ®useTFCard配置)
     * @param useTFCard æ˜¯å¦ä½¿ç”¨TF卡
     * @return è¾“出目录路径
     */
    private String getOutputFileDirectory(boolean useTFCard) {
        if (useTFCard) {
            // ä½¿ç”¨TF卡:/sdcard/h264/当前日期/
            try {
                String storagePath = FileUtil.getStoragePath(this, true);
                if (storagePath == null || storagePath.trim().isEmpty()) {
                    Timber.w("TF card storage path not available, fallback to app directory");
                    return outputFileDirectory;
                }
                File externalStorage = new File(storagePath);
                if (!externalStorage.exists()) {
                    Timber.w("TF card storage directory does not exist: %s, fallback to app directory", storagePath);
                    return outputFileDirectory;
                }
                // èŽ·å–å½“å‰æ—¥æœŸï¼ˆæ ¼å¼ï¼šyyyyMMdd,如20250123)
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
                String dateDir = dateFormat.format(new Date());
                // æž„建路径:/sdcard/h264/20250123/
                File h264Dir = new File(externalStorage, "h264");
                File dateDirFile = new File(h264Dir, dateDir);
                // åˆ›å»ºç›®å½•(如果不存在)
                if (!dateDirFile.exists()) {
                    boolean created = dateDirFile.mkdirs();
                    if (!created && !dateDirFile.exists()) {
                        Timber.e("Failed to create TF card directory: %s, fallback to app directory", dateDirFile.getAbsolutePath());
                        return outputFileDirectory;
                    }
                }
                String tfCardPath = dateDirFile.getAbsolutePath();
                Timber.d("Using TF card directory: %s", tfCardPath);
                return tfCardPath;
            } catch (Exception e) {
                Timber.e(e, "Error getting TF card directory, fallback to app directory");
                return outputFileDirectory;
            }
        } else {
            // ä½¿ç”¨åº”用外部存储目录
            return outputFileDirectory;
        }
    }
    /**
     * å¯åŠ¨ç¼–ç ï¼ˆç»Ÿä¸€æ–¹æ³•ï¼Œæ”¯æŒæ–‡ä»¶å†™å…¥å’Œç½‘ç»œä¼ è¾“çš„ç»„åˆï¼‰
     * @param config ç¼–码配置
     * @return 0-成功,1-失败
@@ -405,8 +460,8 @@
            Timber.e("Encode config cannot be null");
            return 1;
        }
        Timber.d("Starting encode mode, fileOutput: %b, networkTransmit: %b",
                config.enableFileOutput, config.enableNetworkTransmit);
        Timber.d("Starting encode mode, fileOutput: %b, networkTransmit: %b, useTFCard: %b",
                config.enableFileOutput, config.enableNetworkTransmit, config.useTFCard);
        
        // å¦‚果编码器已经在运行,只更新配置
        if (h264Encoder != null) {
@@ -456,9 +511,12 @@
        int framerate = config != null && config.framerate > 0 ? config.framerate : DEFAULT_FRAME_RATE;
            h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE);
            // èŽ·å–è¾“å‡ºæ–‡ä»¶ç›®å½•ï¼ˆæ ¹æ®useTFCard配置)
            String outputDir = getOutputFileDirectory(config.useTFCard);
            // è®¾ç½®è¾“出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // ä½¿ç”¨ä¸€ä¸ªä¸´æ—¶æ–‡ä»¶åæ¥è®¾ç½®ç›®å½•,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            File tempFile = new File(outputDir, "temp.h264");
        h264Encoder.setOutputFile(tempFile.getAbsolutePath());
        h264Encoder.setEnableFileOutput(config.enableFileOutput);
            
app/src/main/java/com/anyun/h264/H264EncodeService2.java
@@ -8,6 +8,7 @@
import com.anyun.h264.model.ResourceInfo;
import com.anyun.h264.model.WatermarkInfo;
import com.anyun.h264.util.FileUtil;
import org.json.JSONException;
import org.json.JSONObject;
@@ -102,6 +103,7 @@
        int height;
        int framerate;
        String simPhone;
        boolean useTFCard = false; // æ˜¯å¦ä½¿ç”¨TF卡
        
        // ä»ŽJSON解析配置
        static EncodeConfig fromJson(String jsonConfig) throws JSONException {
@@ -114,6 +116,7 @@
                config.ip = null;
                config.port = 0;
                config.simPhone = null;
                config.useTFCard = false; // é»˜è®¤ä¸ä½¿ç”¨TF卡
                return config;
            }
            
@@ -124,6 +127,7 @@
            config.ip = json.optString("ip", null);
            config.port = json.optInt("port", 0);
            config.simPhone = json.optString("simPhone", null);
            config.useTFCard = json.optBoolean("useTFCard", false);
            
            return config;
        }
@@ -216,10 +220,61 @@
    }
    
    /**
     * èŽ·å–è¾“å‡ºæ–‡ä»¶ç›®å½•ï¼ˆæ ¹æ®useTFCard配置)
     * @param useTFCard æ˜¯å¦ä½¿ç”¨TF卡
     * @return è¾“出目录路径
     */
    private String getOutputFileDirectory(boolean useTFCard) {
        if (useTFCard) {
            // ä½¿ç”¨TF卡:/sdcard/h264/当前日期/
            try {
                String storagePath = FileUtil.getStoragePath(this, true);
                if (storagePath == null || storagePath.trim().isEmpty()) {
                    Timber.w("TF card storage path not available, fallback to app directory (camera2)");
                    return outputFileDirectory;
                }
                File externalStorage = new File(storagePath);
                if (!externalStorage.exists()) {
                    Timber.w("TF card storage directory does not exist: %s, fallback to app directory (camera2)", storagePath);
                    return outputFileDirectory;
                }
                // èŽ·å–å½“å‰æ—¥æœŸï¼ˆæ ¼å¼ï¼šyyyyMMdd,如20250123)
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
                String dateDir = dateFormat.format(new Date());
                // æž„建路径:/sdcard/h264/20250123/
                File h264Dir = new File(externalStorage, "h264");
                File dateDirFile = new File(h264Dir, dateDir);
                // åˆ›å»ºç›®å½•(如果不存在)
                if (!dateDirFile.exists()) {
                    boolean created = dateDirFile.mkdirs();
                    if (!created && !dateDirFile.exists()) {
                        Timber.e("Failed to create TF card directory: %s, fallback to app directory (camera2)", dateDirFile.getAbsolutePath());
                        return outputFileDirectory;
                    }
                }
                String tfCardPath = dateDirFile.getAbsolutePath();
                Timber.d("Using TF card directory (camera2): %s", tfCardPath);
                return tfCardPath;
            } catch (Exception e) {
                Timber.e(e, "Error getting TF card directory, fallback to app directory (camera2)");
                return outputFileDirectory;
            }
        } else {
            // ä½¿ç”¨åº”用外部存储目录
            return outputFileDirectory;
        }
    }
    /**
     * å¯åŠ¨æ–‡ä»¶ç¼–ç æ¨¡å¼ï¼ˆåªå†™å…¥æ–‡ä»¶ï¼Œä¸è¿›è¡Œç½‘ç»œæŽ¨é€ï¼‰
     */
    private int startFileEncode(EncodeConfig config) {
        Timber.d("Starting file encode mode (camera2)");
        Timber.d("Starting file encode mode (camera2), useTFCard: %b", config != null ? config.useTFCard : false);
        
        // å¦‚果编码器已经在运行,先停止
        if (h264Encoder != null) {
@@ -238,9 +293,13 @@
            int framerate = config != null && config.framerate > 0 ? config.framerate : DEFAULT_FRAME_RATE;
            h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE);
            // èŽ·å–è¾“å‡ºæ–‡ä»¶ç›®å½•ï¼ˆæ ¹æ®useTFCard配置)
            boolean useTFCard = config != null && config.useTFCard;
            String outputDir = getOutputFileDirectory(useTFCard);
            // è®¾ç½®è¾“出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // ä½¿ç”¨ä¸€ä¸ªä¸´æ—¶æ–‡ä»¶åæ¥è®¾ç½®ç›®å½•,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            File tempFile = new File(outputDir, "temp.h264");
            h264Encoder.setOutputFile(tempFile.getAbsolutePath());
            h264Encoder.setEnableFileOutput(true); // å¯ç”¨æ–‡ä»¶è¾“出
            
@@ -299,9 +358,13 @@
            int framerate = config != null && config.framerate > 0 ? config.framerate : DEFAULT_FRAME_RATE;
            h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE);
            // èŽ·å–è¾“å‡ºæ–‡ä»¶ç›®å½•ï¼ˆæ ¹æ®useTFCard配置)
            boolean useTFCard = config != null && config.useTFCard;
            String outputDir = getOutputFileDirectory(useTFCard);
            // è®¾ç½®è¾“出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // ä½¿ç”¨ä¸€ä¸ªä¸´æ—¶æ–‡ä»¶åæ¥è®¾ç½®ç›®å½•,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            File tempFile = new File(outputDir, "temp.h264");
            h264Encoder.setOutputFile(tempFile.getAbsolutePath());
            h264Encoder.setEnableFileOutput(true); // å¯ç”¨æ–‡ä»¶è¾“出
app/src/main/java/com/anyun/h264/model/ResourceInfo.java
@@ -156,3 +156,4 @@
}
app/src/main/java/com/anyun/h264/model/WatermarkInfo.java
@@ -138,3 +138,4 @@
    }
}
app/src/main/java/com/anyun/h264/util/FileUtil.java
New file
@@ -0,0 +1,47 @@
package com.anyun.h264.util;
import android.content.Context;
import android.os.storage.StorageManager;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class FileUtil {
    //获取插入的TFCard目录路径
    public static String getStoragePath(Context mContext, boolean is_removale) {
        if (mContext != null) {
            StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
            Class<?> storageVolumeClazz = null;
            try {
                storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
                Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
                Method getPath = storageVolumeClazz.getMethod("getPath");
                Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
                Object result = getVolumeList.invoke(mStorageManager);
                final int length = Array.getLength(result);
                for (int i = 0; i < length; i++) {
                    Object storageVolumeElement = Array.get(result, i);
                    String path = (String) getPath.invoke(storageVolumeElement);
                    boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
                    if (is_removale == removable) {
                        return path;
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return  null;
        } else {
            return null;
        }
    }
}
check_h264.py
@@ -208,3 +208,4 @@
    sys.exit(0 if success else 1)
¶à½ø³Ì·½°¸Ê¹ÓÃ˵Ã÷.md
@@ -116,3 +116,4 @@
- æ‰€æœ‰æ“ä½œéƒ½é€šè¿‡AIDL接口进行进程间通信
ÈçºÎ¼ì²étest.h264Îļþ.md
@@ -182,3 +182,4 @@
3. **用VLC播放**验证
4. **如果还有问题**,运行检查工具获取详细诊断