Sketcher2 source code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
6.4 KiB

package com.jotuntech.sketcher.client.voice;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.xiph.speex.SpeexDecoder;
public class VoiceChannel {
public final static int SPEEX_FRAME_BYTES = 640;
private SpeexDecoder decoder;
private ByteBuffer[] packetArray;
private int expectedPacketNumber;
private int newestPacketNumber;
private float qualityPercent;
private ByteBuffer audioBuffer;
private VoiceMixer voiceMixer;
private Integer channelKey;
private boolean buffering;
private long lastPlayTime;
private boolean dead;
private VoiceListener listener;
public VoiceChannel(VoiceMixer voiceMixer, Integer channelKey) {
this.voiceMixer = voiceMixer;
this.channelKey = channelKey;
packetArray = new ByteBuffer[voiceMixer.getBufferSize() / SPEEX_FRAME_BYTES];
expectedPacketNumber = -1;
newestPacketNumber = 0;
qualityPercent = 50f;
decoder = new SpeexDecoder();
decoder.init(1, 16000, 1, true);
buffering = true;
lastPlayTime = System.currentTimeMillis();
dead = false;
}
public void packet(ByteBuffer packetBuffer) throws IOException {
if(lastPlayTime < System.currentTimeMillis() - 10000) {
dead = true;
return;
}
if(packetBuffer.remaining() < 4) {
/** Packet too short to even contain its own number */
return;
}
int packetNumber = packetBuffer.getInt();
if(expectedPacketNumber == -1) {
/** Lock on first packet in sequence */
expectedPacketNumber = packetNumber;
}
if(packetNumber - expectedPacketNumber > packetArray.length - 1) {
/** Packet overflow */
/** Decode the N packets up to, and including, the overflowed packet */
expectedPacketNumber = packetNumber - packetArray.length + 1;
newestPacketNumber = packetNumber;
/** Place overflowing packet at head of the queue */
packetArray[packetNumber % packetArray.length] = packetBuffer;
if(listener != null) {
listener.voiceEvent(new VoiceEvent(VoiceEvent.TYPE_PACKET_OVERFLOW, channelKey));
}
} else if(packetNumber < expectedPacketNumber) {
/** Packet arrived too late and is dropped */
} else {
/** Packet has arrived in order. */
packetArray[packetNumber % packetArray.length] = packetBuffer;
if(packetNumber > newestPacketNumber) {
/** Tell decoder to expect newer packets. */
newestPacketNumber = packetNumber;
}
}
}
public void process() throws IOException {
if(lastPlayTime < System.currentTimeMillis() - 10000) {
dead = true;
return;
}
/** Hold off playback until we are buffered up */
if(expectedPacketNumber == -1 || (buffering && newestPacketNumber - expectedPacketNumber < packetArray.length - 1)) {
if(listener != null) {
int bufferPercent = expectedPacketNumber == -1 ? 0 : Math.max(0, ((newestPacketNumber - expectedPacketNumber) * 200) / packetArray.length);
listener.voiceEvent(new VoiceEvent(VoiceEvent.TYPE_PACKET_BUFFERING, channelKey, bufferPercent));
}
return;
}
buffering = false;
/** Attempt to decode packets if there is room for them in the voice mixer buffer. */
while(voiceMixer.available(channelKey) >= SPEEX_FRAME_BYTES) {
/** Look for the next expected packet */
ByteBuffer packetBuffer = packetArray[expectedPacketNumber % packetArray.length];
if(packetBuffer == null) {
/** We have no packet. What to do? */
if(voiceMixer.available(channelKey) >= voiceMixer.getBufferSize() / 2) {
/** Playback buffer is half empty, so we "decode" a lost packet. */
decoder.processData(true);
/** Reduce quality average for missing packets */
qualityPercent -= qualityPercent * 0.1f;
if(expectedPacketNumber > newestPacketNumber) {
/** Packet underflow */
buffering = true;
expectedPacketNumber = -1;
if(listener != null) {
int bufferPercent = Math.max(0, ((newestPacketNumber - expectedPacketNumber) * 200) / packetArray.length);
listener.voiceEvent(new VoiceEvent(VoiceEvent.TYPE_PACKET_UNDERFLOW, channelKey, bufferPercent, (int) qualityPercent));
}
break;
} else {
/** Packet missing */
++expectedPacketNumber;
if(listener != null) {
int bufferPercent = Math.max(0, ((newestPacketNumber - expectedPacketNumber) * 200) / packetArray.length);
listener.voiceEvent(new VoiceEvent(VoiceEvent.TYPE_PACKET_LOST, channelKey, bufferPercent, (int) qualityPercent));
}
}
} else {
/** Playback buffer is good, so we wait for the packet. */
break;
}
} else {
/** We have a packet and we retrieve it. */
byte[] packetBufferArray = packetBuffer.array();
int dataOffset = packetBuffer.arrayOffset() + packetBuffer.position();
int dataLength = packetBuffer.remaining();
/** Decode the packet. */
decoder.processData(packetBufferArray, dataOffset, dataLength);
/** Erase packet and increment counter */
packetArray[expectedPacketNumber++ % packetArray.length] = null;
/** Update last play time */
lastPlayTime = System.currentTimeMillis();
if(listener != null) {
int bufferPercent = ((newestPacketNumber - expectedPacketNumber) * 200) / packetArray.length;
qualityPercent += (100 - qualityPercent) * 0.1f;
listener.voiceEvent(new VoiceEvent(VoiceEvent.TYPE_PACKET_RECEIVED, channelKey, bufferPercent, (int) qualityPercent));
}
}
/** Read the decoded packet into audio buffer */
int audioByteLength = decoder.getProcessedDataByteSize();
if(audioBuffer == null || audioByteLength > audioBuffer.capacity()) {
audioBuffer = ByteBuffer.allocate(audioByteLength);
audioBuffer.order(ByteOrder.LITTLE_ENDIAN);
System.err.println("Created " + audioByteLength + " byte audio buffer.");
}
audioBuffer.clear();
decoder.getProcessedData(audioBuffer.array(), audioBuffer.arrayOffset() + audioBuffer.position());
audioBuffer.position(audioBuffer.position() + audioByteLength);
audioBuffer.flip();
if(listener != null) {
/** Measure peak volume of audio buffer */
int peak = 0;
for(int i = audioBuffer.position(); i < audioBuffer.limit(); i += 2) {
int absSample = Math.abs(audioBuffer.getShort(i));
if(absSample > peak) {
peak = absSample;
}
}
/** Notify listener of peak volume */
listener.voiceEvent(new VoiceEvent(VoiceEvent.TYPE_PACKET_VOLUME, channelKey, peak));
}
/** Write audio buffer to voice mixer. */
voiceMixer.write(channelKey, audioBuffer);
}
}
public boolean isDead() {
return dead;
}
public void setListener(VoiceListener listener) {
this.listener = listener;
}
}