304 lines
9.2 KiB
Java
Executable File
304 lines
9.2 KiB
Java
Executable File
package com.bernard.murder.audio;
|
|
|
|
import java.io.IOException;
|
|
import java.net.SocketAddress;
|
|
import java.net.SocketException;
|
|
import java.net.UnknownHostException;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
|
|
import javax.sound.sampled.AudioFormat;
|
|
import javax.swing.JOptionPane;
|
|
|
|
import com.bernard.murder.BytesUtils;
|
|
|
|
public class AudioServer {
|
|
|
|
// Format des paquets [commande 1, UUID identifier, byte deviceType, String name]
|
|
public static final byte DECLARE_NUMBER = 0x02;
|
|
// Format des paquets [commande 1, UUID identifier, byte deviceType, int id]
|
|
public static final byte OK_ID = 0x03;
|
|
// Format des paquets [commande 1]
|
|
public static final byte ASK_AUDIO_LIST= 0x04;
|
|
// Format des paquets [commande 1, int Count, {int id, String name}]
|
|
public static final byte GIVE_AUDIO_LIST = 0x05;
|
|
// Format des paquets: [commande 1, int listenId, int myId]
|
|
// -> Une enceinte demande à master le son d'un micro
|
|
public static final byte ASK_STREAMING = 0x06;
|
|
// Format des paquets: [commande 1, int id]
|
|
// -> Master demande à un micro d'envoyer du son
|
|
public static final byte START_STREAMING = 0x07;
|
|
// Format des paquets: [commande 1, int listenId, int myId]
|
|
// -> Une enceinte demande à master de ne plus recevoir le son d'un micro
|
|
public static final byte ASK_STOP_STREAMING = 0x09;
|
|
// Format des paquets: [commande 1, int id]
|
|
// -> Master demande à un micro de ne plus émettre.
|
|
public static final byte STOP_STREAMING = 0x08;
|
|
// Format des paquets [commande 1, int id, ~ data]
|
|
public static final byte AUDIO_STREAM = 0x01;
|
|
// Format des paquets [commande 1, byte deviceType, int deviceId]
|
|
// Un terminal indique qu'il se ferme à la connection
|
|
public static final byte DISCONNECTING = 0x0A;
|
|
|
|
|
|
public static final byte MASTER_DEVICE = 0x42;
|
|
public static final byte SPEAKER_DEVICE = 0x01;
|
|
public static final byte MIC_DEVICE = 0x02;
|
|
|
|
|
|
public static AudioFormat formatAudio = new AudioFormat(8000f, 16, 1, true, true);
|
|
public static int packetMaxSize = 97282;
|
|
public static int communicationPort = 35295;
|
|
|
|
public Serveur serveur;
|
|
|
|
int micId = 0;
|
|
int speakerId = 0;
|
|
|
|
Map<Integer,String> mics;
|
|
Map<Integer,String> speakers;
|
|
Map<Integer,SocketAddress> micsAddr;
|
|
Map<Integer,SocketAddress> speakersAddr;
|
|
Map<Integer,List<Integer>> listening; // micId, List<speakerId>
|
|
|
|
private Set<Runnable> changeListeners;
|
|
|
|
public AudioServer() throws SocketException, UnknownHostException {
|
|
mics = new HashMap<Integer, String>();
|
|
micsAddr = new HashMap<Integer, SocketAddress>();
|
|
speakers = new HashMap<Integer, String>();
|
|
speakersAddr = new HashMap<Integer, SocketAddress>();
|
|
listening = new HashMap<Integer, List<Integer>>();
|
|
|
|
changeListeners = new HashSet<>();
|
|
|
|
initServer();
|
|
}
|
|
|
|
public void initServer() throws SocketException, UnknownHostException {
|
|
serveur = new Serveur(this::receiveCommand, AudioServer.communicationPort);
|
|
}
|
|
|
|
public void receiveCommand(ByteBuffer data,SocketAddress senderAddress) {
|
|
byte commande = data.get();
|
|
System.out.println("Commande reçue : "+commande+" de "+senderAddress+" de taille "+(data.limit()+1));
|
|
switch (commande) {
|
|
|
|
case AudioServer.DECLARE_NUMBER:
|
|
|
|
UUID uuid = new UUID(data.getLong(), data.getLong());
|
|
byte deviceType = data.get();
|
|
String deviceName = BytesUtils.readString(data);
|
|
int newId;
|
|
switch (deviceType) {
|
|
case AudioServer.MIC_DEVICE:
|
|
newId = micId++;
|
|
mics.put(newId, deviceName);
|
|
micsAddr.put(newId, senderAddress);
|
|
listening.put(newId, new ArrayList<>());
|
|
publishAudioList(speakersAddr.values());
|
|
break;
|
|
case AudioServer.SPEAKER_DEVICE:
|
|
newId = speakerId++;
|
|
speakers.put(newId, deviceName);
|
|
speakersAddr.put(newId, senderAddress);
|
|
break;
|
|
default:return;
|
|
}
|
|
ByteBuffer out = ByteBuffer.allocate(AudioServer.packetMaxSize);
|
|
out.put(AudioServer.OK_ID);
|
|
out.putLong(uuid.getMostSignificantBits());
|
|
out.putLong(uuid.getLeastSignificantBits());
|
|
out.put(deviceType);
|
|
out.putInt(newId);
|
|
try {
|
|
serveur.sendData(out, senderAddress);
|
|
} catch (IOException e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
changeListeners.forEach(Runnable::run);
|
|
System.out.println("Accepting request from "+senderAddress);
|
|
break;
|
|
case AudioServer.ASK_STREAMING:
|
|
int listened = data.getInt();
|
|
int listener = data.getInt();
|
|
|
|
if(listening.get(listened).contains(listener))
|
|
break;
|
|
|
|
ByteBuffer out3 = ByteBuffer.allocate(AudioServer.packetMaxSize);
|
|
out3.put(AudioServer.START_STREAMING);
|
|
out3.putInt(listened);
|
|
try {
|
|
serveur.sendData(out3, micsAddr.get(listened));
|
|
|
|
listening.get(listened).add(listener);
|
|
changeListeners.forEach(Runnable::run);
|
|
} catch (IOException e2) {
|
|
e2.printStackTrace();
|
|
}
|
|
break;
|
|
|
|
case AudioServer.ASK_STOP_STREAMING:
|
|
int listened2 = data.getInt();
|
|
int listener2 = data.getInt();
|
|
if(listened2==-1)break;
|
|
try{
|
|
listening.get(listened2).remove(listener2);
|
|
}catch(IndexOutOfBoundsException e) {
|
|
System.err.println("Les données enregistrées par ce serveur ne sont pas intègres !");
|
|
}
|
|
changeListeners.forEach(Runnable::run);
|
|
|
|
if(listening.get(listened2).isEmpty()) {
|
|
try {
|
|
ByteBuffer out4 = ByteBuffer.allocate(AudioServer.packetMaxSize);
|
|
out4.put(AudioServer.STOP_STREAMING);
|
|
out4.putInt(listened2);
|
|
serveur.sendData(out4, micsAddr.get(listened2));
|
|
|
|
} catch (IOException e2) {
|
|
e2.printStackTrace();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AudioServer.AUDIO_STREAM:
|
|
int micId = data.getInt();
|
|
data.position(data.limit());
|
|
|
|
for(int spck : listening.get(micId)) {
|
|
SocketAddress dest = speakersAddr.get(spck);
|
|
try {
|
|
serveur.sendData(data, dest);
|
|
} catch (IOException e1) {
|
|
e1.printStackTrace();
|
|
JOptionPane.showMessageDialog(null, "Impossible de transmettre le son !","Son impossible !",JOptionPane.ERROR_MESSAGE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case AudioServer.ASK_AUDIO_LIST:
|
|
|
|
System.out.println("Sending audio list to "+senderAddress+" : ");
|
|
publishAudioList(Collections.singleton(senderAddress));
|
|
|
|
break;
|
|
|
|
case AudioServer.DISCONNECTING:
|
|
byte deviceType2 = data.get();
|
|
int deviceId = data.getInt();
|
|
if(deviceType2==AudioServer.MIC_DEVICE) {
|
|
// On le déréférence
|
|
if(!mics.containsKey(deviceId)) {
|
|
System.out.println("Le micro d'id "+deviceId+" est déjà désinscrit");
|
|
break;
|
|
}
|
|
System.out.println("Déconnection du microphone "+mics.get(deviceId));
|
|
mics.remove(deviceId);
|
|
micsAddr.remove(deviceId);
|
|
changeListeners.forEach(Runnable::run);
|
|
|
|
// On enlève tous les liens d'écoute
|
|
for(int spkId : listening.get(deviceId)) {
|
|
// Annoncer à ce speaker que le micro est déconnécté et qu'il ne peut plus l'écouter.
|
|
ByteBuffer out5 = ByteBuffer.allocate(AudioServer.packetMaxSize);
|
|
out5.put(AudioServer.DISCONNECTING);
|
|
out5.put(AudioServer.MIC_DEVICE);
|
|
out5.putInt(deviceId);
|
|
try {
|
|
serveur.sendData(out5, speakersAddr.get(spkId));
|
|
} catch (IOException e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
}
|
|
listening.remove(deviceId);
|
|
publishAudioList(speakersAddr.values());
|
|
|
|
}else if(deviceType2==AudioServer.SPEAKER_DEVICE) {
|
|
|
|
// On le déréférence
|
|
System.out.println("Déconnection de l'enceinte "+speakers.get(deviceId));
|
|
speakers.remove(deviceId);
|
|
speakersAddr.remove(deviceId);
|
|
changeListeners.forEach(Runnable::run);
|
|
|
|
int lstTo = listens(deviceId);
|
|
if(lstTo!=-1) {
|
|
// On enlève le lien d'écoute
|
|
listening.get(lstTo).remove((Integer)deviceId);
|
|
|
|
if(listening.get(lstTo).isEmpty()) {
|
|
// Si il n'y a plus rien à écouter.
|
|
try {
|
|
ByteBuffer out4 = ByteBuffer.allocate(AudioServer.packetMaxSize);
|
|
out4.put(AudioServer.STOP_STREAMING);
|
|
out4.putInt(lstTo);
|
|
serveur.sendData(out4, micsAddr.get(lstTo));
|
|
} catch (IOException e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
}else {
|
|
System.err.println("Je ne sais pas comment réagir à la déconnection d'un appareil de type "+deviceType2);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
System.out.println("Je ne devait pas recevoir cette commade !");
|
|
}
|
|
}
|
|
|
|
public void addChangeListener(Runnable listener) {
|
|
changeListeners.add(listener);
|
|
}
|
|
public void removeChangeListener(Runnable listener) {
|
|
changeListeners.remove(listener);
|
|
}
|
|
|
|
public Map<Integer, String> getMics() {
|
|
return Collections.unmodifiableMap(mics);
|
|
}
|
|
public Map<Integer, String> getSpeakers() {
|
|
return Collections.unmodifiableMap(speakers);
|
|
}
|
|
|
|
public int listens(int spk) {
|
|
for(int mic : listening.keySet()) {
|
|
if(listening.get(mic).contains(spk))
|
|
return mic;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public void publishAudioList(Collection<SocketAddress> co) {
|
|
ByteBuffer out = ByteBuffer.allocate(AudioServer.packetMaxSize);
|
|
out.put(AudioServer.GIVE_AUDIO_LIST);
|
|
out.putInt(mics.size());
|
|
for(Entry<Integer, String> mic : mics.entrySet()) {
|
|
out.putInt(mic.getKey());
|
|
BytesUtils.writeString(out, mic.getValue());
|
|
}
|
|
|
|
try {
|
|
for(SocketAddress addr : co) {
|
|
serveur.sendData(out, addr);
|
|
}
|
|
} catch (IOException e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
}
|
|
}
|