2

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

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() 来强制中断播放线程,并插入对 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

4 回答 4

5

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

于 2012-11-16T20:36:49.237 回答
1

您遇到的问题是,大部分时间都花在了cOutputLine.write不会因中断而停止的地方。这意味着您循环需要先写入一些数据,然后才能检查中断。如果此阻塞(由于缓冲区已满),它将永远不会检查中断。

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

于 2012-04-23T08:10:09.157 回答
0

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

于 2014-02-16T17:28:47.553 回答
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();
}
于 2018-12-07T00:53:27.930 回答