package de.debugco.jairport; import com.beatofthedrum.alacdecoder.AlacDecodeUtils; import com.beatofthedrum.alacdecoder.AlacFile; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketAddress; public class RaopServer extends Thread { private RaopSession session; private int port; private DatagramSocket receiveSocket; private Cipher aesCipher; private SecretKeySpec secretKey; private IvParameterSpec keySpec; private SocketAddress clientSocketAddress; private AlacFile alac; private SourceDataLine line; private int[] outbuffer; private byte[] outbufferBytes; public RaopServer(RaopSession session) { super("RaopServer " + session.getId()); this.port = session.getControlPort() - 1; this.session = session; try { this.aesCipher = Cipher.getInstance("AES/CBC/NOPADDING"); receiveSocket = new DatagramSocket(port); DataLine.Info info = new DataLine.Info(SourceDataLine.class, new AudioFormat(session.getFormat().getSampleRate(), session.getFormat().getSampleSize(), 2, true, true)); line = (SourceDataLine) AudioSystem.getLine(info); line.open(); line.start(); } catch (Exception e) { throw new RuntimeException(e); } secretKey = new SecretKeySpec(session.getAesKey(), "AES"); secretKey = new SecretKeySpec(session.getAesKey(), "AES"); keySpec = new IvParameterSpec(session.getAesIv()); int frameSize = session.getFormat().getFrameSize(); alac = AlacDecodeUtils.create_alac(session.getFormat().getSampleSize(), 2); alac.setSetinfo_max_samples_per_frame(frameSize); alac.setSetinfo_rice_historymult(session.getFormat().getRiceHistoryMult()); alac.setSetinfo_rice_initialhistory(session.getFormat().getRiceInitialHistory()); alac.setSetinfo_rice_kmodifier(session.getFormat().getRiceKModifier()); alac.setSetinfo_sample_size(session.getFormat().getSampleSize()); outbuffer = new int[frameSize * 4]; outbufferBytes = new byte[outbuffer.length * 2]; } public void requestRtpResend(int first, int last) throws IOException { if(first > last) { System.out.println(first + " > " + last); return; } int len = last - first + 1; byte[] b = new byte[] { (byte) 0x80, (byte) (0x55|0x80), 0x01, 0x00, (byte) ((first & 0xFF00) >> 8), (byte) (first & 0xFF), (byte) ((len & 0xFF00) >> 8), (byte) (len & 0xFF)}; DatagramPacket packet = new DatagramPacket(b, 0, b.length, clientSocketAddress); receiveSocket.send(packet); } private int lastSeqNo = 0; private int convertSampleBufferToByteBuffer(int[] sampleBuffer, int len, byte[] outbuffer) { int j = 0; for (int i = 0; i < len; ++i) { int sample = sampleBuffer[i]; outbuffer[j++] = (byte) (sample >> 8); outbuffer[j++] = (byte) sample; } return j; } private void putDataInBuffer(int seqNo, byte[] data, int offset, int len) { byte[] out = new byte[len]; decryptAes(data, offset, len, out); int samplesInBytes = AlacDecodeUtils.decode_frame(alac, out, outbuffer, outbuffer.length); assert(samplesInBytes == session.getFormat().getFrameSize() * 4); int lenBytes = convertSampleBufferToByteBuffer(outbuffer, samplesInBytes >> 1, outbufferBytes); line.write(outbufferBytes, 0, lenBytes); if(seqNo + 1 < lastSeqNo) { System.out.println(seqNo + " " + lastSeqNo); } lastSeqNo = seqNo; } @Override public void run() { byte[] b = new byte[2048]; DatagramPacket packet = new DatagramPacket(b, b.length); while(!Thread.interrupted()) { try { receiveSocket.receive(packet); } catch (IOException e) { e.printStackTrace(); this.interrupt(); continue; } if(clientSocketAddress == null) { clientSocketAddress = packet.getSocketAddress(); } int offset = packet.getOffset(); int length = packet.getLength(); byte[] data = packet.getData(); byte type = (byte)(data[offset + 1] & ~0x80); if(type == 0x60 || type == 0x56) { // audio data / resend if(type == 0x56) { offset = offset + 4; length = length - 4; } int seqno = (data[offset + 2] << 8) | data[offset + 3]; putDataInBuffer(seqno, data, offset + 12, length - 12); } } } public int getPort() { return port; } private int decryptAes(byte[] inbuf, int offset, int len, byte[] outbuffer) { int decodeLen = len - (len % 16); try { aesCipher.init(Cipher.DECRYPT_MODE, secretKey, keySpec); int ret = aesCipher.doFinal(inbuf, offset, decodeLen, outbuffer); System.arraycopy(inbuf, offset + decodeLen, outbuffer, decodeLen, len % 16); return ret; } catch (Exception e) { throw new RuntimeException(e); } } }