Java:SourceDataLine.write(...)屏蔽中断吗?

时间:2012-04-23 05:42:33

标签: java audio interrupt

我有一个循环调用SourceDataLine.write(...)的Thread,直到写入(播放)所有音频数据。我希望能够提前停止播放(在循环通常由EOF类型条件终止之前)并且(尝试)使用中断。当我调用playbackThread.interrupt()时,它会导致Thread的中断和终止。代码中的微小变化或连续调用具有不同的(看似随机的)结果。我已经调试并尝试使用Thread.currentThread.isInterrupted()而不是Thread.interrupted()。我已经注意到,如果我用其他东西替换SourceDataLine.write(...)调用(例如一个长计数循环),那么Thread会以可预测的方式一致地终止。

SourceDataLine.write(...)“消耗”中断但不抛出InterruptedException吗?意思是,它是否清除了中断标志(可能是Thread.interrupted()),忽略它,然后继续?这可以解释为什么中断间歇性地工作:如果在SDL.write(...)方法中发生,它将被丢弃;否则,它保持“完整”,直到我使用Thread.interrupted()调用进行测试。这将是可怕的,但这是我花了一整天后才能想出的唯一解释。我找不到SourceDataLine.write(...)的任何来源,因为它是一个接口,并且(我想)实现与机器有关。

因为我没有使用try / catch并且期望在我的循环中捕获InterruptedExeption,但是我在循环的底部调用Thread.interrupted()(或Thread.currentThread.isInterrupted())每次传递,可以轻松创建我自己的interruptFlag并测试它。这正是我几个月前在急于满足时间表时中断TargetDataLine.read(...)方法时在其他地方所做的。我周末大部分时间一直在调查这个长期存在的谜团的根源,并没有成功。

使用实际的Java中断机制似乎更加一致/优雅 - 但如果它不起作用则不会。这个解释是否合理?

播放代码:

public void run() {
    int offset = 0;
    int remaining = cRawAudioBuffer.length;
    int length = WRITE_LENGTH;
    int bytesWritten = 0;
    cOutputLine.start();
    while (remaining>0) {
        length = Math.min(length,remaining);
        bytesWritten = cOutputLine.write(cRawAudioBuffer,offset,length);
        offset += bytesWritten;
        remaining -= bytesWritten;
        if (Thread.currentThread().isInterrupted()) {
            System.out.println("I Was interrupted");
            break;
        }
    }
    cOutputLine.close();
}

更新

仍在调查如何更全面地了解这里发生的事情。我通过从写入循环本身内部调用Thread.interrupt()强制执行playbackThread的中断,并插入调用Thread.currentThread()。isInterrupted() - 因此不用测试本身清除中断标志 - 之前和在write()调用之后。我发现,对于前几个循环,中断在从write()方法返回后保留。在前3个循环之后,从write()返回后清除中断。此后,几乎总是,但总是在从write()返回后清除。我的猜测是,如果write()被阻塞,等待其缓冲区清除,它将检测,忽略和清除中断标志。仍然只是一个猜测,我宁愿确定(为了避免传播我做错的东西,让它在以后咬我)。关于如何在无法访问源的情况下验证此假设的任何想法?

public void run() {
    ....
    while (remaining>0) {
        length = Math.min(length,remaining);
        Thread.currentThread().interrupt();
        System.out.println("Player interrupt flag is " + (Thread.currentThread().isInterrupted()?"":"not ") + "set before write()");
        bytesWritten = cOutputLine.write(cRawAudioBuffer,offset,length);
        System.out.println("Player interrupt flag is " + (Thread.currentThread().isInterrupted()?"":"not ") + "set after write()");
        offset += bytesWritten;
        remaining -= bytesWritten;
        if (Thread.interrupted()) {
            System.out.println("I Was interrupted");
            //break;
        }
    }
    cOutputLine.close();
}

4 个答案:

答案 0 :(得分:4)

事实证明,SourceDataLine.write()在写入期间阻塞,如果在写入期间发生了中断,则忽略该中断标志并重置中断标志。无法直接检测中断。我想这对于使用Java Audio的人来说是常识,没有理解这一点就无法创建可靠的可中断音频播放。我(遗憾地)必须求助于在每次write()之后读取的syncrhonized标志以检测中断。

答案 1 :(得分:1)

你遇到的问题是,cOutputLine.write花费的大部分时间都是由于中断而停止的。这意味着循环需要在检查中断之前写入一些数据。如果这阻塞(由于完整的缓冲区),它将永远不会检查中断。

解决此问题的一种方法是关闭流,这将使写入中的IOException崩溃。或者您可以使用阻塞NIO(它不必是非阻塞的),当线程被中断时它会停止。

答案 2 :(得分:0)

我使用的解决方案是在line.available()对象上查询SourceDataLine以获取可以无阻塞地写入的字节数。我观察到如果在write上执行SourceDataLine操作的线程在阻塞写入期间被中断(即写入比从line.available()调用返回的值更多的字节),那么中断的标志似乎没有设置,对Thread.interrupted()的调用返回false

答案 3 :(得分:0)

SourceDataLine.write()通过此源数据线将音频数据写入混频器。此方法将阻塞,直到已写入请求的数据量为止。如果在写操作期间发生中断,它将被忽略并且中断标志被复位。为了解决这个问题,我们可以使用ExecutorService处理InterruptedException异常。

public void run() {
    int offset = 0;
    int remaining = cRawAudioBuffer.length;
    int length = WRITE_LENGTH;
    int bytesWritten = 0;
    cOutputLine.start();

    ExecutorService executor = Executors.newSingleThreadExecutor();

    while (remaining>0) {
        try {
            executor.submit(() -> {
                length = Math.min(length,remaining);
                bytesWritten = cOutputLine.write(cRawAudioBuffer,offset,length);
                offset += bytesWritten;
                remaining -= bytesWritten;
            }).get();
        } catch (InterruptedException interruptedException) {
            Thread.currentThread().interrupt();
        }
    }
    cOutputLine.close();
}