Dana
2025-12-01 ccf7a1f19b5c6276178c72609c88d057214c8239
1.添加timber日志库
7个文件已修改
2个文件已添加
381 ■■■■ 已修改文件
app/build.gradle 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/FileLoggingTree.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264Application.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService.java 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264FileTransmitter.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/MainActivity.kt 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradle/libs.versions.toml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle
@@ -68,6 +68,7 @@
    implementation libs.androidx.ui.tooling.preview
    implementation libs.androidx.material3
    implementation libs.netty.all
    implementation libs.timber
    testImplementation libs.junit
    androidTestImplementation libs.androidx.junit
    androidTestImplementation libs.androidx.espresso.core
app/src/main/AndroidManifest.xml
@@ -7,10 +7,15 @@
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
    <!-- 存储权限(用于日志文件) -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!-- USB摄像头权限 -->
    <uses-feature android:name="android.hardware.usb.host" />
    <application
        android:name=".H264Application"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
app/src/main/java/com/anyun/h264/FileLoggingTree.java
New file
@@ -0,0 +1,146 @@
package com.anyun.h264;
import android.os.Environment;
import android.util.Log;
import timber.log.Timber;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
 * Timber Tree implementation that logs to files.
 * Logs are saved to sdcard/nvlog/h264_yyyyMMdd.log format.
 */
public class FileLoggingTree extends Timber.DebugTree {
    private static final String LOG_DIR = "nvlog";
    private static final String LOG_PREFIX = "h264_";
    private static final String LOG_SUFFIX = ".log";
    private static final String DATE_FORMAT = "yyyyMMdd";
    private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
    private String currentLogFile = null;
    private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.CHINA);
    private SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT, Locale.CHINA);
    @Override
    protected void log(int priority, String tag, String message, Throwable t) {
        try {
            // Get today's log file path
            String today = dateFormat.format(new Date());
            String logFileName = LOG_PREFIX + today + LOG_SUFFIX;
            // Check if we need to update the log file (new day)
            if (!logFileName.equals(currentLogFile)) {
                currentLogFile = logFileName;
            }
            File logFile = getLogFile(logFileName);
            if (logFile == null) {
                return;
            }
            // Format log entry
            String logEntry = formatLogEntry(priority, tag, message, t);
            // Write to file (append mode)
            synchronized (this) {
                try (FileWriter writer = new FileWriter(logFile, true)) {
                    writer.append(logEntry);
                    writer.append("\n");
                    writer.flush();
                } catch (IOException e) {
                    // If file writing fails, log to system log as fallback
                    Log.e("FileLoggingTree", "Failed to write log to file", e);
                }
            }
        } catch (Exception e) {
            // If anything goes wrong, log to system log
            Log.e("FileLoggingTree", "Error in FileLoggingTree", e);
        }
    }
    /**
     * Get the log file. Creates directory if needed.
     */
    private File getLogFile(String fileName) {
        try {
            File logDir = new File(Environment.getExternalStorageDirectory(), LOG_DIR);
            if (!logDir.exists()) {
                if (!logDir.mkdirs()) {
                    Log.e("FileLoggingTree", "Failed to create log directory: " + logDir.getAbsolutePath());
                    return null;
                }
            }
            File logFile = new File(logDir, fileName);
            if (!logFile.exists()) {
                if (!logFile.createNewFile()) {
                    Log.e("FileLoggingTree", "Failed to create log file: " + logFile.getAbsolutePath());
                    return null;
                }
            }
            return logFile;
        } catch (IOException e) {
            Log.e("FileLoggingTree", "Error getting log file", e);
            return null;
        }
    }
    /**
     * Format a log entry with timestamp, priority, tag, and message.
     */
    private String formatLogEntry(int priority, String tag, String message, Throwable t) {
        StringBuilder sb = new StringBuilder();
        // Timestamp
        sb.append(timestampFormat.format(new Date()));
        sb.append(" ");
        // Priority level
        sb.append(getPriorityString(priority));
        sb.append("/");
        // Tag
        sb.append(tag);
        sb.append(": ");
        // Message
        sb.append(message);
        // Throwable stack trace
        if (t != null) {
            sb.append("\n");
            sb.append(Log.getStackTraceString(t));
        }
        return sb.toString();
    }
    /**
     * Get priority string representation.
     */
    private String getPriorityString(int priority) {
        switch (priority) {
            case Log.VERBOSE:
                return "V";
            case Log.DEBUG:
                return "D";
            case Log.INFO:
                return "I";
            case Log.WARN:
                return "W";
            case Log.ERROR:
                return "E";
            case Log.ASSERT:
                return "A";
            default:
                return "?";
        }
    }
}
app/src/main/java/com/anyun/h264/H264Application.java
New file
@@ -0,0 +1,27 @@
package com.anyun.h264;
import android.app.Application;
import timber.log.Timber;
/**
 * Application class for initializing Timber logging.
 */
public class H264Application extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // Initialize Timber
//        if (BuildConfig.DEBUG) {
            // In debug mode, use both console and file logging
            Timber.plant(new Timber.DebugTree());
            Timber.plant(new FileLoggingTree());
//        } else {
//            // In release mode, only use file logging
//            Timber.plant(new FileLoggingTree());
//        }
        Timber.d("H264Application onCreate - Timber initialized");
    }
}
app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -4,7 +4,7 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import timber.log.Timber;
import com.anyun.h264.model.ResourceInfo;
@@ -56,22 +56,22 @@
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "H264EncodeService created");
        Timber.d("H264EncodeService created");
        
        // 初始化输出文件目录(使用应用外部存储目录)
        outputFileDirectory = getExternalFilesDir(null).getAbsolutePath();
        Log.d(TAG, "Output file directory: " + outputFileDirectory);
        Timber.d("Output file directory: %s", outputFileDirectory);
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "Service bound");
        Timber.d("Service bound");
        return binder;
    }
    
    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "Service unbound");
        Timber.d("Service unbound");
        // 不自动停止编码器,让它在服务中保持运行
        return super.onUnbind(intent);
    }
@@ -79,7 +79,7 @@
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Service destroyed");
        Timber.d("Service destroyed");
        
        // 停止并释放编码器和文件传输器
        stopEncoder();
@@ -170,7 +170,7 @@
     * @return 0-成功,1-失败
     */
    private synchronized int controlEncode(int action, String jsonConfig) {
        Log.d(TAG, "controlEncode called with action: " + action + ", jsonConfig: " + jsonConfig);
        Timber.d("controlEncode called with action: %d, jsonConfig: %s", action, jsonConfig);
        
        try {
            switch (action) {
@@ -179,7 +179,7 @@
                        EncodeConfig config0 = EncodeConfig.fromJson(jsonConfig);
                        return startFileEncode(config0);
                    } catch (JSONException e) {
                        Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e);
                        Timber.e(e, "Failed to parse JSON config: %s", jsonConfig);
                        return 1;
                    }
                    
@@ -191,7 +191,7 @@
                        EncodeConfig config2 = EncodeConfig.fromJson(jsonConfig);
                        return startNetworkEncode(config2);
                    } catch (JSONException e) {
                        Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e);
                        Timber.e(e, "Failed to parse JSON config: %s", jsonConfig);
                        return 1;
                    }
                    
@@ -203,7 +203,7 @@
                        FileTransmitConfig config4 = FileTransmitConfig.fromJson(jsonConfig);
                        return startFileTransmit(config4);
                    } catch (JSONException e) {
                        Log.e(TAG, "Failed to parse JSON config: " + jsonConfig, e);
                        Timber.e(e, "Failed to parse JSON config: %s", jsonConfig);
                        return 1;
                    }
                    
@@ -211,11 +211,11 @@
                    return stopFileTransmitter();
                    
                default:
                    Log.e(TAG, "Unknown action: " + action);
                    Timber.e("Unknown action: %d", action);
                    return 1; // 失败
            }
        } catch (Exception e) {
            Log.e(TAG, "Error in controlEncode", e);
            Timber.e(e, "Error in controlEncode");
            return 1; // 失败
        }
    }
@@ -224,11 +224,11 @@
     * 启动文件编码模式(只写入文件,不进行网络推送)
     */
    private int startFileEncode(EncodeConfig config) {
        Log.d(TAG, "Starting file encode mode");
        Timber.d("Starting file encode mode");
        
        // 如果编码器已经在运行,先停止
        if (h264Encoder != null) {
            Log.w(TAG, "Encoder is already running, stopping it first");
            Timber.w("Encoder is already running, stopping it first");
            stopEncoder();
        }
        
@@ -245,7 +245,7 @@
            long timeFile = System.currentTimeMillis();
            SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss");
            String str = bcdFormat.format(timeFile);
            Log.i(TAG,"文件名:"+str);
            Timber.i("文件名:%s", str);
            // 设置输出文件
            String fileName = "h264_" + timeFile+ ".h264";
            File outputFile = new File(outputFileDirectory, fileName);
@@ -259,16 +259,16 @@
            int[] resolution = {width, height};
            if (h264Encoder.initialize(DEFAULT_CAMERA_ID_RANGE, null, resolution, false)) {
                h264Encoder.start();
                Log.d(TAG, "File encode started successfully, output file: " + outputFile.getAbsolutePath() +
                        ", resolution: " + width + "x" + height + ", framerate: " + framerate);
                Timber.d("File encode started successfully, output file: %s, resolution: %dx%d, framerate: %d",
                        outputFile.getAbsolutePath(), width, height, framerate);
                return 0; // 成功
            } else {
                Log.e(TAG, "Failed to initialize encoder");
                Timber.e("Failed to initialize encoder");
                h264Encoder = null;
                return 1; // 失败
            }
        } catch (Exception e) {
            Log.e(TAG, "Failed to start file encode", e);
            Timber.e(e, "Failed to start file encode");
            h264Encoder = null;
            return 1; // 失败
        }
@@ -278,17 +278,17 @@
     * 启动网络推送模式(只进行网络推送,不写入文件)
     */
    private int startNetworkEncode(EncodeConfig config) {
        Log.d(TAG, "Starting network encode mode");
        Timber.d("Starting network encode mode");
        
        // 如果编码器已经在运行,先停止
        if (h264Encoder != null) {
            Log.w(TAG, "Encoder is already running, stopping it first");
            Timber.w("Encoder is already running, stopping it first");
            stopEncoder();
        }
        
        // 检查必需的配置参数
        if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) {
            Log.e(TAG, "Network encode requires valid ip and port in config");
            Timber.e("Network encode requires valid ip and port in config");
            return 1; // 失败
        }
        
@@ -321,16 +321,16 @@
            int[] resolution = {width, height};
            if (h264Encoder.initialize(DEFAULT_CAMERA_ID_RANGE, null, resolution, false)) {
                h264Encoder.start();
                Log.d(TAG, "Network encode started successfully, server: " + config.ip + ":" + config.port +
                        ", resolution: " + width + "x" + height + ", framerate: " + framerate);
                Timber.d("Network encode started successfully, server: %s:%d, resolution: %dx%d, framerate: %d",
                        config.ip, config.port, width, height, framerate);
                return 0; // 成功
            } else {
                Log.e(TAG, "Failed to initialize encoder");
                Timber.e("Failed to initialize encoder");
                h264Encoder = null;
                return 1; // 失败
            }
        } catch (Exception e) {
            Log.e(TAG, "Failed to start network encode", e);
            Timber.e(e, "Failed to start network encode");
            h264Encoder = null;
            return 1; // 失败
        }
@@ -340,22 +340,22 @@
     * 停止编码器
     */
    private int stopEncoder() {
        Log.d(TAG, "Stopping encoder");
        Timber.d("Stopping encoder");
        
        if (h264Encoder != null) {
            try {
                h264Encoder.stop();
                h264Encoder.release();
                h264Encoder = null;
                Log.d(TAG, "Encoder stopped successfully");
                Timber.d("Encoder stopped successfully");
                return 0; // 成功
            } catch (Exception e) {
                Log.e(TAG, "Error stopping encoder", e);
                Timber.e(e, "Error stopping encoder");
                h264Encoder = null;
                return 1; // 失败
            }
        } else {
            Log.w(TAG, "Encoder is not running");
            Timber.w("Encoder is not running");
            return 0; // 成功(没有运行的编码器,视为成功)
        }
    }
@@ -364,22 +364,22 @@
     * 启动文件传输模式(从H264文件读取并网络推送)
     */
    private int startFileTransmit(FileTransmitConfig config) {
        Log.d(TAG, "Starting file transmit mode");
        Timber.d("Starting file transmit mode");
        
        // 如果文件传输器已经在运行,先停止
        if (h264FileTransmitter != null) {
            Log.w(TAG, "File transmitter is already running, stopping it first");
            Timber.w("File transmitter is already running, stopping it first");
            stopFileTransmitter();
        }
        
        // 检查必需的配置参数
        if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) {
            Log.e(TAG, "File transmit requires valid ip and port in config");
            Timber.e("File transmit requires valid ip and port in config");
            return 1; // 失败
        }
        
        if (config.filePath == null || config.filePath.trim().isEmpty()) {
            Log.e(TAG, "File transmit requires valid filePath in config");
            Timber.e("File transmit requires valid filePath in config");
            return 1; // 失败
        }
        
@@ -387,7 +387,7 @@
            // 检查文件是否存在
            File file = new File(config.filePath);
            if (!file.exists() || !file.isFile()) {
                Log.e(TAG, "File does not exist: " + config.filePath);
                Timber.e("File does not exist: %s", config.filePath);
                return 1; // 失败
            }
            
@@ -413,24 +413,24 @@
            h264FileTransmitter.setOnTransmitProgressCallback(new H264FileTransmitter.OnTransmitProgressCallback() {
                @Override
                public void onProgress(int currentFrame, int totalFrames) {
                    Log.d(TAG, "File transmit progress: frame " + currentFrame +
                            (totalFrames > 0 ? " of " + totalFrames : ""));
                    Timber.d("File transmit progress: frame %d%s", currentFrame,
                            totalFrames > 0 ? " of " + totalFrames : "");
                }
                
                @Override
                public void onComplete() {
                    Log.d(TAG, "File transmit completed");
                    Timber.d("File transmit completed");
                }
                
                @Override
                public void onError(String error) {
                    Log.e(TAG, "File transmit error: " + error);
                    Timber.e("File transmit error: %s", error);
                }
            });
            
            // 初始化Socket连接
            if (!h264FileTransmitter.initialize()) {
                Log.e(TAG, "Failed to initialize file transmitter socket");
                Timber.e("Failed to initialize file transmitter socket");
                h264FileTransmitter = null;
                return 1; // 失败
            }
@@ -438,19 +438,18 @@
            // 开始传输文件
            h264FileTransmitter.transmitFile(config.filePath);
            
            Log.d(TAG, "File transmit started successfully, file: " + config.filePath +
                    ", server: " + config.ip + ":" + config.port +
                    ", protocol: " + (config.protocolType == JT1076ProtocolHelper.PROTOCOL_TYPE_UDP ? "UDP" : "TCP") +
                    ", framerate: " + framerate);
            Timber.d("File transmit started successfully, file: %s, server: %s:%d, protocol: %s, framerate: %d",
                    config.filePath, config.ip, config.port,
                    config.protocolType == JT1076ProtocolHelper.PROTOCOL_TYPE_UDP ? "UDP" : "TCP", framerate);
            return 0; // 成功
            
        } catch (Exception e) {
            Log.e(TAG, "Failed to start file transmit", e);
            Timber.e(e, "Failed to start file transmit");
            if (h264FileTransmitter != null) {
                try {
                    h264FileTransmitter.stop();
                } catch (Exception ex) {
                    Log.e(TAG, "Error stopping file transmitter after failure", ex);
                    Timber.e(ex, "Error stopping file transmitter after failure");
                }
                h264FileTransmitter = null;
            }
@@ -462,21 +461,21 @@
     * 停止文件传输器
     */
    private int stopFileTransmitter() {
        Log.d(TAG, "Stopping file transmitter");
        Timber.d("Stopping file transmitter");
        
        if (h264FileTransmitter != null) {
            try {
                h264FileTransmitter.stop();
                h264FileTransmitter = null;
                Log.d(TAG, "File transmitter stopped successfully");
                Timber.d("File transmitter stopped successfully");
                return 0; // 成功
            } catch (Exception e) {
                Log.e(TAG, "Error stopping file transmitter", e);
                Timber.e(e, "Error stopping file transmitter");
                h264FileTransmitter = null;
                return 1; // 失败
            }
        } else {
            Log.w(TAG, "File transmitter is not running");
            Timber.w("File transmitter is not running");
            return 0; // 成功(没有运行的文件传输器,视为成功)
        }
    }
@@ -488,7 +487,7 @@
     * @return 资源列表
     */
    private List<ResourceInfo> getResourceList(String startTime, String endTime) {
        Log.d(TAG, "getResourceList called, startTime: " + startTime + ", endTime: " + endTime);
        Timber.d("getResourceList called, startTime: %s, endTime: %s", startTime, endTime);
        
        List<ResourceInfo> resourceList = new ArrayList<>();
        
@@ -496,13 +495,13 @@
            // 扫描输出目录中的H264文件
            File dir = new File(outputFileDirectory);
            if (!dir.exists() || !dir.isDirectory()) {
                Log.w(TAG, "Output directory does not exist: " + outputFileDirectory);
                Timber.w("Output directory does not exist: %s", outputFileDirectory);
                return resourceList;
            }
            
            File[] files = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".h264"));
            if (files == null || files.length == 0) {
                Log.d(TAG, "No H264 files found in directory");
                Timber.d("No H264 files found in directory");
                return resourceList;
            }
            
@@ -511,7 +510,7 @@
            Date endDate = parseTime(endTime);
            
            if (startDate == null || endDate == null) {
                Log.e(TAG, "Invalid time format, startTime: " + startTime + ", endTime: " + endTime);
                Timber.e("Invalid time format, startTime: %s, endTime: %s", startTime, endTime);
                return resourceList;
            }
            
@@ -523,11 +522,11 @@
                }
            }
            
            Log.d(TAG, "Found " + resourceList.size() + " resources in time range");
            Timber.d("Found %d resources in time range", resourceList.size());
            return resourceList;
            
        } catch (Exception e) {
            Log.e(TAG, "Error getting resource list", e);
            Timber.e(e, "Error getting resource list");
            return resourceList;
        }
    }
@@ -578,7 +577,7 @@
            return resourceInfo;
            
        } catch (Exception e) {
            Log.e(TAG, "Error creating resource info from file: " + file.getName(), e);
            Timber.e(e, "Error creating resource info from file: %s", file.getName());
            return null;
        }
    }
@@ -597,7 +596,7 @@
            SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss", Locale.CHINA);
            return format.parse(timeStr);
        } catch (ParseException e) {
            Log.e(TAG, "Failed to parse time: " + timeStr, e);
            Timber.e(e, "Failed to parse time: %s", timeStr);
            return null;
        }
    }
app/src/main/java/com/anyun/h264/H264FileTransmitter.java
@@ -1,6 +1,6 @@
package com.anyun.h264;
import android.util.Log;
import timber.log.Timber;
import java.io.File;
import java.io.FileInputStream;
@@ -119,7 +119,7 @@
    public void setFrameRate(int frameRate) {
        this.frameRate = frameRate > 0 ? frameRate : 25;
        this.frameInterval = 1000 / this.frameRate;
        Log.d(TAG, "Frame rate set to: " + this.frameRate + " fps, interval: " + this.frameInterval + " ms");
        Timber.d("Frame rate set to: %d fps, interval: %d ms", this.frameRate, this.frameInterval);
    }
    
    /**
@@ -135,12 +135,12 @@
     */
    public boolean initialize() {
        if (isRunning.get()) {
            Log.w(TAG, "Transmitter is already running");
            Timber.w("Transmitter is already running");
            return false;
        }
        
        if (!protocolHelper.initializeSocket()) {
            Log.e(TAG, "Failed to initialize socket");
            Timber.e("Failed to initialize socket");
            return false;
        }
        
@@ -152,7 +152,7 @@
        lastIFrameTime = 0;
        lastFrameTime = 0;
        
        Log.d(TAG, "Socket initialized successfully");
        Timber.d("Socket initialized successfully");
        return true;
    }
    
@@ -162,13 +162,13 @@
     */
    public void transmitFile(String filePath) {
        if (isRunning.get()) {
            Log.w(TAG, "Transmitter is already running");
            Timber.w("Transmitter is already running");
            return;
        }
        
        File file = new File(filePath);
        if (!file.exists() || !file.isFile()) {
            Log.e(TAG, "File does not exist: " + filePath);
            Timber.e("File does not exist: %s", filePath);
            if (progressCallback != null) {
                progressCallback.onError("File does not exist: " + filePath);
            }
@@ -186,7 +186,7 @@
        });
        transmitThread.start();
        
        Log.d(TAG, "Started transmitting file: " + filePath);
        Timber.d("Started transmitting file: %s", filePath);
    }
    
    /**
@@ -214,10 +214,10 @@
            }
            
            if (totalRead != fileData.length) {
                Log.w(TAG, "File read incomplete, expected: " + fileData.length + ", actual: " + totalRead);
                Timber.w("File read incomplete, expected: %d, actual: %d", fileData.length, totalRead);
            }
            
            Log.d(TAG, "File read complete, size: " + fileData.length + " bytes");
            Timber.d("File read complete, size: %d bytes", fileData.length);
            
            // 按帧解析并传输(一个帧包含从一个起始码到下一个起始码之间的所有数据,包括起始码)
            int offset = 0;
@@ -255,19 +255,19 @@
                offset = nextFrameStart;
            }
            
            Log.d(TAG, "Transmission complete, total frames: " + frameCount);
            Timber.d("Transmission complete, total frames: %d", frameCount);
            
            if (progressCallback != null) {
                progressCallback.onComplete();
            }
            
        } catch (IOException e) {
            Log.e(TAG, "Error transmitting file", e);
            Timber.e(e, "Error transmitting file");
            if (progressCallback != null) {
                progressCallback.onError("IO Error: " + e.getMessage());
            }
        } catch (Exception e) {
            Log.e(TAG, "Unexpected error during transmission", e);
            Timber.e(e, "Unexpected error during transmission");
            if (progressCallback != null) {
                progressCallback.onError("Error: " + e.getMessage());
            }
@@ -276,7 +276,7 @@
                try {
                    fis.close();
                } catch (IOException e) {
                    Log.e(TAG, "Error closing file", e);
                    Timber.e(e, "Error closing file");
                }
            }
            isRunning.set(false);
@@ -464,13 +464,13 @@
                    Thread.sleep(frameInterval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    Log.d(TAG, "Transmission interrupted");
                    Timber.d("Transmission interrupted");
                    return;
                }
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error transmitting frame", e);
            Timber.e(e, "Error transmitting frame");
        }
    }
    
@@ -489,7 +489,7 @@
            try {
                transmitThread.join(2000);
            } catch (InterruptedException e) {
                Log.e(TAG, "Wait transmit thread error", e);
                Timber.e(e, "Wait transmit thread error");
            }
        }
        
@@ -498,7 +498,7 @@
            protocolHelper.closeSocket();
        }
        
        Log.d(TAG, "H264 file transmitter stopped");
        Timber.d("H264 file transmitter stopped");
    }
    
    /**
app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java
@@ -1,12 +1,13 @@
package com.anyun.h264;
import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import timber.log.Timber;
/**
 * JT/T 1076-2016 协议工具类
@@ -78,7 +79,7 @@
     */
    public void setProtocolType(int protocolType) {
        if (protocolType != PROTOCOL_TYPE_UDP && protocolType != PROTOCOL_TYPE_TCP) {
            Log.w(TAG, "Invalid protocol type: " + protocolType + ", using UDP");
            Timber.w("Invalid protocol type: " + protocolType + ", using UDP");
            protocolType = PROTOCOL_TYPE_UDP;
        }
        
@@ -92,7 +93,7 @@
        }
        
        this.protocolType = protocolType;
        Log.d(TAG, "Protocol type set to: " + (protocolType == PROTOCOL_TYPE_UDP ? "UDP" : "TCP"));
        Timber.d("Protocol type set to: " + (protocolType == PROTOCOL_TYPE_UDP ? "UDP" : "TCP"));
    }
    
    /**
@@ -127,16 +128,16 @@
    public boolean initializeUdpSocket() {
        try {
            if (serverIp == null || serverIp.isEmpty()) {
                Log.e(TAG, "Server IP not set");
                Timber.e("Server IP not set");
                return false;
            }
            
            udpSocket = new DatagramSocket();
            serverAddress = InetAddress.getByName(serverIp);
            Log.d(TAG, "UDP socket initialized, target: " + serverIp + ":" + serverPort);
            Timber.d("UDP socket initialized, target: " + serverIp + ":" + serverPort);
            return true;
        } catch (Exception e) {
            Log.e(TAG, "Initialize UDP socket failed", e);
            Timber.e(e,"Initialize UDP socket failed");
            return false;
        }
    }
@@ -147,7 +148,7 @@
    public boolean initializeTcpSocket() {
        try {
            if (serverIp == null || serverIp.isEmpty()) {
                Log.e(TAG, "Server IP not set");
                Timber.e("Server IP not set");
                return false;
            }
            
@@ -159,26 +160,26 @@
                tcpClient.setConnectionListener(new JT1076TcpClient.ConnectionListener() {
                    @Override
                    public void onConnected() {
                        Log.d(TAG, "TCP connection established");
                        Timber.d("TCP connection established");
                    }
                    
                    @Override
                    public void onDisconnected() {
                        Log.d(TAG, "TCP connection disconnected");
                        Timber.d( "TCP connection disconnected");
                    }
                    
                    @Override
                    public void onError(Throwable cause) {
                        Log.e(TAG, "TCP connection error", cause);
                        Timber.e(cause, "TCP connection error");
                    }
                });
            }
            
            tcpClient.connect();
            Log.d(TAG, "TCP socket initializing, target: " + serverIp + ":" + serverPort);
            Timber.d("TCP socket initializing, target: " + serverIp + ":" + serverPort);
            return true;
        } catch (Exception e) {
            Log.e(TAG, "Initialize TCP socket failed", e);
            Timber.e(e,"Initialize TCP socket failed");
            return false;
        }
    }
@@ -202,7 +203,7 @@
            try {
                udpSocket.close();
            } catch (Exception e) {
                Log.e(TAG, "Close UDP socket error", e);
                Timber.e( e,"Close UDP socket error");
            }
            udpSocket = null;
        }
@@ -240,10 +241,10 @@
                    packet, packet.length, serverAddress, serverPort);
                udpSocket.send(datagramPacket);
            } else {
                Log.w(TAG, "UDP socket not initialized");
                Timber.w("UDP socket not initialized");
            }
        } catch (Exception e) {
            Log.e(TAG, "Send UDP packet error", e);
            Timber.e(e,"Send UDP packet error");
        }
    }
    
@@ -254,7 +255,7 @@
        if (tcpClient != null && tcpClient.isConnected()) {
            tcpClient.sendPacket(packet);
        } else {
            Log.w(TAG, "TCP socket not connected");
            Timber.w("TCP socket not connected");
        }
    }
    
app/src/main/java/com/anyun/h264/MainActivity.kt
@@ -1,7 +1,7 @@
package com.anyun.h264
import android.os.Bundle
import android.util.Log
import timber.log.Timber
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@@ -52,7 +52,7 @@
    
    private fun startH264Encoder(): Boolean {
        if (h264Encoder != null) {
            Log.w("MainActivity", "H264Encoder is already running")
            Timber.w("H264Encoder is already running")
            return false
        }
        
@@ -80,16 +80,16 @@
            
            if (h264Encoder?.initialize(cameraIdRange, null, resolution, false) == true) {
                h264Encoder?.start()
                Log.d("MainActivity", "H264Encoder started successfully")
                Log.d("MainActivity", "Output file: ${outputFile.absolutePath}")
                Timber.d("H264Encoder started successfully")
                Timber.d("Output file: %s", outputFile.absolutePath)
                return true
            } else {
                Log.e("MainActivity", "Failed to initialize H264Encoder")
                Timber.e("Failed to initialize H264Encoder")
                h264Encoder = null
                return false
            }
        } catch (e: Exception) {
            Log.e("MainActivity", "Failed to start H264Encoder", e)
            Timber.e(e, "Failed to start H264Encoder")
            h264Encoder = null
            return false
        }
@@ -99,9 +99,9 @@
        h264Encoder?.let { encoder ->
            try {
                encoder.stop()
                Log.d("MainActivity", "H264Encoder stopped")
                Timber.d("H264Encoder stopped")
            } catch (e: Exception) {
                Log.e("MainActivity", "Failed to stop H264Encoder", e)
                Timber.e(e, "Failed to stop H264Encoder")
            }
            h264Encoder = null
        }
gradle/libs.versions.toml
@@ -9,6 +9,7 @@
activityCompose = "1.8.0"
composeBom = "2024.04.01"
netty = "4.1.48.Final"
timber = "4.7.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -26,6 +27,7 @@
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
netty-all = { group = "io.netty", name = "netty-all", version.ref = "netty" }
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }