我有一个循环调用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();
}
答案 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();
}