From ccf7a1f19b5c6276178c72609c88d057214c8239 Mon Sep 17 00:00:00 2001
From: Dana <Dana_Lee1016@126.com>
Date: 星期一, 01 十二月 2025 09:51:50 +0800
Subject: [PATCH] 1.添加timber日志库

---
 app/src/main/java/com/anyun/h264/H264EncodeService.java    |  115 ++++++++--------
 app/src/main/java/com/anyun/h264/H264Application.java      |   27 +++
 app/src/main/java/com/anyun/h264/MainActivity.kt           |   16 +-
 app/src/main/AndroidManifest.xml                           |    5 
 app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java |   33 ++--
 app/src/main/java/com/anyun/h264/H264FileTransmitter.java  |   36 ++--
 gradle/libs.versions.toml                                  |    2 
 app/build.gradle                                           |    1 
 app/src/main/java/com/anyun/h264/FileLoggingTree.java      |  146 ++++++++++++++++++++
 9 files changed, 281 insertions(+), 100 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index b971839..9fa237e 100644
--- a/app/build.gradle
+++ b/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
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9254442..58a629f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/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"
diff --git a/app/src/main/java/com/anyun/h264/FileLoggingTree.java b/app/src/main/java/com/anyun/h264/FileLoggingTree.java
new file mode 100644
index 0000000..d133697
--- /dev/null
+++ b/app/src/main/java/com/anyun/h264/FileLoggingTree.java
@@ -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 "?";
+        }
+    }
+}
+
diff --git a/app/src/main/java/com/anyun/h264/H264Application.java b/app/src/main/java/com/anyun/h264/H264Application.java
new file mode 100644
index 0000000..6c1ba4b
--- /dev/null
+++ b/app/src/main/java/com/anyun/h264/H264Application.java
@@ -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");
+    }
+}
+
diff --git a/app/src/main/java/com/anyun/h264/H264EncodeService.java b/app/src/main/java/com/anyun/h264/H264EncodeService.java
index a2ec246..e1f5ad0 100644
--- a/app/src/main/java/com/anyun/h264/H264EncodeService.java
+++ b/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);
                 }
             });
             
             // 鍒濆鍖朣ocket杩炴帴
             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;
         }
     }
diff --git a/app/src/main/java/com/anyun/h264/H264FileTransmitter.java b/app/src/main/java/com/anyun/h264/H264FileTransmitter.java
index 51299d0..af9919f 100644
--- a/app/src/main/java/com/anyun/h264/H264FileTransmitter.java
+++ b/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);
             
             // 鎸夊抚瑙f瀽骞朵紶杈擄紙涓�涓抚鍖呭惈浠庝竴涓捣濮嬬爜鍒颁笅涓�涓捣濮嬬爜涔嬮棿鐨勬墍鏈夋暟鎹紝鍖呮嫭璧峰鐮侊級
             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");
     }
     
     /**
diff --git a/app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java b/app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java
index 1489907..71af779 100644
--- a/app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java
+++ b/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");
         }
     }
     
diff --git a/app/src/main/java/com/anyun/h264/MainActivity.kt b/app/src/main/java/com/anyun/h264/MainActivity.kt
index f70360f..4fc1e0d 100644
--- a/app/src/main/java/com/anyun/h264/MainActivity.kt
+++ b/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
         }
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ed71bad..07b697f 100644
--- a/gradle/libs.versions.toml
+++ b/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" }

--
Gitblit v1.8.0