| | |
| | | // 编码参数 |
| | | 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; |
| | |
| | | |
| | | // 网络传输控制 |
| | | private boolean enableNetworkTransmission = true; // 是否启用TCP/UDP网络传输 |
| | | |
| | | // SPS/PPS 缓存(用于网络传输) |
| | | private byte[] spsBuffer = null; // SPS 缓存 |
| | | private byte[] ppsBuffer = null; // PPS 缓存 |
| | | |
| | | // 编码回调 |
| | | public interface OnFrameEncodedCallback { |
| | |
| | | encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
| | | encoder.start(); |
| | | |
| | | // 清理 SPS/PPS 缓存 |
| | | spsBuffer = null; |
| | | ppsBuffer = null; |
| | | |
| | | Timber.d( "H264 encoder initialized"); |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | } 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | // 关闭文件输出 |
| | | closeFileOutput(); |
| | | |
| | | // 清理 SPS/PPS 缓存 |
| | | spsBuffer = null; |
| | | ppsBuffer = null; |
| | | |
| | | Timber.d("H264 encoder stopped"); |
| | | } |
| | | |