Dana
2025-12-02 8bc72ebc21e04e3d52f7250716c44d3104bfecfa
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; // 上一个I帧时间
    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);
                // 发送RTP包(UDP或TCP,根据协议类型自动选择)
                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");
    }
    
    /**
     * 解析帧中的NAL单元并根据类型处理
     */
    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) {
            // 没有解析出NAL单元时,直接按P帧发送
            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() {