java - 确定音频处理中的延迟

标签 java multithreading audio midi

我一直致力于在现有的音乐软件项目中实现实时音频捕获和分析系统。该系统的目标是在用户按下录音按钮时(或在指定的预备拍后)开始捕获音频,确定用户演唱或演奏的音符,并将这些音符记在乐谱上。我的方法的要点是使用一个线程捕获音频数据 block 并将它们放入队列中,然后使用另一个线程从队列中删除数据并执行分析。

该方案运行良好,但我无法量化音频捕获开始与 MIDI 伴奏乐器播放之间的延迟。音频捕捉在 MIDI 乐器开始回放之前就开始了,并且用户可能会将他或她的演奏与 MIDI 乐器同步。因此,我需要忽略在支持 MIDI 乐器开始播放之前捕获的音频数据,而只分析在该点之后收集的音频数据。

背景音轨的播放由一组代码处理,该代码已经存在了很长一段时间并由其他人维护,因此我想尽可能避免重构整个程序。音频捕获由一个 Timer 对象和一个扩展 TimerTask 的类控制,其实例是在一个名为 Notate 的笨重(约 25k 行)类中创建的。顺便说一句,Notate 还密切关注处理背景音轨播放的对象。 Timer 的 .scheduleAtFixedRate() 方法用于控制音频捕获的周期,TimerTask 通过调用队列 (ArrayBlockingQueue) 上的 .notify() 通知捕获线程开始。

我计算这两个进程初始化之间的时间间隔的策略是从播放开始时的时间戳中减去捕获开始前的时间戳(以毫秒为单位),我将其定义为 .start () 方法在负责 MIDI 背景音轨的 Java Sequencer 对象上调用。然后我使用结果来确定我希望在此间隔 (n) 内捕获的音频样本数,并忽略捕获的音频数据数组中的前 n * 2 个字节(n * 2 因为我正在捕获 16-位样本,而数据存储为字节数组……每个样本 2 个字节)。

但是,这种方法并没有给我准确的结果。计算出的偏移量总是小于我的预期,因此在指定位置开始分析后,音频数据中仍然存在大量(不幸的是变化多端的)“空白”空间。这会导致程序尝试分析在用户尚未开始演奏伴奏 MIDI 乐器时收集的音频数据,有效地在用户音乐段落的开头添加休止符(没有音符)并破坏节奏值计算所有后续笔记。

下面是我的音频捕获线程的代码,它还确定了捕获的音频数据数组的延迟和相应的位置偏移。谁能深入了解为什么我确定延迟的方法无法正常工作?

public class CaptureThread extends Thread
{
    public void run()
    {
        //number of bytes to capture before putting data in the queue.
    //determined via the sample rate, tempo, and # of "beats" in 1 "measure"
        int bytesToCapture = (int) ((SAMPLE_RATE * 2.) / (score.getTempo()
                / score.getMetre()[0] / 60.));
    //temporary buffer - will be added to ByteArrayOutputStream upon filling.
        byte tempBuffer[] = new byte[target.getBufferSize() / 5];

        int limit = (int) (bytesToCapture / tempBuffer.length);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bytesToCapture);
        int bytesRead;

        try
        { //Loop until stopCapture is set.
            while (!stopCapture)
            { //first, wait for notification from TimerTask
                synchronized (thisCapture)
                {
                    thisCapture.wait();
                }

                if (!processingStarted)
                { //the time at which audio capture begins
                    startTime = System.currentTimeMillis();
                }

                //start the TargetDataLine, from which audio data is read
                target.start();

                //collect 1 captureInterval's worth of data
                for (int n = 0; n < limit; n++)
                {
                    bytesRead = target.read(tempBuffer, 0, tempBuffer.length);
                    if (bytesRead > 0)
                    {   //Append data to output stream.
                        outputStream.write(tempBuffer, 0, bytesRead);
                    }
                }

                if (!processingStarted)
                {
                    long difference = (midiSynth.getPlaybackStartTime()
                            + score.getCountInTime() * 1000 - startTime);

                    positionOffset = (int) ((difference / 1000.)
                            * SAMPLE_RATE * 2.);

                    if (positionOffset % 2 != 0)
                    { //1 sample = 2 bytes, so positionOffset must be even
                        positionOffset += 1;
                    }
                }
                if (outputStream.size() > 0)
                {   //package data collected in the output stream into a byte array
                    byte[] capturedAudioData = outputStream.toByteArray();
                    //add captured data to the queue for processing
                    processingQueue.add(capturedAudioData);

                    synchronized (processingQueue)
                    {
                        try
                        { //notify the analysis thread that data is in the queue
                            processingQueue.notify();
                        } catch (Exception e)
                        {
                            //handle the error
                        }
                    }

                    outputStream.reset(); //reset the output stream
                }
            }
        } catch (Exception e)
        {
            //handle error
        }
    }
}

我正在考虑使用 Mixer对象同步 TargetDataLine它正在接受来自麦克风的数据和处理 MIDI 乐器播放的线路。现在找到处理播放的线路...有什么想法吗?

最佳答案

Google 有一个很好的开源应用,叫做 AudioBufferSize,您可能很熟悉。我修改了这个应用程序以测试一种延迟时间——也就是说,从用户按下按钮到音频 API 播放声音之间的时间。这是我添加到 AudioBufferSize 以实现此目的的代码。您能否使用这种方法来提供事件与用户感知事件之间的时间差?

final Button latencyButton = (Button) findViewById(R.id.latencyButton);
latencyButton.setOnClickListener(new OnClickListener() {
    public void onClick(View v) {
        mLatencyStartTime = getCurrentTime();
        latencyButton.setEnabled(false);

        // Do the latency calculation, play a 440 hz sound for 250 msec
        AudioTrack sound = generateTone(440, 250);              
        sound.setNotificationMarkerPosition(count /2); // Listen for the end of the sample

        sound.setPlaybackPositionUpdateListener(new OnPlaybackPositionUpdateListener() {
            public void onPeriodicNotification(AudioTrack sound) { }
            public void onMarkerReached(AudioTrack sound) {
                // The sound has finished playing, so record the time
                mLatencyStopTime = getCurrentTime();
                diff = mLatencyStopTime - mLatencyStartTime;
                // Update the latency result
                TextView lat = (TextView)findViewById(R.id.latency);
                lat.setText(diff + " ms");
                latencyButton.setEnabled(true);
                logUI("Latency test result= " + diff + " ms");
            }
        });
        sound.play();
    }
});

有一个对 generateTone 的引用,看起来像这样:

private AudioTrack generateTone(double freqHz, int durationMs) {
    int count = (int)(44100.0 * 2.0 * (durationMs / 1000.0)) & ~1;
    short[] samples = new short[count];
    for(int i = 0; i < count; i += 2){
        short sample = (short)(Math.sin(2 * Math.PI * i / (44100.0 / freqHz)) * 0x7FFF);
        samples[i + 0] = sample;
        samples[i + 1] = sample;
    }
    AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
    AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
    count * (Short.SIZE / 8), AudioTrack.MODE_STATIC);
    track.write(samples, 0, count);
    return track;
}

刚刚意识到,这个问题已经存在多年了。对不起,也许有人会觉得它有用。

关于java - 确定音频处理中的延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11693527/

相关文章:

java - 如何在运行时根据用户/程序员的需要自动增加数组的大小?

java - 第二个 JFrame 是空白的并且非常小

java - 如何正确地将路径传递给 MediaPlayer(错误 java.net.URISyntaxException)

c++ - Qt:在 linux 上无法播放

java - 处理 16 位字符的 I/O

java - 使用 Spring JPA 处理软删除

c# - 2 WCF 服务和 c# 线程

java - Java线程的run方法不取更新状态

java - CompletableFuture supplyAsync(供应商)与 supplyAsync(供应商,执行者)

android - Android:录制和收听音频