From 0ff46cc3489bf028802c9ef46f54bf2b77c23fb4 Mon Sep 17 00:00:00 2001
From: Dana <Dana_Lee1016@126.com>
Date: 星期日, 30 十一月 2025 16:22:32 +0800
Subject: [PATCH] 1.h264文件以rtp sendPacket
---
app/src/main/java/com/anyun/h264/H264FileTransmitter.java | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 511 insertions(+), 0 deletions(-)
diff --git a/app/src/main/java/com/anyun/h264/H264FileTransmitter.java b/app/src/main/java/com/anyun/h264/H264FileTransmitter.java
new file mode 100644
index 0000000..51299d0
--- /dev/null
+++ b/app/src/main/java/com/anyun/h264/H264FileTransmitter.java
@@ -0,0 +1,511 @@
+package com.anyun.h264;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * H264鏂囦欢浼犺緭鍣�
+ * 浠嶩264鏂囦欢璇诲彇鏁版嵁锛屾寜鐓T/T 1076-2016鍗忚閫氳繃TCP/UDP浼犺緭
+ *
+ * 浣跨敤绀轰緥锛�
+ * <pre>
+ * // 鍒涘缓浼犺緭鍣�
+ * H264FileTransmitter transmitter = new H264FileTransmitter();
+ *
+ * // 璁剧疆鏈嶅姟鍣ㄥ湴鍧�鍜屽崗璁被鍨�
+ * transmitter.setServerAddress("192.168.1.100", 8888);
+ * transmitter.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP); // 鎴� PROTOCOL_TYPE_UDP
+ *
+ * // 璁剧疆鍗忚鍙傛暟
+ * transmitter.setProtocolParams("013120122580", (byte)1);
+ *
+ * // 璁剧疆甯х巼锛堢敤浜庤绠楁椂闂存埑闂撮殧锛�
+ * transmitter.setFrameRate(25);
+ *
+ * // 鍒濆鍖朣ocket
+ * if (transmitter.initialize()) {
+ * // 寮�濮嬩紶杈撴枃浠�
+ * transmitter.transmitFile("/path/to/video.h264");
+ * }
+ *
+ * // 鍋滄浼犺緭
+ * transmitter.stop();
+ * </pre>
+ */
+public class H264FileTransmitter {
+ private static final String TAG = "H264FileTransmitter";
+
+ // H264 NAL璧峰鐮�
+ private static final byte[] START_CODE_3 = {0x00, 0x00, 0x01};
+ private static final byte[] START_CODE_4 = {0x00, 0x00, 0x00, 0x01};
+
+ // JT/T 1076-2016 鍗忚宸ュ叿绫�
+ private JT1076ProtocolHelper protocolHelper;
+
+ // 浼犺緭鎺у埗
+ private AtomicBoolean isRunning = new AtomicBoolean(false);
+ private Thread transmitThread;
+
+ // 鍙傛暟閰嶇疆
+ private int frameRate = 25; // 甯х巼锛岀敤浜庤绠楁椂闂存埑闂撮殧
+ private long frameInterval = 1000 / 25; // 甯ч棿闅旓紙姣锛�
+
+ // 鏃堕棿鎴崇鐞�
+ private long lastIFrameTime = 0; // 涓婁竴涓狪甯ф椂闂�
+ private long lastFrameTime = 0; // 涓婁竴甯ф椂闂�
+ private long baseTimestamp = 0; // 鍩哄噯鏃堕棿鎴�
+
+ /**
+ * 浼犺緭杩涘害鍥炶皟鎺ュ彛
+ */
+ public interface OnTransmitProgressCallback {
+ /**
+ * 浼犺緭杩涘害鍥炶皟
+ * @param currentFrame 褰撳墠甯у簭鍙凤紙浠�1寮�濮嬶級
+ * @param totalFrames 鎬诲抚鏁帮紙濡傛灉鏈煡鍒欎负-1锛�
+ */
+ void onProgress(int currentFrame, int totalFrames);
+
+ /**
+ * 浼犺緭瀹屾垚鍥炶皟
+ */
+ void onComplete();
+
+ /**
+ * 浼犺緭閿欒鍥炶皟
+ * @param error 閿欒淇℃伅
+ */
+ void onError(String error);
+ }
+ private OnTransmitProgressCallback progressCallback;
+
+ public H264FileTransmitter() {
+ this.protocolHelper = new JT1076ProtocolHelper();
+ // 榛樿浣跨敤TCP鍗忚
+ protocolHelper.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP);
+ }
+
+ /**
+ * 璁剧疆鏈嶅姟鍣ㄥ湴鍧�
+ */
+ public void setServerAddress(String ip, int port) {
+ protocolHelper.setServerAddress(ip, port);
+ }
+
+ /**
+ * 璁剧疆浼犺緭鍗忚绫诲瀷锛圲DP鎴朤CP锛�
+ * @param protocolType PROTOCOL_TYPE_UDP 鎴� PROTOCOL_TYPE_TCP
+ */
+ public void setProtocolType(int protocolType) {
+ protocolHelper.setProtocolType(protocolType);
+ }
+
+ /**
+ * 璁剧疆SIM鍗″彿鍜岄�昏緫閫氶亾鍙�
+ */
+ public void setProtocolParams(String simCardNumber, byte logicalChannelNumber) {
+ protocolHelper.setProtocolParams(simCardNumber, logicalChannelNumber);
+ }
+
+ /**
+ * 璁剧疆甯х巼锛堢敤浜庤绠楁椂闂存埑闂撮殧锛�
+ * @param frameRate 甯х巼锛坒ps锛�
+ */
+ 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");
+ }
+
+ /**
+ * 璁剧疆浼犺緭杩涘害鍥炶皟
+ */
+ public void setOnTransmitProgressCallback(OnTransmitProgressCallback callback) {
+ this.progressCallback = callback;
+ }
+
+ /**
+ * 鍒濆鍖朣ocket杩炴帴
+ * @return 鏄惁鎴愬姛
+ */
+ public boolean initialize() {
+ if (isRunning.get()) {
+ Log.w(TAG, "Transmitter is already running");
+ return false;
+ }
+
+ if (!protocolHelper.initializeSocket()) {
+ Log.e(TAG, "Failed to initialize socket");
+ return false;
+ }
+
+ // 閲嶇疆搴忓彿
+ protocolHelper.resetSequenceNumber();
+
+ // 鍒濆鍖栨椂闂存埑
+ baseTimestamp = System.currentTimeMillis();
+ lastIFrameTime = 0;
+ lastFrameTime = 0;
+
+ Log.d(TAG, "Socket initialized successfully");
+ return true;
+ }
+
+ /**
+ * 寮�濮嬩紶杈揌264鏂囦欢
+ * @param filePath H264鏂囦欢璺緞
+ */
+ public void transmitFile(String filePath) {
+ if (isRunning.get()) {
+ Log.w(TAG, "Transmitter is already running");
+ return;
+ }
+
+ File file = new File(filePath);
+ if (!file.exists() || !file.isFile()) {
+ Log.e(TAG, "File does not exist: " + filePath);
+ if (progressCallback != null) {
+ progressCallback.onError("File does not exist: " + filePath);
+ }
+ return;
+ }
+
+ isRunning.set(true);
+
+ // 鍚姩浼犺緭绾跨▼
+ transmitThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ transmitFileInternal(file);
+ }
+ });
+ transmitThread.start();
+
+ Log.d(TAG, "Started transmitting file: " + filePath);
+ }
+
+ /**
+ * 浼犺緭鏂囦欢鐨勫唴閮ㄥ疄鐜�
+ */
+ private void transmitFileInternal(File file) {
+ FileInputStream fis = null;
+ int frameCount = 0;
+
+ try {
+ fis = new FileInputStream(file);
+
+ // 璇诲彇鏁翠釜鏂囦欢鍒板唴瀛�
+ // 娉ㄦ剰锛氬浜庡ぇ鏂囦欢锛屽彲浠ユ敼涓烘祦寮忚鍙栵紝浣嗕负绠�鍖栧疄鐜帮紝杩欓噷浣跨敤涓�娆℃�ц鍙�
+ long fileSize = file.length();
+ if (fileSize > Integer.MAX_VALUE) {
+ throw new IOException("File too large: " + fileSize + " bytes");
+ }
+
+ byte[] fileData = new byte[(int) fileSize];
+ int bytesRead = 0;
+ int totalRead = 0;
+ while (totalRead < fileData.length && (bytesRead = fis.read(fileData, totalRead, fileData.length - totalRead)) > 0) {
+ totalRead += bytesRead;
+ }
+
+ if (totalRead != fileData.length) {
+ Log.w(TAG, "File read incomplete, expected: " + fileData.length + ", actual: " + totalRead);
+ }
+
+ Log.d(TAG, "File read complete, size: " + fileData.length + " bytes");
+
+ // 鎸夊抚瑙f瀽骞朵紶杈擄紙涓�涓抚鍖呭惈浠庝竴涓捣濮嬬爜鍒颁笅涓�涓捣濮嬬爜涔嬮棿鐨勬墍鏈夋暟鎹紝鍖呮嫭璧峰鐮侊級
+ int offset = 0;
+ while (offset < fileData.length && isRunning.get()) {
+ // 鏌ユ壘涓嬩竴涓抚鐨勮捣濮嬩綅缃�
+ int nextFrameStart = findNextFrameStart(fileData, offset);
+ if (nextFrameStart < 0) {
+ // 娌℃湁鎵惧埌涓嬩竴涓捣濮嬬爜锛屽綋鍓峯ffset鍒版枃浠舵湯灏炬槸涓�涓畬鏁寸殑甯�
+ if (offset < fileData.length) {
+ byte[] frameData = Arrays.copyOfRange(fileData, offset, fileData.length);
+ transmitFrame(frameData, frameCount);
+ frameCount++;
+
+ // 閫氱煡杩涘害
+ if (progressCallback != null) {
+ progressCallback.onProgress(frameCount, -1);
+ }
+ }
+ break;
+ }
+
+ // 鎻愬彇褰撳墠甯ф暟鎹紙鍖呭惈璧峰鐮侊級
+ if (nextFrameStart > offset) {
+ byte[] frameData = Arrays.copyOfRange(fileData, offset, nextFrameStart);
+ transmitFrame(frameData, frameCount);
+ frameCount++;
+
+ // 閫氱煡杩涘害
+ if (progressCallback != null) {
+ progressCallback.onProgress(frameCount, -1);
+ }
+ }
+
+ // 绉诲姩鍒颁笅涓�涓抚鐨勮捣濮嬩綅缃�
+ offset = nextFrameStart;
+ }
+
+ Log.d(TAG, "Transmission complete, total frames: " + frameCount);
+
+ if (progressCallback != null) {
+ progressCallback.onComplete();
+ }
+
+ } catch (IOException e) {
+ Log.e(TAG, "Error transmitting file", e);
+ if (progressCallback != null) {
+ progressCallback.onError("IO Error: " + e.getMessage());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error during transmission", e);
+ if (progressCallback != null) {
+ progressCallback.onError("Error: " + e.getMessage());
+ }
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing file", e);
+ }
+ }
+ isRunning.set(false);
+ }
+ }
+
+ /**
+ * 鏌ユ壘涓嬩竴涓抚鐨勮捣濮嬩綅缃紙涓嬩竴涓捣濮嬬爜鐨勪綅缃級
+ * @param data 鏂囦欢鏁版嵁
+ * @param currentFrameStart 褰撳墠甯х殑璧峰浣嶇疆锛堣捣濮嬬爜浣嶇疆锛�
+ * @return 涓嬩竴涓抚鐨勮捣濮嬬爜浣嶇疆锛屽鏋滄湭鎵惧埌杩斿洖-1
+ */
+ private int findNextFrameStart(byte[] data, int currentFrameStart) {
+ if (currentFrameStart >= data.length) {
+ return -1;
+ }
+
+ // 璺宠繃褰撳墠甯х殑璧峰鐮�
+ int offset = currentFrameStart;
+ if (isStartCodeAt(data, offset)) {
+ offset += getStartCodeLength(data, offset);
+ }
+
+ // 鏌ユ壘涓嬩竴涓捣濮嬬爜
+ for (int i = offset; i < data.length - 3; i++) {
+ if (isStartCodeAt(data, i)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * 妫�鏌ユ寚瀹氫綅缃槸鍚︿负璧峰鐮�
+ */
+ private boolean isStartCodeAt(byte[] data, int offset) {
+ if (offset + 3 > data.length) {
+ return false;
+ }
+
+ // 妫�鏌�4瀛楄妭璧峰鐮�
+ if (offset + 4 <= data.length) {
+ if (data[offset] == 0x00 && data[offset + 1] == 0x00 &&
+ data[offset + 2] == 0x00 && data[offset + 3] == 0x01) {
+ return true;
+ }
+ }
+
+ // 妫�鏌�3瀛楄妭璧峰鐮侊紙纭繚鍓嶉潰涓嶆槸0x00锛�
+ if (offset == 0 || data[offset - 1] != 0x00) {
+ if (data[offset] == 0x00 && data[offset + 1] == 0x00 &&
+ data[offset + 2] == 0x01) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 鑾峰彇璧峰鐮侀暱搴�
+ */
+ private int getStartCodeLength(byte[] data, int offset) {
+ if (offset + 4 <= data.length &&
+ data[offset] == 0x00 && data[offset + 1] == 0x00 &&
+ data[offset + 2] == 0x00 && data[offset + 3] == 0x01) {
+ return 4;
+ }
+ return 3;
+ }
+
+ /**
+ * 鍒ゆ柇甯ф暟鎹槸鍚︿负I甯э紙IDR锛�
+ * @param frameData 甯ф暟鎹紙鍖呭惈璧峰鐮侊級
+ * @return 鏄惁涓篒甯�
+ */
+ private boolean isIFrame(byte[] frameData) {
+ if (frameData == null || frameData.length < 5) {
+ return false;
+ }
+
+ // 鏌ユ壘甯т腑鎵�鏈夌殑NAL鍗曞厓锛屾鏌ユ槸鍚﹀寘鍚獻DR甯�
+ int offset = 0;
+ boolean hasSpsPps = false;
+
+ while (offset < frameData.length - 3) {
+ // 妫�鏌ュ綋鍓嶄綅缃槸鍚︿负璧峰鐮�
+ if (isStartCodeAt(frameData, offset)) {
+ // 璺宠繃璧峰鐮�
+ int startCodeLen = getStartCodeLength(frameData, offset);
+ int nalStart = offset + startCodeLen;
+
+ if (nalStart < frameData.length) {
+ // 鑾峰彇NAL绫诲瀷锛堢涓�涓瓧鑺傜殑浣�5浣嶏級
+ int nalType = frameData[nalStart] & 0x1F;
+
+ // NAL绫诲瀷5 = IDR (Instantaneous Decoder Refresh) 鍏抽敭甯�
+ if (nalType == 5) {
+ return true; // 鎵惧埌IDR甯э紝纭畾鏄疘甯�
+ }
+
+ // NAL绫诲瀷7 = SPS, 绫诲瀷8 = PPS
+ if (nalType == 7 || nalType == 8) {
+ hasSpsPps = true;
+ }
+ }
+
+ // 绉诲姩鍒颁笅涓�涓彲鑳界殑浣嶇疆缁х画鏌ユ壘
+ offset = nalStart + 1;
+ } else {
+ offset++;
+ }
+ }
+
+ // 濡傛灉娌℃湁鎵惧埌IDR锛屼絾鍖呭惈SPS/PPS锛屼篃璁や负鏄叧閿抚鐩稿叧鐨勬暟鎹�
+ // 锛堟湁浜涚紪鐮佸櫒鍙兘灏哠PS/PPS鍗曠嫭浣滀负涓�涓�"甯�"鍙戦�侊級
+ return hasSpsPps;
+ }
+
+ /**
+ * 浼犺緭涓�涓畬鏁寸殑甯ф暟鎹紙绫讳技H264Encoder鐨勬柟寮忥級
+ * @param frameData 甯ф暟鎹紙鍖呭惈璧峰鐮侊紝涓嶩264Encoder杈撳嚭鐨勬牸寮忎竴鑷达級
+ * @param frameIndex 甯у簭鍙�
+ */
+ private void transmitFrame(byte[] frameData, int frameIndex) {
+ if (frameData == null || frameData.length == 0) {
+ return;
+ }
+
+ try {
+ // 鍒ゆ柇甯х被鍨�
+ boolean isKeyFrame = isIFrame(frameData);
+
+ // 璁$畻鏃堕棿鎴�
+ long timestamp = baseTimestamp + (frameIndex * frameInterval);
+
+ // 璁$畻鏃堕棿闂撮殧
+ long lastIFrameInterval = (lastIFrameTime > 0) ? (timestamp - lastIFrameTime) : 0;
+ long lastFrameInterval = (lastFrameTime > 0) ? (timestamp - lastFrameTime) : frameInterval;
+
+ if (isKeyFrame) {
+ lastIFrameTime = timestamp;
+ }
+ lastFrameTime = timestamp;
+
+ // 鍒ゆ柇甯х被鍨嬶紙鐢ㄤ簬鍗忚锛�
+ 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;
+ }
+
+ // 鎺у埗鍙戦�侀�熺巼锛堟ā鎷熷抚鐜囷級
+ if (frameInterval > 0) {
+ try {
+ Thread.sleep(frameInterval);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.d(TAG, "Transmission interrupted");
+ return;
+ }
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, "Error transmitting frame", e);
+ }
+ }
+
+ /**
+ * 鍋滄浼犺緭
+ */
+ public void stop() {
+ if (!isRunning.get()) {
+ return;
+ }
+
+ isRunning.set(false);
+
+ // 绛夊緟浼犺緭绾跨▼缁撴潫
+ if (transmitThread != null) {
+ try {
+ transmitThread.join(2000);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Wait transmit thread error", e);
+ }
+ }
+
+ // 鍏抽棴Socket
+ if (protocolHelper != null) {
+ protocolHelper.closeSocket();
+ }
+
+ Log.d(TAG, "H264 file transmitter stopped");
+ }
+
+ /**
+ * 閲婃斁璧勬簮
+ */
+ public void release() {
+ stop();
+ }
+}
+
--
Gitblit v1.8.0