-
Bug
-
Resolution: Fixed
-
P3
-
8, 11, 17, 19, 20, 21
-
b22
-
x86_64
-
windows_10
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8323361 | 21.0.3-oracle | Renjith Kannath Pariyangad | P3 | Resolved | Fixed | b01 |
JDK-8319742 | 21.0.2 | Renjith Kannath Pariyangad | P3 | Resolved | Fixed | b07 |
JDK-8319920 | 17.0.11-oracle | Renjith Kannath Pariyangad | P3 | Resolved | Fixed | b01 |
JDK-8321246 | 17.0.11 | Goetz Lindenmaier | P3 | Resolved | Fixed | b01 |
JDK-8319921 | 11.0.23-oracle | Renjith Kannath Pariyangad | P3 | Resolved | Fixed | b01 |
JDK-8322785 | 11.0.23 | Amos SHI | P3 | Resolved | Fixed | b01 |
x64 / Windows 10 / Java 17.0.6 or 19.0.2
a Headset with a Microphone to test sound recording.
A DESCRIPTION OF THE PROBLEM :
TargetDataLines become unusable (throw LineUnavailableException) if AudioSystem was called before creating a JFileChooser or COM initialization.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Use AudioSystem e.g. AudioSystem.getMixerInfo()
2. Create a JFileChooser or init COM library
3. Lock / unlock screen
4. Try to open TargetDataLine for recording
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
TargetDataLine is successfully opened
ACTUAL -
Exception is thrown:
javax.sound.sampled.LineUnavailableException: line with format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported.
at java.desktop/com.sun.media.sound.DirectAudioDevice$DirectDL.implOpen(DirectAudioDevice.java:484)
at java.desktop/com.sun.media.sound.AbstractDataLine.open(AbstractDataLine.java:115)
---------- BEGIN SOURCE ----------
// run each test separately in debug mode with a breakpoint to manually lock / unlock screen before resuming
// source: https://github.com/evgenii-orel/java-recording-issue-after-screen-lock/blob/master/src/test/java/com/epam/sound/RecordChannelLockingTest.java
package com.epam.sound;
import com.sun.jna.platform.win32.Ole32;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.TargetDataLine;
import javax.swing.JFileChooser;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Demonstrates breaking {@link TargetDataLine}s so that it always fails with {@link LineUnavailableException}:
* <br>
* Option 1. subsequently use {@link AudioSystem}, {@link JFileChooser} in any threads, lock / unlock screen
* then any following recording attempt fails.
* <br>
* Option 2. subsequently use {@link AudioSystem}, COM initialization in the same thread, lock / unlock screen
* then any following recording attempt fails in that thread.
* <br>
* OS: Windows 10, Java: 17, 19.0.2
* <br>
* The issue somehow relates to {@link sun.awt.shell.ShellFolder}s usage in the {@link JFileChooser} or COM initialization.
* A workaround could be calling a {@link JFileChooser} in any thread
* or COM initialization in the same thread before using {@link AudioSystem}.
*/
@Disabled // manual tests
class RecordChannelLockingTest {
private final ExecutorService thread1 = Executors.newSingleThreadExecutor();
private final ExecutorService thread2 = Executors.newSingleThreadExecutor();
private final Runnable maliciousMethod = this::fileChooser;
// private final Runnable maliciousMethod = this::initCom;
@AfterEach
void shutdown() {
thread1.shutdown();
thread2.shutdown();
}
// AudioSystem is accessed before JFileChooser / COM
@Test // fails
void shouldRecordIfBeforeInTheSameThread() throws ExecutionException, InterruptedException {
thread1.submit(this::record).get();
thread1.submit(maliciousMethod).get();
// breakpoint, lock / unlock screen before resuming
assertDoesNotThrow(() -> thread1.submit(this::record).get());
}
@Test // fails
void shouldRecordIfBeforeInAnyThread() throws ExecutionException, InterruptedException {
thread2.submit(this::record).get();
thread1.submit(maliciousMethod).get();
// breakpoint, lock / unlock screen before resuming
assertDoesNotThrow(() -> thread1.submit(this::record).get());
}
@Test // fails for file chooser, but successful for COM
void shouldRecordIfBeforeInAnyThread2() throws ExecutionException, InterruptedException {
thread2.submit(this::record).get();
thread1.submit(maliciousMethod).get();
// breakpoint, lock / unlock screen before resuming
assertDoesNotThrow(() -> thread2.submit(this::record).get());
}
// AudioSystem is accessed after JFileChooser / COM
@Test // successful
void shouldRecordIfAfterInTheSameThread() throws ExecutionException, InterruptedException {
thread1.submit(maliciousMethod).get();
thread1.submit(this::record).get();
// breakpoint, lock / unlock screen before resuming
assertDoesNotThrow(() -> thread1.submit(this::record).get());
}
@Test // successful for file chooser, but fails for COM
void shouldRecordIfAfterInOtherThread() throws ExecutionException, InterruptedException {
thread1.submit(maliciousMethod).get();
thread2.submit(this::record).get();
// breakpoint, lock / unlock screen before resuming
assertDoesNotThrow(() -> thread1.submit(this::record).get());
}
@Test // successful for file chooser, but fails for COM
void shouldRecordIfAfterInOtherThreadAndBeforeInTeSame() throws ExecutionException, InterruptedException {
thread1.submit(maliciousMethod).get();
thread2.submit(this::record).get();
thread2.submit(maliciousMethod).get();
// breakpoint, lock / unlock screen before resuming
assertDoesNotThrow(() -> thread2.submit(this::record).get());
assertDoesNotThrow(() -> thread1.submit(this::record).get());
}
void initCom() {
System.out.println("----");
System.out.println("Initializing COM");
System.out.println("Thread: " + Thread.currentThread().getName());
Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_APARTMENTTHREADED);
}
void fileChooser() {
System.out.println("----");
System.out.println("File chooser created with hash " + new JFileChooser().hashCode());
System.out.println("Thread: " + Thread.currentThread().getName());
}
void record() {
try {
System.out.println("----");
System.out.println("Recording...");
System.out.println("Thread: " + Thread.currentThread().getName());
Mixer mixer = getMixer();
System.out.println("Mixer: " + mixer.getMixerInfo().getName());
Line.Info lineInfo = mixer.getTargetLineInfo()[0];
TargetDataLine line = (TargetDataLine) mixer.getLine(lineInfo);
line.open();
line.close();
System.out.println("Recording stopped.");
} catch (LineUnavailableException e) {
throw new IllegalStateException(e);
}
}
Mixer getMixer() {
return Arrays.stream(AudioSystem.getMixerInfo())
.map(AudioSystem::getMixer)
.filter(this::isRecordingDevice)
.skip(1) // to skip the primary driver and choose one directly
.findAny()
.orElseThrow();
}
boolean isRecordingDevice(Mixer mixer) {
Line.Info[] lineInfos = mixer.getTargetLineInfo();
return lineInfos.length > 0 && lineInfos[0].getLineClass() == TargetDataLine.class;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Option 1: Create JFileChooser before first AudioSystem call in any thread.
Option 2: Initialize COM before first AudioSystem call per thread.
Option 3: (Worked only for our complex GUI app, not in the tests) Use Swing's EDT thread for AudioSystem calls.
FREQUENCY : always
- backported by
-
JDK-8319742 Invalid TargetDataLine after screen lock when using JFileChooser or COM library
- Resolved
-
JDK-8319920 Invalid TargetDataLine after screen lock when using JFileChooser or COM library
- Resolved
-
JDK-8319921 Invalid TargetDataLine after screen lock when using JFileChooser or COM library
- Resolved
-
JDK-8321246 Invalid TargetDataLine after screen lock when using JFileChooser or COM library
- Resolved
-
JDK-8322785 Invalid TargetDataLine after screen lock when using JFileChooser or COM library
- Resolved
-
JDK-8323361 Invalid TargetDataLine after screen lock when using JFileChooser or COM library
- Resolved
- links to
-
Commit openjdk/jdk11u-dev/d94b3f84
-
Commit openjdk/jdk17u-dev/2a019da4
-
Commit openjdk/jdk21u/7f02e4a8
-
Commit openjdk/jdk/613a3cc6
-
Review openjdk/jdk11u-dev/2365
-
Review openjdk/jdk17u-dev/2007
-
Review openjdk/jdk21u/326
-
Review openjdk/jdk/14898