| | |
| | | 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; // 上一帧时间 |
| | |
| | | baseTimestamp = System.currentTimeMillis(); |
| | | lastIFrameTime = 0; |
| | | lastFrameTime = 0; |
| | | spsBuffer = null; |
| | | ppsBuffer = null; |
| | | |
| | | Timber.d("Socket initialized successfully"); |
| | | return true; |
| | |
| | | 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) { |
| | |
| | | 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() { |