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<AudioSendData> {
|
|
@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);
|
}
|
}
|