通过 TCP 套接字将音频写入服务器

Posted

技术标签:

【中文标题】通过 TCP 套接字将音频写入服务器【英文标题】:Writing audio to server over TCP socket 【发布时间】:2016-04-24 18:52:30 【问题描述】:

我正在尝试通过 TCP 套接字将实时麦克风录音传输到服务器,然后服务器将输入流写入文件。 连接已建立,但一段时间后,我的客户端出现连接被拒绝错误。

服务器代码:

    public class auServer extends Thread
    private static ServerSocket serverSocket;
    private static int port = 3333; 

    public void run()
    

        System.out.println("init success");
       while(true)
       

          try
          
              serverSocket = new ServerSocket(port);
              serverSocket.setSoTimeout(10000);
              Socket clientSoc = serverSocket.accept();
             System.out.println("Waiting for client on port " +serverSocket.getLocalPort() + "...");
             System.out.println("Just connected to " + clientSoc.getRemoteSocketAddress());
             InputStream in = clientSoc.getInputStream();
             while(in!=null)
             
                 writeToFile(in);
             
             System.out.println("socket");

             clientSoc.close();
          catch(SocketTimeoutException s)
          
             System.out.println("Socket timed out!");
             break;
          catch(IOException e)
          
             e.printStackTrace();
                     System.out.println("some io");
             break;
           catch (Exception e) 
                    System.out.println("some e");
            e.printStackTrace();
        
       
    

    private void writeToFile(InputStream in) throws IOException 
        // Write the output audio in byte
        String filePath = "8k16bitMono1.wav";
        short sData[] = new short[1024];
        byte[] bData = IOUtils.toByteArray(in);
        FileOutputStream os = null;
        try 
         os = new FileOutputStream(filePath);
         catch (FileNotFoundException e) 
         e.printStackTrace();
        
         System.out.println("Short wirting to file" + sData.toString());
         try 
          os.write(bData, 0, 2048);
          catch (IOException e) 
          e.printStackTrace();
         
        try 
         os.close();
         catch (IOException e) 
         e.printStackTrace();
        

    


    public static void main(String[] args) 
        // TODO Auto-generated method stub
      try
      
        Thread serverThread = new auServer();
        serverThread.run();
        System.out.println("runing");
       catch(IOException e)
         e.printStackTrace();
      
    

和客户:

private void streamData(byte[] bData) throws UnknownHostException, IOException, InterruptedException   //bData is byte array to transmit
    Thread.sleep(500);
    Socket client = new Socket("10.221.40.41",3333);
    OutputStream outToServer = client.getOutputStream();
    outToServer.write(bData);
    if(!isRecording)
        client.close();

可能是什么问题? 提前致谢。

【问题讨论】:

有什么例外吗?请阅读一些“阻塞网络 I/O 教程”。代码存在多个问题。 “试图传输实时麦克风录音”——你对“实时”的定义是什么? 'streamData(byte[] bData)' bData 有多少字节?您是在说一次 streamData() 调用吗? 为什么要为每条数据创建一个新的客户端?为什么服务器一次又一次地将这些片段写入同一个文件?该文件不会增长,但只包含一份。请说明您的想法。 os.write(bData, 0, 2048);。 2048 年?你确定吗?为什么? 【参考方案1】:

我会逐条评论你的代码。

private static ServerSocket serverSocket;

这没有理由是静态的。

while(true)

    try
    
        serverSocket = new ServerSocket(port);
        serverSocket.setSoTimeout(10000);

最后两行应该在循环之前。这就是连接被拒绝的原因,也会导致你没有提到的BindExceptions。不清楚为什么需要超时。

             Socket clientSoc = serverSocket.accept();
             System.out.println("Waiting for client on port " +serverSocket.getLocalPort() + "...");

不,你不是。他已经联系上了。你在accept().

之前等待
             System.out.println("Just connected to " + clientSoc.getRemoteSocketAddress());
             InputStream in = clientSoc.getInputStream();
             while(in!=null)

循环和测试都是徒劳的。该变量最初不是空的,而且它永远不可能变为空。循环是徒劳的,因为writeToFile() 方法完全耗尽了输入流,因此再也没有什么可读取的了。这将导致您没有提到的垃圾数据。

             
                 writeToFile(in);
             
             System.out.println("socket");

毫无意义的消息。

             clientSoc.close();

accept() 之后的行到这里的所有代码都应该在单独的线程中执行。接受循环应该只接受连接并启动线程。

          catch(SocketTimeoutException s)
          
             System.out.println("Socket timed out!");

这里超时的是accept(),因为监听套接字是您设置超时的唯一套接字。我怀疑你需要这个。

             break;
          catch(IOException e)
          
             e.printStackTrace();
                     System.out.println("some io");

另一个无用的消息。

             break;
           catch (Exception e) 
                    System.out.println("some e");

又一个。当您遇到异常时,打印异常。这不是您自己设计的无用信息。否则调试就变成了一场猜谜游戏。

            e.printStackTrace();
        
       
    

    private void writeToFile(InputStream in) throws IOException 
        // Write the output audio in byte
        String filePath = "8k16bitMono1.wav";
        short sData[] = new short[1024];

未使用。删除。

        byte[] bData = IOUtils.toByteArray(in);

不要使用这个。它浪费空间并增加延迟。请参阅下面的正确解决方案。

        FileOutputStream os = null;
        try 
         os = new FileOutputStream(filePath);
         catch (FileNotFoundException e) 
         e.printStackTrace();
        

技术差。这个catch 放错地方了。取决于try 块中代码是否成功的代码应该在同一个try 块内。目前你正在经历这个try-catch,好像从未发生过异常,这将导致下面的代码中出现NullPointerException

         System.out.println("Short wirting to file" + sData.toString());

另一个毫无意义的消息。拼写错误; sData 里面什么都没有; sData.toString() 无论内容如何,​​都不会打印任何有用的东西;并且不正确,因为您根本没有写sData

         try 
          os.write(bData, 0, 2048);

这会准确写入 2048 个字节,而不管读取的数量可能更少或更多。如果它小于它会抛出一个ArrayIndexOutOfBoundsException 或类似的,我希望在第二次调用中看到它,尽管你没有提到它,因为数组在第二次和后续调用中应该是零长度(如果有的话)。它应该是一个循环,一般形式:

int count;
byte[] buffer = new byte[8192]; // or more if you like
while ((count = in.read(buffer)) > 0)

    out.write(buffer, 0, count);

回到你的代码:

          catch (IOException e) 
          e.printStackTrace();
         
        try 
         os.close();
         catch (IOException e) 
         e.printStackTrace();
        
    


    public static void main(String[] args) 
        // TODO Auto-generated method stub
      try
      
        Thread serverThread = new auServer();
        serverThread.run();

这会运行线程的run() 方法。它不会启动线程。应该是serverThread.start().

        System.out.println("runing");

拼写错误。在解决上述启动/运行问题之前,在服务器的 run() 方法退出之前,您不会看到此消息。

和客户:

private void streamData(byte[] bData) throws UnknownHostException, IOException, InterruptedException   //bData is byte array to transmit
    Thread.sleep(500);

毫无意义。消除。不要在网络代码中添加睡眠。

    Socket client = new Socket("10.221.40.41",3333);

不要为每个缓冲区创建新连接。在客户端的整个生命周期中使用相同的连接。

    OutputStream outToServer = client.getOutputStream();
    outToServer.write(bData);
    if(!isRecording)
        client.close();

这应该是无条件的,并且应该在其他地方,以及套接字的创建。

可能是什么问题?

问题。复数。多种的。见上文。

【讨论】:

很好的解释,这正是我所期待的,REVIEW(+1)。感谢您的努力,将尝试使用上述指南,如果可行,将标记为答案。再次感谢,抱歉粗鲁:) @EJP 客户端的一件事,如果我没有为每个缓冲区建立新连接并在每个循环中关闭它,它会发送 10XXX~ 字节的完整记录(直到我遇到退出 while 的情况)同时。我想将它作为 2048 字节连续发送(流)而不是最后发送。 连续流式传输正是建议的更改所发生的情况。我不明白你所说的“不在最后”是什么意思。【参考方案2】:

我想出了解决办法。 问题实际上是由于错误编码的流而阻塞了网络 I/O。

我错过的是

InputStream in = clientSoc.getInputStream();
DataInputStream DataIn =new DataInputStream(in)
while(DataIn!=null)
    
        writeToFile(DataIn);
    

还有客户端

OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.write(sData);

【讨论】:

不,您没有找到解决方案。 while (DataIn != null) 开始没有意义了。无论是最初还是之后,它都不能为空,因此循环永远不会终止,并且尝试多次从同一个流中写入是徒劳的,或者应该是。在这种情况下,更改为 DataInputStreamDataOutputStream 也是徒劳的。您的代码有很多问题,但它们与这些完全不同。 @EJP 我成功地获取了直播数据包并在服务器端播放它们。 DataIn!=null 可能是错误的条件,但是当我添加 DataOutputStream out = new DataOutputStream(outToServer); 时,它可以正常工作。我同意代码可能有问题,但请帮助我解决问题,而不是批评并使其成为-1。 您一定也更改了其他内容。这种变化没有任何改变。 DataOutputStream.write()DataInputStream.read() 与任何其他读取或写入方法没有什么不同。您在此处发布的代码会引发运行时异常。如果您不希望您的代码受到批评,那么发布它没有多大意义。如果它是完美的,你就不会有问题。你到处乱下结论。关于谁对您的答案投了反对票,您的证据为零。您似乎不了解此网站的工作原理,也不了解代码审查。 注意,如果您真的认为数据输入/输出流具有解决此问题所需的神奇属性,您需要说明原因,以及这些属性是什么。否则你的答案就是货物崇拜编程。【参考方案3】:

我猜你的客户端是一个安卓应用程序。有一次,我尝试通过套接字连接读取和写入数据到 android 应用程序。最终,我通过使用 adb(android 调试器桥)来做到这一点。

比方说,有一个 java 应用程序创建了一个端口号为 1992 的 Socket。还有一个 android 应用程序创建了一个端口号为 1993 的 ServerSocket

真正的交易来了!

在开始运行应用程序之前,需要在 adb shell 中执行以下命令(或者您可以在 cmd/terminal 中找到您的 adb 并执行该命令)

adb forward tcp:1992 tcp:1993    

转发端口后,您可以使用套接字的 IO 流从 java 应用程序向 android 应用程序写入和读取数据,反之亦然。

桌面应用代码sn-p:

while (flag) 
    try 
        final Socket socket = new Socket("localhost", 1992);
        new Thread()
            @Override
            public void run()                         
                while (flag)                             
                    try 
                        new PrintWriter(socket.getOutputStream(), true).println(new Date().toString());
                     catch (IOException ex) 
                        break;
                    
                
                                
        .start();
     catch (IOException ex) 
        // need port-forwarding
    
    

Android应用代码sn-p:

while (flag) 
    try 
        final Socket socket = new ServerSocket(1993).accept();
        new AsyncTask<Void, Void, Void>() 
            @Override
            public Void doInBackground(Void... params) 
                while (flag) 
                    try 
                        String line = new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine();
                        Log.d("", line);
                     catch (IOException ex) 
                        return null;
                    
                
                return null;
            
        .execute();
     catch (IOException ex) 
        break;
    
    

您可以将音频转换为字节数组并将其写入输出流。希望我的回答对你有用。

【讨论】:

对不起,我的问题没有很好地提出,我的 android 应用程序会将音频流式传输到服务器。在我的代码中,我只传输字节数据,我不明白为什么需要ADB forward tcp。请解释。感谢您的努力。【参考方案4】:

serversocket 的创建必须在循环之外完成。 对于并行连接,您需要为每个连接启动一个线程。

还将超时应用于已建立的连接。 serverSocket 上的超时将在超时后结束接受,这在服务器中不是你想要的。

...
public void run() 
    ServerSocket serverSocket;
    try 
        serverSocket = new ServerSocket(port);
     catch (Exception ex) 
        ex.printStackTrace();
        return;
    

    System.out.println("init success");

    while (true) 
        try 
            System.out.println("Waiting for client on port " + serverSocket.getLocalPort() + "...");
            final Socket clientSoc = serverSocket.accept();
            clientSoc.setSoTimeout(10000);

            System.out.println("Just connected to " + clientSoc.getRemoteSocketAddress());


            new Thread() 
                public void run() 
                    try 
                        InputStream in = clientSoc.getInputStream();
                        writeToFile(in);
                     catch (Exception e) 
                        e.printStackTrace();
                     finally 
                        try 
                            clientSoc.close();
                         catch (Exception ignore) 
                        
                    
                
            .start();
         catch (Exception ex) 
            ex.printStackTrace();
        
    

...

【讨论】:

以上是关于通过 TCP 套接字将音频写入服务器的主要内容,如果未能解决你的问题,请参考以下文章

Qt 通过 TCP 套接字实时流式传输音频

通过 tcp 套接字流式传输 PCM 音频

利用TCP服务器接收多客户端的音频数据,通过Naudio进行播放及音频处理TCP服务器

C++ Boost.Asio - tcp 套接字异步写入

服务器端的 C++ TCP 套接字多次写入()被视为客户端的单次读取

使用 Boost Asio 在 TCP 套接字上执行异步写入操作