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; } }