1

目前我正在研究一个关于“延迟听觉反馈”(DAF)的项目。基本上我想从麦克风录制声音,将其延迟特定的时间,然后播放。使用大约 200 毫秒的延迟和一个戴着耳机的人,这个反馈会关闭这个人流利说话的能力。(非常有趣:YouTube 上的 DAF

现在我正在尝试使用具有 256 个字节的 byte[]-buffer 使用 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 ...

}
4

2 回答 2

2

您可以轻松计算延迟,因为它取决于音频的采样率。假设这是 CD 质量(单声道)音频,采样率为每秒 44,100 个样本。200 毫秒是 0.2 秒,所以 44,100 X 0.2 = 8820。

因此,您的音频播放需要延迟 8820 个样本(或 17640 个字节)。如果您将录制和播放缓冲区设置为这个大小(17640 字节),它将使您的代码非常简单。当每个记录缓冲区都被填满时,您将其传递给播放;这将实现恰好一个缓冲区持续时间的播放延迟。

于 2013-02-24T13:28:16.140 回答
0

您应该考虑 Android 固有的一些延迟,但除此之外......

创建一个循环缓冲区。不管多大,只要它对于 N 0 个样本足够大。现在用 N 个“0”样本写出来。

在这种情况下,N 是(以秒为单位的延迟)*(以赫兹为单位的采样率)。

示例:200 毫秒,16kHz 立体声:

0.2s*16000Hz*(2 通道)=3200*2 个样本 = 6400 个样本

您可能也会使用 pcm 数据,它是 16 位的,所以使用 short 而不是 byte。

用适量的零填充缓冲区后,开始读取扬声器的数据,同时填充来自麦克风的数据。

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 ...

} 
于 2013-02-24T13:12:52.720 回答