Dana
2025-12-02 079bfefba9bc3a96a6bd6f93eb8c26e8397e3949
app/src/main/java/com/anyun/h264/H264Encoder.java
@@ -64,9 +64,9 @@
    // 编码参数
    private int width = 640;
    private int height = 480;
    private int frameRate = 25;
    private int frameRate = 15;
    private int bitrate = 2000000; // 2Mbps
    private int iFrameInterval = 1; // I帧间隔(秒)
    private int iFrameInterval = 2; // I帧间隔(秒)
    // JT/T 1076-2016 协议工具类
    private JT1076ProtocolHelper protocolHelper;
@@ -81,6 +81,10 @@
    // 网络传输控制
    private boolean enableNetworkTransmission = true; // 是否启用TCP/UDP网络传输
    // SPS/PPS 缓存(用于网络传输)
    private byte[] spsBuffer = null; // SPS 缓存
    private byte[] ppsBuffer = null; // PPS 缓存
    // 编码回调
    public interface OnFrameEncodedCallback {
@@ -315,6 +319,10 @@
        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        encoder.start();
        // 清理 SPS/PPS 缓存
        spsBuffer = null;
        ppsBuffer = null;
        Timber.d( "H264 encoder initialized");
    }
@@ -502,22 +510,21 @@
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = encoder.getOutputBuffer(outputBufferIndex);
                if (outputBuffer != null && bufferInfo.size > 0) {
                    // 检查是否为关键帧
                    boolean isKeyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;
                    // 复制编码数据
                    byte[] encodedData = new byte[bufferInfo.size];
                    byte[] nalUnit = new byte[bufferInfo.size];
                    outputBuffer.position(bufferInfo.offset);
                    outputBuffer.get(encodedData, 0, bufferInfo.size);
                    outputBuffer.get(nalUnit, 0, bufferInfo.size);
                    // 写入文件
                    writeToFile(encodedData, isKeyFrame);
                    // 解析并处理 NAL 单元
                    processNalUnit(nalUnit, timestamp);
                    // 发送编码数据
                    sendEncodedData(encodedData, timestamp, isKeyFrame);
                    // 写入文件(保持原有逻辑)
                    boolean isKeyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;
                    writeToFile(nalUnit, isKeyFrame);
                    // 回调
                    if (callback != null) {
                        callback.onFrameEncoded(encodedData, isKeyFrame);
                        callback.onFrameEncoded(nalUnit, isKeyFrame);
                    }
                }
@@ -528,6 +535,139 @@
        } catch (Exception e) {
            Timber.e(e,"Encode frame error");
        }
    }
    /**
     * 处理 NAL 单元,识别类型并分别处理
     */
    private void processNalUnit(byte[] data, long timestamp) {
        if (data == null || data.length < 5) {
            return;
        }
        // MediaCodec 输出的数据是 Annex-B 格式,可能包含多个 NAL 单元
        // 每个 NAL 单元以 0x00000001 或 0x000001 开头
        int offset = 0;
        while (offset < data.length) {
            // 查找起始码
            int startCodePos = findStartCode(data, offset);
            if (startCodePos == -1) {
                break; // 没有找到起始码,结束
            }
            // 跳过起始码,找到 NAL 单元数据
            int nalStart = startCodePos;
            int startCodeLength = (startCodePos + 2 < data.length &&
                                   data[startCodePos] == 0x00 &&
                                   data[startCodePos + 1] == 0x00 &&
                                   data[startCodePos + 2] == 0x01) ? 3 : 4;
            int nalDataStart = nalStart + startCodeLength;
            // 查找下一个起始码,确定当前 NAL 单元的结束位置
            int nextStartCodePos = findStartCode(data, nalDataStart);
            int nalDataEnd = (nextStartCodePos == -1) ? data.length : nextStartCodePos;
            int nalDataLength = nalDataEnd - nalDataStart;
            if (nalDataLength > 0 && nalDataStart < data.length) {
                // 提取 NAL 单元数据(包含起始码)
                byte[] nalUnit = new byte[nalDataLength + startCodeLength];
                System.arraycopy(data, nalStart, nalUnit, 0, startCodeLength);
                System.arraycopy(data, nalDataStart, nalUnit, startCodeLength, nalDataLength);
                // 获取 NAL 类型(起始码后的第一个字节的低5位)
                int nalType = (nalUnit[startCodeLength] & 0x1F);
                // 根据 NAL 类型处理
                switch (nalType) {
                    case 7: // SPS
                        spsBuffer = nalUnit.clone();
                        Timber.d("SPS cached, size: %d", spsBuffer.length);
                        break;
                    case 8: // PPS
                        ppsBuffer = nalUnit.clone();
                        Timber.d("PPS cached, size: %d", ppsBuffer.length);
                        break;
                    case 5: // IDR 帧 (关键帧)
                        // 关键帧必须与 SPS、PPS 一起发送
                        if (spsBuffer != null && ppsBuffer != null) {
                            // 将 SPS, PPS, IDR 帧数据组合成一个完整的"帧数据单元"
                            byte[] frameData = combineFrameData(spsBuffer, ppsBuffer, nalUnit);
                            // 发送组合后的数据
                            sendEncodedData(frameData, timestamp, true);
                        } else {
                            Timber.w("Received IDR frame, but SPS or PPS not ready, dropping");
                            // 如果没有 SPS/PPS,仍然发送 IDR 帧(可能包含在数据中)
                            sendEncodedData(nalUnit, timestamp, true);
                        }
                        break;
                    case 1: // 非 IDR 帧 (P 帧)
                        // 直接发送 P 帧
                        sendEncodedData(nalUnit, timestamp, false);
                        break;
                    default:
                        // 其他 NALU 类型(如 SEI 等),根据协议决定是否处理
                        // 这里可以选择发送或忽略
                        Timber.d("NAL unit type %d, size: %d", nalType, nalUnit.length);
                        break;
                }
            }
            // 移动到下一个可能的起始位置
            offset = nalDataEnd;
        }
    }
    /**
     * 查找起始码位置(0x00000001 或 0x000001)
     * @param data 数据数组
     * @param startPos 开始搜索的位置
     * @return 起始码位置,如果未找到返回 -1
     */
    private int findStartCode(byte[] data, int startPos) {
        for (int i = startPos; i < data.length - 2; i++) {
            if (data[i] == 0x00 && data[i + 1] == 0x00) {
                if (i + 2 < data.length && data[i + 2] == 0x01) {
                    return i; // 找到 0x000001
                }
                if (i + 3 < data.length && data[i + 2] == 0x00 && data[i + 3] == 0x01) {
                    return i; // 找到 0x00000001
                }
            }
        }
        return -1;
    }
    /**
     * 组合 SPS、PPS 和 IDR 帧数据
     * @param sps SPS 数据(包含起始码)
     * @param pps PPS 数据(包含起始码)
     * @param idr IDR 帧数据(包含起始码)
     * @return 组合后的数据
     */
    private byte[] combineFrameData(byte[] sps, byte[] pps, byte[] idr) {
        int totalLength = sps.length + pps.length + idr.length;
        byte[] combined = new byte[totalLength];
        int offset = 0;
        // 复制 SPS
        System.arraycopy(sps, 0, combined, offset, sps.length);
        offset += sps.length;
        // 复制 PPS
        System.arraycopy(pps, 0, combined, offset, pps.length);
        offset += pps.length;
        // 复制 IDR 帧
        System.arraycopy(idr, 0, combined, offset, idr.length);
        Timber.d("Combined SPS/PPS/IDR frame, total size: %d (SPS: %d, PPS: %d, IDR: %d)",
                 totalLength, sps.length, pps.length, idr.length);
        return combined;
    }
    /**
@@ -664,6 +804,10 @@
        // 关闭文件输出
        closeFileOutput();
        // 清理 SPS/PPS 缓存
        spsBuffer = null;
        ppsBuffer = null;
        Timber.d("H264 encoder stopped");
    }