【发布时间】:2018-08-26 19:54:09
【问题描述】:
我的一个项目有问题。 该项目是一个语音聊天,您应该能够在其中跨设备相互交谈。 当 2 个 pc 客户端正在通话时,该项目运行良好。 当 pc 客户端正在通话并且 android 设备获取数据时,它充满了噪音。我能隐约听到在说什么。 有人知道我可以做些什么来过滤掉噪音吗?
所有代码都在这里
PC 客户端:
package test;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
public class Client extends javax.swing.JFrame {
private Socket socket;
private ObjectInputStream dis;
private ObjectOutputStream dos;
private ServerHandler sh = new ServerHandler();
private SourceDataLine speaker = null; //speaker
private TargetDataLine mic = null;
private MicThread micThread;
private Thread speakerThread;
private boolean keepPlaying = true;
public Client() {
initComponents();
}
class ServerHandler extends Thread {
@Override
public void run () {
try {
socket = new Socket(server.getText(), new Integer(port.getText()));
dos = new ObjectOutputStream(socket.getOutputStream());
dis = new ObjectInputStream(socket.getInputStream());
login((int) number.getValue());
try {
byte[] bytes;
DataPacket dp;
while ((dp = (DataPacket) dis.readObject()) != null) {
if (speaker == null || dp == null) {
return;
}
bytes = dp.getBytes();
speaker.write(bytes, 0, bytes.length);
}
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void login (int uid) {
Login l = new Login(uid);
try {
dos.writeObject(l);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void send (byte[] bytes) {
if (dos == null) {
return;
}
try {
DataPacket dp = new DataPacket((int) calling.getValue(), bytes.length, bytes);
dos.writeObject(dp);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void connect () {
}
public void disconnect () {
mic.stop();
try {
if (dis != null) { dis.close(); }
if (dos != null) { dos.close(); }
if (socket != null) { socket.close(); }
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MicThread extends Thread {
public MicThread () {
try {
AudioFormat af = new AudioFormat(44100, 16, 1, true, true);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, null);
mic = (TargetDataLine) (AudioSystem.getLine(info));
mic.open(af);
mic.start();
} catch (LineUnavailableException ex) {
ex.printStackTrace();
}
}
@Override
public void run () {
for (;;) {
if (mic.available() >= 7072) {
byte[] buff = new byte[7072];
while (mic.available() >= 7072) {
mic.read(buff, 0, buff.length);
}
long tot = 0;
for (int i = 0; i < buff.length; i++) {
tot += Math.abs(buff[i]);
}
tot *= 2.5;
tot /= buff.length;
if (tot == 0) {
} else {
sh.send(buff);
}
} else {
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jPanel1 = new javax.swing.JPanel();
connect = new javax.swing.JButton();
port = new javax.swing.JTextField();
server = new javax.swing.JTextField();
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
hangupButton = new javax.swing.JButton();
jPanel2 = new javax.swing.JPanel();
jLabel3 = new javax.swing.JLabel();
number = new javax.swing.JSpinner();
callButton = new javax.swing.JButton();
calling = new javax.swing.JSpinner();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
connect.setText("Connect");
connect.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
connectActionPerformed(evt);
}
});
port.setText("2222");
server.setText("localhost");
jLabel1.setText("Server");
jLabel2.setText("Port");
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 200, Short.MAX_VALUE)
.addComponent(server, javax.swing.GroupLayout.Alignment.LEADING))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(port, javax.swing.GroupLayout.DEFAULT_SIZE, 88, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(connect))
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(jLabel2)
.addGap(0, 0, Short.MAX_VALUE))))
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(jLabel2))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(port)
.addComponent(connect, javax.swing.GroupLayout.DEFAULT_SIZE, 37, Short.MAX_VALUE)
.addComponent(server, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
);
hangupButton.setText("Hangup");
hangupButton.setEnabled(false);
hangupButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
hangupButtonActionPerformed(evt);
}
});
jLabel3.setText("Number");
javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
jPanel2.setLayout(jPanel2Layout);
jPanel2Layout.setHorizontalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel2Layout.createSequentialGroup()
.addComponent(jLabel3)
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(number, javax.swing.GroupLayout.Alignment.TRAILING)
);
jPanel2Layout.setVerticalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel2Layout.createSequentialGroup()
.addComponent(jLabel3)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(number, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
);
callButton.setText("Call");
callButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
callButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(calling)
.addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(hangupButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(callButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(calling, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(3, 3, 3)
.addComponent(callButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(hangupButton)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
private void connectActionPerformed(java.awt.event.ActionEvent evt) {
sh.start();
hangupButton.setEnabled(true);
speakerThread = new Thread(new Runnable() {
@Override
public void run() {
try {
//open channel to sound card
//AudioFormat af = new AudioFormat(11025f, 8, 1, true, true);
//AudioFormat af = new AudioFormat(33075f, 16, 1, true, true);
AudioFormat af = new AudioFormat(44100f, 16, 1, true, true);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
speaker = (SourceDataLine) AudioSystem.getLine(info);
speaker.open(af);
speaker.start();
//sound card ready
} catch (Exception e) { //sound card error or connection error, stop
e.printStackTrace();
if (speaker != null) {
speaker.close();
}
}
}
});
speakerThread.start();
}
private void hangupButtonActionPerformed(java.awt.event.ActionEvent evt) {
sh.disconnect();
hangupButton.setEnabled(false);
System.exit(0);
}
private void callButtonActionPerformed(java.awt.event.ActionEvent evt) {
micThread = new MicThread();
micThread.start();
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(Client.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(Client.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(Client.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(Client.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Client().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton callButton;
private javax.swing.JSpinner calling;
private javax.swing.JButton connect;
private javax.swing.JButton hangupButton;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JSpinner number;
private javax.swing.JTextField port;
private javax.swing.JTextField server;
// End of variables declaration
}
DataPacket.java:
package test;
import java.io.Serializable;
public class DataPacket implements Serializable {
private int to = 0;
private int sizeInBytes = 1200;
private byte[] bytes = null;
public DataPacket (int to, int sizeInBytes, byte[] bytes) {
this.to = to;
this.sizeInBytes = sizeInBytes;
this.bytes = bytes;
}
public int getTo () {
return to;
}
public int getSizeInBytes () {
return sizeInBytes;
}
public byte[] getBytes () {
return bytes;
}
}
登录.java:
package test;
import java.io.Serializable;
public class Login implements Serializable {
private int number = 0;
public Login (int number) {
this.number = number;
}
public int getNumber () {
return number;
}
}
Server.java:
package test;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
public class Server {
private HashMap<Integer, ClientThread> connected = new HashMap<>();
private int port = 2222;
private boolean keepGoing = true;
private void stop () {
keepGoing = false;
try {
new Socket("localhost", port);
} catch(Exception e) {
}
}
private void start () {
keepGoing = true;
try {
ServerSocket serverSocket = new ServerSocket(port);
while (keepGoing) {
Socket socket = serverSocket.accept();
if (!keepGoing) {
break;
}
ClientThread t = new ClientThread(socket);
t.start();
}
try {
serverSocket.close();
Set<Integer> set = connected.keySet();
Iterator<Integer> it = set.iterator();
while (it.hasNext()) {
int i = it.next();
ClientThread t = connected.get(i);
t.close();
}
} catch (Exception e) {
}
} catch (IOException e) {
}
}
class ClientThread extends Thread {
private Socket socket;
private ObjectInputStream dis;
private ObjectOutputStream dos;
private int userID; // The user id of the loggedin user
public ClientThread(Socket socket) {
this.socket = socket;
try {
this.dis = new ObjectInputStream(socket.getInputStream());
this.dos = new ObjectOutputStream(socket.getOutputStream());
} catch (IOException e) {
}
}
public void sendVoice (DataPacket dp) {
try {
dos.writeObject(dp);
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public void run () {
boolean keepGoing = true;
while (keepGoing) {
try {
if (dis == null) {
keepGoing = false;
break;
}
Object obj = dis.readObject();
System.out.println(obj);
if (obj instanceof DataPacket) {
DataPacket dp = (DataPacket) obj;
int to = dp.getTo();
ClientThread ct = connected.get(to);
if (ct != null) {
ct.sendVoice(dp);
}
} else if (obj instanceof Login) {
Login l = (Login) obj;
userID = l.getNumber();
connected.put(userID, this);
}
} catch (IOException e) {
keepGoing = false;
e.printStackTrace();
} catch (ClassNotFoundException ex) {
keepGoing = false;
ex.printStackTrace();
}
}
close();
}
public void close () {
try {
if (dos != null) {dos.close();}
if (dis != null) {dis.close();}
if (socket != null) {socket.close();}
} catch (IOException e) {
}
closeThread(userID);
}
}
public void closeThread (int updater) {
ClientThread ct = connected.get(updater);
if (ct != null) {
ct.interrupt();
}
connected.remove(updater);
}
public static void main(String[] args) {
Server s = new Server();
s.start();
}
}
安卓
MainActivity.java
package test.test;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.nio.file.Files;
import test.DataPacket;
import test.Login;
public class MainActivity extends AppCompatActivity {
private Socket socket;
private ObjectInputStream dis;
private ObjectOutputStream dos;
private ServerHandler sh = new ServerHandler();
private AudioTrack speaker = null;
//private TargetDataLine mic = null;
//private MicThread micThread;
private Thread speakerThread;
private boolean keepPlaying = true;
private EditText number;
private EditText server;
private EditText port;
private EditText calling;
class ServerHandler extends Thread {
@Override
public void run () {
try {
socket = new Socket(server.getText().toString(), new Integer(port.getText().toString()));
dos = new ObjectOutputStream(socket.getOutputStream());
dis = new ObjectInputStream(socket.getInputStream());
login(new Integer(number.getText().toString()));
try {
byte[] bytes;
DataPacket dp;
while ((dp = (DataPacket) dis.readObject()) != null) {
if (speaker == null || dp == null) {
return;
}
bytes = dp.getBytes();
speaker.write(bytes, 0, bytes.length);
}
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void login (int uid) {
Login l = new Login(uid);
try {
dos.writeObject(l);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void send (byte[] bytes) {
if (dos == null) {
return;
}
try {
DataPacket dp = new DataPacket(new Integer(calling.getText().toString()), bytes.length, bytes);
dos.writeObject(dp);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void connect () {
}
public void disconnect () {
//mic.stop();
try {
if (dis != null) { dis.close(); }
if (dos != null) { dos.close(); }
if (socket != null) { socket.close(); }
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
number = (EditText) findViewById(R.id.number);
server = (EditText) findViewById(R.id.server);
port = (EditText) findViewById(R.id.port);
calling = (EditText) findViewById(R.id.calling);
Button connect = (Button) findViewById(R.id.connect);
Button callButton = (Button) findViewById(R.id.callButton);
Button hangupButton = (Button) findViewById(R.id.hangupButton);
connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sh.start();
}
});
callButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
hangupButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
sh = new ServerHandler();
int sampleRate = 44100;
int bufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
speaker = new AudioTrack(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(), new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(41000)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(), bufferSize, AudioTrack.MODE_STREAM, 0);
speaker.play();
}
}
据我所知,PC 设置与 Android 设置 AudioFormat 和 AudioTrack 匹配。
【问题讨论】:
-
.setSampleRate(41000)是错字吗?您已经为44100、.setSampleRate(sampleRate)定义了一个常量,我不知道这是否是单声道声音的有效采样率。我认为这就是 cd 立体声采样率(红皮书)。 Double checked。来自维基百科链接:音频 CD 可以表示高达 22.05 kHz 的频率,即 44.1 kHz 采样率的奈奎斯特频率。 -
是的,这是一个错字。不管我设置什么采样率,结果都是一样的