package cn.org.hentai.jtt1078.server.audio; import cn.org.hentai.jtt1078.entity.AudioSendData; import cn.org.hentai.jtt1078.entity.enums.ProtocolVersion; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import lombok.extern.slf4j.Slf4j; import java.nio.ByteBuffer; import java.nio.ByteOrder; @Slf4j public class Jt1078AudioEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, AudioSendData audioData, ByteBuf out) throws Exception { try { // 1. 转换音频格式为PCM byte[] pcmData = audioData.getAudioBytes(); // 2. 编码为G.711 A-law byte[] g711Data = encodePcmToG711A(pcmData); // 3. 构建JT1078协议包 buildJt1078Packet(out, audioData, g711Data); } catch (Exception e) { log.error("音频编码失败", e); throw e; } } private byte[] encodePcmToG711A(byte[] pcmData) { byte[] alawData = new byte[pcmData.length / 2]; ByteBuffer buffer = ByteBuffer.wrap(pcmData); buffer.order(ByteOrder.LITTLE_ENDIAN); for (int i = 0; i < alawData.length; i++) { short pcmSample = buffer.getShort(); alawData[i] = linearToALaw(pcmSample); } return alawData; } private void buildJt1078Packet(ByteBuf out, AudioSendData audioData, byte[] g711Data) { // 1. JT1078 Header out.writeBytes(new byte[]{0x30, 0x31, 0x63, 0x64}); // 2. Payload Type (06 = Audio Stream) out.writeByte(0x81); out.writeByte(0x86); // 3. Sequence Number out.writeShort(audioData.getSequenceNumber()); // 4. SIM (BCD, 10 bytes) int simLength = (audioData.getProtocolVersion() == ProtocolVersion.V2013) ? 6 : 10; byte[] simBcd = encodeSimToBcd(audioData.getSim(), simLength); // byte[] simBcd = encodeSimToBcd(audioData.getSim(), 6); // 可能需要更长 out.writeBytes(simBcd); // 5. Channel out.writeByte(audioData.getChannel()); // 6. Data Type (Audio) + Frame Info (Atomic) out.writeByte(0x30); // 7. Timestamp (8 bytes) out.writeLong(System.currentTimeMillis()); // 8. Data Length (2 bytes) out.writeShort(g711Data.length); // 9. Audio Data (G.711 A-law) out.writeBytes(g711Data); // 打印十六进制字符串(推荐) //log.info("发送的 JT1078 报文(Hex): {}", ByteBufUtil.hexDump(out)); } private byte[] encodeSimToBcd(String sim, int length) { // BCD编码每个字节存2位数字,因此需要2倍长度 int digitCount = length * 2; // 补全或截断SIM卡号 String normalizedSim = sim.length() > digitCount ? sim.substring(0, digitCount) : String.format("%-" + digitCount + "s", sim).replace(' ', '0'); byte[] bcd = new byte[length]; for (int i = 0; i < digitCount; i += 2) { int high = Character.digit(normalizedSim.charAt(i), 10); int low = Character.digit(normalizedSim.charAt(i + 1), 10); bcd[i / 2] = (byte) ((high << 4) | low); } return bcd; } private byte linearToALaw(short pcm) { // G.711 A-law编码算法实现 int sign = (pcm & 0x8000) >> 8; if (sign != 0) { pcm = (short) -pcm; } if (pcm > 32767) pcm = 32767; int exponent = 7; int mask = 0x4000; while ((pcm & mask) == 0 && exponent > 0) { exponent--; mask >>= 1; } int mantissa = (pcm >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F; byte alaw = (byte) (sign | (exponent << 4) | mantissa); return (byte) (alaw ^ 0xD5); } }