2013-02-24 176 views
1

目前我正在研究一個關於「延遲聽覺反饋」(DAF)的項目。基本上我想錄制來自麥克風的聲音,將其延遲一段特定的時間然後再播放。這種反饋使用200毫秒左右的延遲時間和耳機使用者,可以關閉人員流利說話的能力。 (非常有趣:DAF on youtube延遲播放音頻

現在我正在嘗試使用byte [] - 使用256字節的緩衝區與SourceDataLine和TargetDataLine進行循環。如果緩衝區變大,延遲也會變大。我現在的問題是:我無法確定延遲是以毫秒爲單位。

是否有任何方法來計算從緩衝區大小的實際延遲毫秒?或者可能有另一種方法來獲得這個結果?

這是我的循環看起來像此刻:

private int mBufferSize; // 256 
private TargetDataLine mLineOutput; 
private SourceDataLine mLineInput; 
public void run() { 

    ... creating the DataLines and getting the lines from AudioSystem ... 

    // byte buffer for audio 
    byte[] data = new byte[mBufferSize]; 

    // start the data lines 
    mLineOutput.start(); 
    mLineInput.start(); 

    // start recording and playing back 
    while (running) { 
     mLineOutput.read(data, 0, mBufferSize); 
     mLineInput.write(data, 0, mBufferSize); 
    } 

    ... closing the lines and exiting ... 

} 

回答

1

您可以很容易地計算延遲,因爲它取決於音頻的採樣率。假設這是CD質量(單聲道)音頻,採樣率爲每秒44,100個採樣。 200毫秒爲0.2秒,因此44,100 X 0.2 = 8820.

因此,您的音頻播放需要延遲8820個採樣(或17640字節)。如果您將錄製和播放緩衝區設置爲這種大小(17640字節),它將使您的代碼變得非常簡單。當每個錄製緩衝區都被填滿時,您將其傳遞給回放;這將實現恰好一個緩衝器持續時間的重放滯後。

+0

小問題:在音頻API下可能發生的雙緩衝怎麼樣?必須有其他一些存在播放間隙的風險。 :) – 2013-02-24 13:43:42

+0

好吧,這聽起來合理,非常整潔。謝謝:) – gtRfnkN 2013-02-24 14:09:49

+0

我會先嚐試MusiGenesis的建議,因爲它非常簡單。如果遇到問題,請嘗試使用FIFO並使用更小的緩衝區。我只和Android一起工作過,所以不知道你在純Java和MusiGenesis的建議下得到的音頻層可能工作得很好。我將很快發佈FIFO的代碼。 – 2013-02-24 15:20:36

0

有在Android的一些固有的延遲,你應該考慮,但除了...

創建循環緩衝區。無論多大,只要它對於N 0個樣本來說足夠大。現在用N'0'樣本寫下來。

N在這種情況下是(以秒爲單位延遲)*(以赫茲爲單位的採樣率)。

實例:通過16kHz的立體聲的200ms:

0.2秒* * 16000,(2個通道)= 3200個* 2個樣品=樣品6400

你將有可能與PCM數據工作太,其是16-位,所以使用短而不是字節。

用適量的零填充緩衝區後,開始讀取揚聲器的數據,同時填充麥克風的數據。

PCM先進先出:

public class PcmQueue 
{ 
    private short    mBuf[] = null; 
    private int     mWrIdx = 0; 
    private int     mRdIdx = 0; 
    private int     mCount = 0; 
    private int     mBufSz = 0; 
    private Object    mSync = new Object(); 

    private PcmQueue(){} 

    public PcmQueue(int nBufSz) 
    { 
     try { 
      mBuf = new short[nBufSz]; 
     } catch (Exception e) { 
      Log.e(this.getClass().getName(), "AudioQueue allocation failed.", e); 
      mBuf = null; 
      mBufSz = 0; 
     } 
    } 

    public int doWrite(final short pWrBuf[], final int nWrBufIdx, final int nLen) 
    { 
     int sampsWritten = 0; 

     if (nLen > 0) { 

      int toWrite; 
      synchronized(mSync) { 
       // Write nothing if there isn't room in the buffer. 
       toWrite = (nLen <= (mBufSz - mCount)) ? nLen : 0; 
      } 

      // We can definitely read toWrite shorts. 
      while (toWrite > 0) 
      { 
       // Calculate how many contiguous shorts to the end of the buffer 
       final int sampsToCopy = Math.min(toWrite, (mBufSz - mWrIdx)); 

       // Copy that many shorts. 
       System.arraycopy(pWrBuf, sampsWritten + nWrBufIdx, mBuf, mWrIdx, sampsToCopy); 

       // Circular buffering. 
       mWrIdx += sampsToCopy; 
       if (mWrIdx >= mBufSz) { 
        mWrIdx -= mBufSz; 
       } 

       // Increment the number of shorts sampsWritten. 
       sampsWritten += sampsToCopy; 
       toWrite -= sampsToCopy; 
      } 

      synchronized(mSync) { 
       // Increment the count. 
       mCount = mCount + sampsWritten; 
      } 
     } 
     return sampsWritten; 
    } 

    public int doRead(short pcmBuffer[], final int nRdBufIdx, final int nRdBufLen) 
    { 
     int sampsRead = 0; 
     final int nSampsToRead = Math.min(nRdBufLen, pcmBuffer.length - nRdBufIdx); 

     if (nSampsToRead > 0) { 
      int sampsToRead; 
      synchronized(mSync) { 
       // Calculate how many shorts can be read from the RdBuffer. 
       sampsToRead = Math.min(mCount, nSampsToRead); 
      } 

      // We can definitely read sampsToRead shorts. 
      while (sampsToRead > 0) 
      { 
       // Calculate how many contiguous shorts to the end of the buffer 
       final int sampsToCopy = Math.min(sampsToRead, (mBufSz - mRdIdx)); 

       // Copy that many shorts. 
       System.arraycopy(mBuf, mRdIdx, pcmBuffer, sampsRead + nRdBufIdx, sampsToCopy); 

       // Circular buffering. 
       mRdIdx += sampsToCopy; 
       if (mRdIdx >= mBufSz) { 
        mRdIdx -= mBufSz; 
       } 

       // Increment the number of shorts read. 
       sampsRead += sampsToCopy; 
       sampsToRead -= sampsToCopy; 
      } 

      // Decrement the count. 
      synchronized(mSync) { 
       mCount = mCount - sampsRead; 
      } 
     } 
     return sampsRead; 
    } 
} 

而且你的代碼,修改爲FIFO ......我有TargetDataLine的/ SourceDataLine的沒有經驗,所以如果他們只處理字節數組,修改的FIFO字節,而不是短期。

private int mBufferSize; // 256 
private TargetDataLine mLineOutput; 
private SourceDataLine mLineInput; 
public void run() { 

    ... creating the DataLines and getting the lines from AudioSystem ... 


    // short buffer for audio 
    short[] data = new short[256]; 
    final int emptySamples = (int)(44100.0 * 0.2); 
    final int bufferSize = emptySamples*2; 
    PcmQueue pcmQueue = new PcmQueue(bufferSize); 

    // Create a temporary empty buffer to write to the PCM queue 
    { 
     short[] emptyBuf = new short[emptySamples]; 
     Arrays.fill(emptyBuf, (short)emptySamples); 
     pcmQueue.doWrite(emptyBuf, 0, emptySamples); 
    } 

    // start recording and playing back 
    while (running) { 
     mLineOutput.read(data, 0, mBufferSize); 
     pcmQueue.doWrite(data, 0, mBufferSize);   
     pcmQueue.doRead(data, 0, mBufferSize);   
     mLineInput.write(data, 0, mBufferSize); 
    } 

    ... closing the lines and exiting ... 

} 
+0

小調整:PCM是一種可變格式,因此有效的PCM數據可以是每個採樣8位或每個採樣16位(其他值也可以)。但是*大多數* PCM(包括CD質量)是16位。 – MusiGenesis 2013-02-24 13:23:51

+0

的確如此。它甚至可以是浮動的。這只是表示它是某種時間標記的數據。你是對的。 – 2013-02-24 13:40:43

+0

謝謝!其實我不devolop爲Android但普通的Java 1.7,這是否也適用於此? 你可以發佈你的代碼片段嗎? – gtRfnkN 2013-02-24 14:08:17