From 8bc72ebc21e04e3d52f7250716c44d3104bfecfa Mon Sep 17 00:00:00 2001
From: Dana <Dana_Lee1016@126.com>
Date: 星期二, 02 十二月 2025 15:28:10 +0800
Subject: [PATCH] 1.修改文件推流

---
 app/src/main/java/com/anyun/h264/MainActivity.kt           |   87 +++++++++++++----
 app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java |    1 
 app/src/main/java/com/anyun/h264/H264FileTransmitter.java  |  187 +++++++++++++++++++++++++++++++------
 3 files changed, 223 insertions(+), 52 deletions(-)

diff --git a/app/src/main/java/com/anyun/h264/H264FileTransmitter.java b/app/src/main/java/com/anyun/h264/H264FileTransmitter.java
index 6f76ca1..7e77d79 100644
--- a/app/src/main/java/com/anyun/h264/H264FileTransmitter.java
+++ b/app/src/main/java/com/anyun/h264/H264FileTransmitter.java
@@ -55,6 +55,10 @@
     private int frameRate = 25; // 甯х巼锛岀敤浜庤绠楁椂闂存埑闂撮殧
     private long frameInterval = 1000 / 25; // 甯ч棿闅旓紙姣锛�
     
+    // SPS/PPS缂撳瓨
+    private byte[] spsBuffer;
+    private byte[] ppsBuffer;
+    
     // 鏃堕棿鎴崇鐞�
     private long lastIFrameTime = 0; // 涓婁竴涓狪甯ф椂闂�
     private long lastFrameTime = 0; // 涓婁竴甯ф椂闂�
@@ -151,6 +155,8 @@
         baseTimestamp = System.currentTimeMillis();
         lastIFrameTime = 0;
         lastFrameTime = 0;
+        spsBuffer = null;
+        ppsBuffer = null;
         
         Timber.d("Socket initialized successfully");
         return true;
@@ -427,36 +433,7 @@
             int dataType = isKeyFrame ? JT1076ProtocolHelper.DATA_TYPE_I_FRAME :
                     JT1076ProtocolHelper.DATA_TYPE_P_FRAME;
             
-            // 鍒嗗寘鍙戦�侊紙濡傛灉鏁版嵁瓒呰繃鏈�澶у寘澶у皬锛�
-            int offset = 0;
-            int totalPackets = (int) Math.ceil((double) frameData.length / JT1076ProtocolHelper.MAX_PACKET_SIZE);
-            
-            for (int i = 0; i < totalPackets; i++) {
-                int packetDataSize = Math.min(JT1076ProtocolHelper.MAX_PACKET_SIZE, frameData.length - offset);
-                byte[] packetData = Arrays.copyOfRange(frameData, offset, offset + packetDataSize);
-                
-                // 纭畾鍒嗗寘鏍囪
-                int packetMark;
-                if (totalPackets == 1) {
-                    packetMark = JT1076ProtocolHelper.PACKET_MARK_ATOMIC;
-                } else if (i == 0) {
-                    packetMark = JT1076ProtocolHelper.PACKET_MARK_FIRST;
-                } else if (i == totalPackets - 1) {
-                    packetMark = JT1076ProtocolHelper.PACKET_MARK_LAST;
-                } else {
-                    packetMark = JT1076ProtocolHelper.PACKET_MARK_MIDDLE;
-                }
-                
-                // 鍒涘缓RTP鍖�
-                byte[] rtpPacket = protocolHelper.createVideoRtpPacket(
-                        packetData, timestamp, dataType, packetMark,
-                        lastIFrameInterval, lastFrameInterval);
-                
-                // 鍙戦�丷TP鍖咃紙UDP鎴朤CP锛屾牴鎹崗璁被鍨嬭嚜鍔ㄩ�夋嫨锛�
-                protocolHelper.sendPacket(rtpPacket);
-                
-                offset += packetDataSize;
-            }
+            processNalUnits(frameData, timestamp, lastIFrameInterval, lastFrameInterval);
             
             // 鎺у埗鍙戦�侀�熺巼锛堟ā鎷熷抚鐜囷級
             if (frameInterval > 0) {
@@ -498,10 +475,160 @@
             protocolHelper.closeSocket();
         }
         
+        spsBuffer = null;
+        ppsBuffer = null;
+        
         Timber.d("H264 file transmitter stopped");
     }
     
     /**
+     * 瑙f瀽甯т腑鐨凬AL鍗曞厓骞舵牴鎹被鍨嬪鐞�
+     */
+    private void processNalUnits(byte[] frameData, long timestamp,
+                                 long lastIFrameInterval, long lastFrameInterval) {
+        if (frameData == null || frameData.length == 0) {
+            return;
+        }
+        
+        boolean nalProcessed = false;
+        int offset = 0;
+        while (offset < frameData.length) {
+            int startCodePos = findStartCode(frameData, offset);
+            if (startCodePos < 0) {
+                break;
+            }
+            
+            int startCodeLen = getStartCodeLength(frameData, startCodePos);
+            int nalDataStart = startCodePos + startCodeLen;
+            if (nalDataStart >= frameData.length) {
+                break;
+            }
+            
+            int nextStart = findStartCode(frameData, nalDataStart);
+            int nalEnd = (nextStart == -1) ? frameData.length : nextStart;
+            int nalLength = nalEnd - startCodePos;
+            if (nalLength <= startCodeLen) {
+                break;
+            }
+            
+            byte[] nalUnit = Arrays.copyOfRange(frameData, startCodePos, nalEnd);
+            int nalType = nalUnit[startCodeLen] & 0x1F;
+            
+            handleNalUnit(nalUnit, nalType, timestamp, lastIFrameInterval, lastFrameInterval);
+            nalProcessed = true;
+            offset = nalEnd;
+        }
+        
+        if (!nalProcessed) {
+            // 娌℃湁瑙f瀽鍑篘AL鍗曞厓鏃讹紝鐩存帴鎸塒甯у彂閫�
+            sendFramePayload(frameData, timestamp, false, lastIFrameInterval, lastFrameInterval);
+        }
+    }
+    
+    private void handleNalUnit(byte[] nalUnit, int nalType, long timestamp,
+                               long lastIFrameInterval, long lastFrameInterval) {
+        switch (nalType) {
+            case 7: // SPS
+                spsBuffer = nalUnit.clone();
+                Timber.d("Cached SPS, size: %d", spsBuffer.length);
+                break;
+            case 8: // PPS
+                ppsBuffer = nalUnit.clone();
+                Timber.d("Cached PPS, size: %d", ppsBuffer.length);
+                break;
+            case 5: // IDR
+                if (spsBuffer != null && ppsBuffer != null) {
+                    byte[] combined = combineFrameData(spsBuffer, ppsBuffer, nalUnit);
+                    sendFramePayload(combined, timestamp, true, lastIFrameInterval, lastFrameInterval);
+                } else {
+                    Timber.w("IDR frame without SPS/PPS cache, sending raw IDR");
+                    sendFramePayload(nalUnit, timestamp, true, lastIFrameInterval, lastFrameInterval);
+                }
+                break;
+            case 1: // P frame
+                sendFramePayload(nalUnit, timestamp, false, lastIFrameInterval, lastFrameInterval);
+                break;
+            default:
+                Timber.d("Forwarding NAL type %d, size: %d", nalType, nalUnit.length);
+                sendFramePayload(nalUnit, timestamp, false, lastIFrameInterval, lastFrameInterval);
+                break;
+        }
+    }
+    
+    private void sendFramePayload(byte[] payload, long timestamp, boolean isKeyFrame,
+                                  long lastIFrameInterval, long lastFrameInterval) {
+        if (payload == null || payload.length == 0) {
+            return;
+        }
+        
+        int dataType = isKeyFrame ? JT1076ProtocolHelper.DATA_TYPE_I_FRAME :
+                JT1076ProtocolHelper.DATA_TYPE_P_FRAME;
+        
+        int offset = 0;
+        int totalPackets = (int) Math.ceil((double) payload.length / JT1076ProtocolHelper.MAX_PACKET_SIZE);
+        for (int i = 0; i < totalPackets; i++) {
+            int packetDataSize = Math.min(JT1076ProtocolHelper.MAX_PACKET_SIZE, payload.length - offset);
+            byte[] packetData = Arrays.copyOfRange(payload, offset, offset + packetDataSize);
+            
+            int packetMark;
+            if (totalPackets == 1) {
+                packetMark = JT1076ProtocolHelper.PACKET_MARK_ATOMIC;
+            } else if (i == 0) {
+                packetMark = JT1076ProtocolHelper.PACKET_MARK_FIRST;
+            } else if (i == totalPackets - 1) {
+                packetMark = JT1076ProtocolHelper.PACKET_MARK_LAST;
+            } else {
+                packetMark = JT1076ProtocolHelper.PACKET_MARK_MIDDLE;
+            }
+            
+            byte[] rtpPacket = protocolHelper.createVideoRtpPacket(
+                    packetData, timestamp, dataType, packetMark,
+                    lastIFrameInterval, lastFrameInterval);
+            protocolHelper.sendPacket(rtpPacket);
+            offset += packetDataSize;
+        }
+    }
+    
+    private int findStartCode(byte[] data, int startPos) {
+        if (data == null || startPos >= data.length - 3) {
+            return -1;
+        }
+        for (int i = startPos; i < data.length - 3; i++) {
+            if (isStartCodeAt(data, i)) {
+                return i;
+            }
+        }
+        // 澶勭悊鍓╀綑涓嶈冻4瀛楄妭浣嗗彲鑳芥槸3瀛楄妭璧峰鐮佺殑鎯呭喌
+        for (int i = Math.max(startPos, data.length - 3); i < data.length - 2; i++) {
+            if (isStartCodeAt(data, i)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    
+    private byte[] combineFrameData(byte[] sps, byte[] pps, byte[] idr) {
+        int totalLength = (sps != null ? sps.length : 0) +
+                (pps != null ? pps.length : 0) +
+                (idr != null ? idr.length : 0);
+        byte[] combined = new byte[totalLength];
+        int offset = 0;
+        if (sps != null) {
+            System.arraycopy(sps, 0, combined, offset, sps.length);
+            offset += sps.length;
+        }
+        if (pps != null) {
+            System.arraycopy(pps, 0, combined, offset, pps.length);
+            offset += pps.length;
+        }
+        if (idr != null) {
+            System.arraycopy(idr, 0, combined, offset, idr.length);
+        }
+        Timber.d("Combined SPS/PPS/IDR payload, total: %d", totalLength);
+        return combined;
+    }
+    
+    /**
      * 閲婃斁璧勬簮
      */
     public void release() {
diff --git a/app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java b/app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java
index 6e53e5d..c438f89 100644
--- a/app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java
+++ b/app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java
@@ -67,6 +67,7 @@
     public void setServerAddress(String ip, int port) {
         this.serverIp = ip;
         this.serverPort = port;
+        Timber.w("璁剧疆IP="+ip+"_port:"+port);
         // 濡傛灉TCP瀹㈡埛绔凡瀛樺湪锛屾洿鏂板湴鍧�
         if (tcpClient != null) {
             tcpClient.setServerAddress(ip, port);
diff --git a/app/src/main/java/com/anyun/h264/MainActivity.kt b/app/src/main/java/com/anyun/h264/MainActivity.kt
index 143808e..fb80784 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 timber.log.Timber
+import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.activity.enableEdgeToEdge
@@ -12,25 +12,29 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import com.anyun.h264.ui.theme.MyApplicationTheme
+import timber.log.Timber
 import java.io.File
 
 class MainActivity : ComponentActivity() {
     private var h264Encoder: H264Encoder? = null
-    
+    private var transmitter: H264FileTransmitter? = null
+    companion object{
+        const val TAG ="MainActivity"
+    }
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         enableEdgeToEdge()
-        
+
         setContent {
             var isRunning by remember { mutableStateOf(false) }
-            
+
             MyApplicationTheme {
                 Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                     MainScreen(
                         modifier = Modifier.padding(innerPadding),
                         isRunning = isRunning,
                         onStartH264Click = {
-                            val success = startH264Encoder()
+                            val success = startFileTransmitter()
                             if (success) {
                                 isRunning = true
                             }
@@ -44,40 +48,79 @@
             }
         }
     }
-    
+
     override fun onDestroy() {
         super.onDestroy()
         stopH264Encoder()
     }
-    
+
+    private fun startFileTransmitter():Boolean {
+
+        if (transmitter != null) {
+            Timber.w("H264Encoder is already running")
+            return false
+        }
+
+
+
+        try {
+            transmitter = H264FileTransmitter()
+            Log.i(TAG,"startFileTransmitter")
+            transmitter?.setServerAddress("192.168.16.138", 1078)
+            transmitter?.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP) // 鎴� PROTOCOL_TYPE_UDP
+
+
+            // 璁剧疆鍗忚鍙傛暟
+            transmitter?.setProtocolParams("013120122580", 1.toByte())
+
+
+            // 璁剧疆甯х巼锛堢敤浜庤绠楁椂闂存埑闂撮殧锛�
+            transmitter?.setFrameRate(25)
+
+
+            // 鍒濆鍖朣ocket
+            if (transmitter?.initialize()==true) {
+                // 寮�濮嬩紶杈撴枃浠�
+                transmitter?.transmitFile("/storage/emulated/0/Android/data/com.anyun.h264/files/h264_1764574451071.h264")
+                return true
+            }else{
+                return false
+            }
+        } catch (e: Exception) {
+            Timber.e(e, "Failed to start H264Encoder")
+            transmitter = null
+            return false
+        }
+    }
+
     private fun startH264Encoder(): Boolean {
         if (h264Encoder != null) {
             Timber.w("H264Encoder is already running")
             return false
         }
-        
+
         try {
             // 鍒涘缓缂栫爜鍣�
             h264Encoder = H264Encoder()
-            
+
             // 璁剧疆缂栫爜鍙傛暟
             h264Encoder?.setEncoderParams(640, 480, 25, 2000000)
-            
+
             // 璁剧疆杈撳嚭鏂囦欢锛堝彲閫夛級
             val outputFile = File(getExternalFilesDir(null), "test2.h264")
             h264Encoder?.setOutputFile(outputFile.absolutePath)
             h264Encoder?.setEnableFileOutput(false) // 鍚敤鏂囦欢杈撳嚭
-            
+
             // 璁剧疆UDP鏈嶅姟鍣ㄥ湴鍧�锛堝彲閫夛級
 //             h264Encoder?.setServerAddress("58.48.93.67", 11935)
             h264Encoder?.setEnableNetworkTransmission(true)
-             h264Encoder?.setServerAddress("192.168.16.138", 1078)
-             h264Encoder?.setProtocolParams("013120122580", 1)
-            
+            h264Encoder?.setServerAddress("192.168.16.138", 1078)
+            h264Encoder?.setProtocolParams("013120122580", 1)
+
             // 鍒濆鍖栧苟鍚姩
             val cameraIdRange = intArrayOf(1, 2)
             val resolution = intArrayOf(640, 480)
-            
+
             if (h264Encoder?.initialize(cameraIdRange, null, resolution, false) == true) {
                 h264Encoder?.start()
                 Timber.d("H264Encoder started successfully")
@@ -94,7 +137,7 @@
             return false
         }
     }
-    
+
     private fun stopH264Encoder() {
         h264Encoder?.let { encoder ->
             try {
@@ -126,9 +169,9 @@
             text = "H264 缂栫爜鍣�",
             style = MaterialTheme.typography.headlineMedium
         )
-        
+
         Spacer(modifier = Modifier.height(32.dp))
-        
+
         Button(
             onClick = onStartH264Click,
             enabled = !isRunning,
@@ -138,9 +181,9 @@
         ) {
             Text("鍚姩 H264")
         }
-        
+
         Spacer(modifier = Modifier.height(16.dp))
-        
+
         Button(
             onClick = onStopH264Click,
             enabled = isRunning,
@@ -153,9 +196,9 @@
         ) {
             Text("鍋滄 H264")
         }
-        
+
         Spacer(modifier = Modifier.height(32.dp))
-        
+
         if (isRunning) {
             Text(
                 text = "缂栫爜鍣ㄨ繍琛屼腑...",

--
Gitblit v1.8.0