2015-09-15 45 views
1

我正在通過網絡以RTP數據包的形式獲取實時音頻流,並且我必須編寫一個代碼來捕獲,緩衝和播放音頻流。捕獲緩衝播放現場音頻流

問題

我們解決這個問題,我已經寫了兩個線程一個用於捕獲音頻,另一個用於播放。 現在,當我開始我的兩個捕獲線程運行比打線程 :(

緩衝區要求

  • RTP音頻數據包。
  • 8kHz的16位線性樣本(線性PCM慢線)
  • 4幀20ms音頻將在每個RTP包中發送
  • 直到AudioStart = 24(20ms幀數)到達時才播放
  • 播放時...如果緩衝區中20ms幀的數量達到0 ... 將停止播放,直到AudioStart幀被緩衝,然後重新啓動。
  • 在播放時......如果緩衝區中20ms幀的數量超過 AudioBufferHigh = 50,則刪除24幀(以最簡單的方式 - 從緩衝區中刪除 或刪除接下來的6個RTP消息)。

    我迄今所做..

代碼

BufferManager.java

public abstract class BufferManager { 
    protected static final Integer ONE = new Integer(1); 
    protected static final Integer TWO = new Integer(2); 
    protected static final Integer THREE = new Integer(3); 
    protected static final Integer BUFFER_SIZE = 5334;//5.334KB 
    protected static volatile Map<Integer, ByteArrayOutputStream> bufferPool = new ConcurrentHashMap<>(3, 0.9f, 2); 
    protected static volatile Integer captureBufferKey = ONE; 
    protected static volatile Integer playingBufferKey = ONE; 
    protected static Boolean running; 
    protected static volatile Integer noOfFrames = 0; 

    public BufferManager() { 
     //captureBufferKey = ONE; 
     //playingBufferKey = ONE; 
     //noOfFrames = new Integer(0); 
    } 

    protected void switchCaptureBufferKey() { 
     if(ONE.intValue() == captureBufferKey.intValue()) 
      captureBufferKey = TWO; 
     else if(TWO.intValue() == captureBufferKey.intValue()) 
      captureBufferKey = THREE; 
     else 
      captureBufferKey = ONE; 
     //printBufferState("SWITCHCAPTURE"); 
    }//End of switchWritingBufferKey() Method. 

    protected void switchPlayingBufferKey() { 
     if(ONE.intValue() == playingBufferKey.intValue()) 
      playingBufferKey = TWO; 
     else if(TWO.intValue() == playingBufferKey.intValue()) 
      playingBufferKey = THREE; 
     else 
      playingBufferKey = ONE; 
    }//End of switchWritingBufferKey() Method. 

    protected static AudioFormat getFormat() { 
     float sampleRate = 8000; 
     int sampleSizeInBits = 16; 
     int channels = 1; 
     boolean signed = true; 
     boolean bigEndian = true; 
     return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian); 
    } 

    protected int getByfferSize() { 
     return bufferPool.get(ONE).size() 
       + bufferPool.get(TWO).size() 
       + bufferPool.get(THREE).size(); 
    } 

    protected static void printBufferState(String flag) { 
     int a = bufferPool.get(ONE).size(); 
     int b = bufferPool.get(TWO).size(); 
     int c = bufferPool.get(THREE).size(); 
     System.out.println(flag + " == TOTAL : [" + (a + b +c) + "bytes] "); 
//  int a,b,c; 
//  System.out.println(flag + "1 : [" + (a = bufferPool.get(ONE).size()) + "bytes], 2 : [" + (b = bufferPool.get(TWO).size()) 
//    + "bytes] 3 : [" + (c = bufferPool.get(THREE).size()) + "bytes], TOTAL : [" + (a + b +c) + "bytes] "); 
    } 
}//End of BufferManager Class. 

AudioCapture.java

public class AudioCapture extends BufferManager implements Runnable { 
    private static final Integer RTP_HEADER_SIZE = 12; 
    private InetAddress ipAddress; 
    private DatagramSocket serverSocket; 
    long lStartTime = 0; 

    public AudioCapture(Integer port) throws UnknownHostException, SocketException { 
     super(); 
     running = Boolean.TRUE; 
     bufferPool.put(ONE, new ByteArrayOutputStream(BUFFER_SIZE)); 
     bufferPool.put(TWO, new ByteArrayOutputStream(BUFFER_SIZE)); 
     bufferPool.put(THREE, new ByteArrayOutputStream(BUFFER_SIZE)); 
     this.ipAddress = InetAddress.getByName("0.0.0.0"); 
     serverSocket = new DatagramSocket(port, ipAddress); 
    } 

    @Override 
    public void run() { 
     System.out.println(); 
     byte[] receiveData = new byte[1300]; 
     DatagramPacket receivePacket = null; 
     lStartTime = System.currentTimeMillis(); 
     receivePacket = new DatagramPacket(receiveData, receiveData.length); 
     byte[] packet = new byte[receivePacket.getLength() - RTP_HEADER_SIZE]; 
     ByteArrayOutputStream buff = bufferPool.get(captureBufferKey); 
     while (running) { 
      if(noOfFrames <= 50) { 
       try { 
        serverSocket.receive(receivePacket); 
        packet = Arrays.copyOfRange(receivePacket.getData(), RTP_HEADER_SIZE, receivePacket.getLength()); 
        if((buff.size() + packet.length) > BUFFER_SIZE) { 
         switchCaptureBufferKey(); 
         buff = bufferPool.get(captureBufferKey); 
        } 
        buff.write(packet); 
        noOfFrames += 4; 
       } catch (SocketException e) { 
        e.printStackTrace(); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } // End of try-catch block. 
      } else { 
       //System.out.println("Packet Ignored, Buffer reached to its maximum limit "); 
      }//End of if-else block. 
     } // End of while loop. 
    }//End of run() Method. 
} 

AudioPlayer.java

public class AudioPlayer extends BufferManager implements Runnable { 
    long lStartTime = 0; 

    public AudioPlayer() { 
     super(); 
    } 

    @Override 
    public void run() { 
     AudioFormat format = getFormat(); 
     DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 
     SourceDataLine line = null; 
     try { 
      line = (SourceDataLine) AudioSystem.getLine(info); 
      line.open(format); 
      line.start(); 
     } catch (LineUnavailableException e1) { 
      e1.printStackTrace(); 
     } 

     while (running) { 
      if (noOfFrames >= 24) { 
       ByteArrayOutputStream out = null; 
       try { 
        out = bufferPool.get(playingBufferKey); 
        InputStream input = new ByteArrayInputStream(out.toByteArray()); 
        byte buffer[] = new byte[640]; 
        int count; 
        while ((count = input.read(buffer, 0, buffer.length)) != -1) { 
         if (count > 0) { 
          InputStream in = new ByteArrayInputStream(buffer); 
          AudioInputStream ais = new AudioInputStream(in, format, buffer.length/format.getFrameSize()); 

          byte buff[] = new byte[640]; 
          int c = 0; 
          if((c = ais.read(buff)) != -1) 
           line.write(buff, 0, buff.length); 
         } 
        } 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
       /*byte buffer[] = new byte[1280]; 
       try { 
        int count; 
        while ((count = ais.read(buffer, 0, buffer.length)) != -1) { 
         if (count > 0) { 
          line.write(buffer, 0, count); 
         } 
        } 
       } catch (IOException e) { 
        e.printStackTrace(); 
       }*/ 
       out.reset(); 
       noOfFrames -= 4; 
       try { 
        if (getByfferSize() >= 10240) { 
         Thread.sleep(15); 
        } else if (getByfferSize() >= 5120) { 
         Thread.sleep(25); 
        } else if (getByfferSize() >= 0) { 
         Thread.sleep(30); 
        } 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } else { 
       // System.out.println("Number of frames :- " + noOfFrames); 
      } 
     } 
    }// End of run() method. 
}// End of AudioPlayer Class class. 

任何幫助或指針有用的鏈接將很可觀謝謝...

回答

0

This answer explains a few challenges with streaming.

簡而言之,您的客戶需要處理兩個問題:

1)客戶端和服務器上的時鐘(晶振)不完全同步。服務器可能比客戶端快一點/慢一點。客戶端通過檢查rtp數據包傳輸的速率持續匹配推斷服務器的時鐘速率。然後客戶端通過採樣率轉換來調整回放速率。所以不是以48k回放,而是以48000.0001 Hz的頻率回放。

2)數據包丟失,無序到達等必須處理。如果丟失數據包,則需要爲緩衝流中的數據包保留一個佔位符,否則,您的音頻將跳過並聽起來很刺耳並且變得不對齊。最簡單的方法是,以取代那些沉默,但鄰近包的體積丟失的包應進行調整,以避免急劇變化的信封搶購0

您的設計似乎有點非正統的。我已經成功使用了環形緩衝區。你也必須處理邊緣情況。

我總是說流媒體不是一項簡單的任務。