G722编码和解码
这部分使用笔者的开源库 https://github.com/litongjava/java-media-codec
package com.litongjava.sip.rtp.codec;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.litongjava.media.MediaCodec;
/**
* 基于 java-media-codec JNI 的 G.722 codec。
*
* 设计说明:
* 1. 一个 G722Codec 实例对应一个媒体流/会话,不要跨多个通话共享。
* 2. encoder / decoder 都是有状态的 native 对象。
* 3. encode / decode 分别加锁,避免同一实例并发访问同一个 native handle。
* 4. 内部使用 DirectByteBuffer,满足 JNI zero-copy 要求。
*/
public class G722Codec implements AudioCodec, AutoCloseable {
private static final int CODEC_TYPE = MediaCodec.CODEC_G722;
private static final int SAMPLE_RATE = 16000;
private static final int CHANNELS = 1;
private static final int BITRATE = 64000;
private static final int OPTIONS = 0;
private final Object encodeLock = new Object();
private final Object decodeLock = new Object();
private long encoder;
private long decoder;
private ByteBuffer encodePcmBuffer;
private ByteBuffer encodeOutBuffer;
private ByteBuffer decodeInBuffer;
private ByteBuffer decodePcmBuffer;
@Override
public String codecName() {
return "G722";
}
@Override
public int payloadType() {
return 9;
}
/**
* RTP/SDP 中 G722 常写为 G722/8000,
* 这里返回媒体处理层使用的 PCM 采样率 16000。
*/
@Override
public int sampleRate() {
return SAMPLE_RATE;
}
@Override
public short[] decode(byte[] payload) {
if (payload == null || payload.length == 0) {
return new short[0];
}
synchronized (decodeLock) {
ensureDecoder();
int encodedLen = payload.length;
int pcmSamplesCapacity = estimateDecodedSamples(encodedLen);
int pcmBytesCapacity = pcmSamplesCapacity * 2;
decodeInBuffer = ensureDirectBuffer(decodeInBuffer, encodedLen, ByteOrder.BIG_ENDIAN);
decodePcmBuffer = ensureDirectBuffer(decodePcmBuffer, pcmBytesCapacity, ByteOrder.LITTLE_ENDIAN);
decodeInBuffer.clear();
decodePcmBuffer.clear();
decodeInBuffer.put(payload);
int decodedSamples = MediaCodec.decodeDirect(decoder, decodeInBuffer, encodedLen, decodePcmBuffer);
if (decodedSamples < 0) {
throw new IllegalStateException("G722 decodeDirect failed, code=" + decodedSamples);
}
short[] out = new short[decodedSamples];
for (int i = 0; i < decodedSamples; i++) {
out[i] = decodePcmBuffer.getShort(i * 2);
}
return out;
}
}
@Override
public byte[] encode(short[] pcm16) {
if (pcm16 == null || pcm16.length == 0) {
return new byte[0];
}
synchronized (encodeLock) {
ensureEncoder();
int pcmSamples = pcm16.length;
int pcmBytes = pcmSamples * 2;
int encodedCapacity = estimateEncodedBytes(pcmSamples);
encodePcmBuffer = ensureDirectBuffer(encodePcmBuffer, pcmBytes, ByteOrder.LITTLE_ENDIAN);
encodeOutBuffer = ensureDirectBuffer(encodeOutBuffer, encodedCapacity, ByteOrder.BIG_ENDIAN);
encodePcmBuffer.clear();
encodeOutBuffer.clear();
for (int i = 0; i < pcmSamples; i++) {
encodePcmBuffer.putShort(i * 2, pcm16[i]);
}
int encodedLen = MediaCodec.encodeDirect(encoder, encodePcmBuffer, pcmSamples, encodeOutBuffer);
if (encodedLen < 0) {
throw new IllegalStateException("G722 encodeDirect failed, code=" + encodedLen);
}
byte[] out = new byte[encodedLen];
for (int i = 0; i < encodedLen; i++) {
out[i] = encodeOutBuffer.get(i);
}
return out;
}
}
private void ensureEncoder() {
if (encoder != 0) {
return;
}
encoder = MediaCodec.createEncoder(CODEC_TYPE, SAMPLE_RATE, CHANNELS, BITRATE, OPTIONS);
if (encoder == 0) {
throw new IllegalStateException("Failed to create G722 encoder");
}
}
private void ensureDecoder() {
if (decoder != 0) {
return;
}
decoder = MediaCodec.createDecoder(CODEC_TYPE, SAMPLE_RATE, CHANNELS, BITRATE, OPTIONS);
if (decoder == 0) {
throw new IllegalStateException("Failed to create G722 decoder");
}
}
private int estimateEncodedBytes(int pcmSamples) {
long bytes = ((long) pcmSamples * BITRATE + (SAMPLE_RATE * 8L - 1)) / (SAMPLE_RATE * 8L);
return (int) Math.max(bytes + 16, 64);
}
private int estimateDecodedSamples(int encodedBytes) {
long samples = ((long) encodedBytes * 8L * SAMPLE_RATE + BITRATE - 1) / BITRATE;
return (int) Math.max(samples + 16, 160);
}
private static ByteBuffer ensureDirectBuffer(ByteBuffer buffer, int capacity, ByteOrder order) {
if (buffer != null && buffer.capacity() >= capacity) {
buffer.clear();
buffer.order(order);
return buffer;
}
return ByteBuffer.allocateDirect(capacity).order(order);
}
@Override
public void close() {
synchronized (encodeLock) {
if (encoder != 0) {
MediaCodec.destroyEncoder(encoder);
encoder = 0;
}
encodePcmBuffer = null;
encodeOutBuffer = null;
}
synchronized (decodeLock) {
if (decoder != 0) {
MediaCodec.destroyDecoder(decoder);
decoder = 0;
}
decodeInBuffer = null;
decodePcmBuffer = null;
}
}
}
