I'm playing around with javax.sound
libraries for the first time and I'm trying to record audio that is currently playing through my computer's default device, such as music or a video (not a microphone).
I might just be getting myself confused between TargetDataLine
and SourceDataLine
, but no matter what I try, all I can manage to do is record sound from the microphone.
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@Slf4j
@Service
public class AudioServiceImpl implements AudioService {
private final File wavFile = new File("output.wav");
private final AudioFileFormat.Type fileType = AudioFileFormat.Type.WAVE;
private TargetDataLine line;
@Override
public void start() {
final Mixer defaultMixer = getMixersWithAudioFormats().stream()
.filter(mixer -> mixer.getMixerInfo().getName().equals("Default Audio Device"))
.findFirst()
.orElseThrow(() -> new RuntimeException("No default audio device found"));
try {
final AudioInterface audioInterface = getAudioFormat(defaultMixer);
line = (TargetDataLine) AudioSystem.getLine(audioInterface.info());
line.open(line.getFormat());
line.start();
AudioInputStream audioInputStream = new AudioInputStream(line);
log.info("Recording...");
AudioSystem.write(audioInputStream, fileType, wavFile);
} catch (LineUnavailableException | IOException ex) {
ex.printStackTrace();
}
}
/**
* Find the first audio format where the line is supported.
*/
private AudioInterface getAudioFormat(final Mixer mixer) {
for (final Line.Info mixerInfo : mixer.getTargetLineInfo()) {
final AudioFormat[] formats = getFormats(mixerInfo);
for (final AudioFormat format : formats) {
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
if (AudioSystem.isLineSupported(info)) {
return new AudioInterface(format, info);
}
}
}
throw new RuntimeException("No supported audio format found");
}
/**
* Find all mixers with audio formats.
*/
private List<Mixer> getMixersWithAudioFormats() {
final List<Mixer> results = new LinkedList<>();
for (Mixer.Info info : AudioSystem.getMixerInfo()) {
final Mixer mixer = AudioSystem.getMixer(info);
final Line.Info[] lineInfos = mixer.getTargetLineInfo();
for (Line.Info lineInfo : lineInfos) {
if ((lineInfo instanceof final DataLine.Info dataLineInfo) && (dataLineInfo.getFormats().length > 0)) {
results.add(mixer);
}
}
}
return results;
}
/**
* Helper to grab audio formats from a line info.
*/
private AudioFormat[] getFormats(final Line.Info lineInfo) {
return lineInfo instanceof final DataLine.Info dataLineInfo ? dataLineInfo.getFormats() : null;
}
@PreDestroy
void finish() {
line.stop();
line.close();
}
}
The AudioInterface
is just a helper record
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.DataLine;
public record AudioInterface(AudioFormat format, DataLine.Info info) {
}
My understanding is that I'm correctly grabbing the Default Audio Device mixer, extracting the format out of it and loading that into the AudioSystem.getLine
method. I just would have thought that to end up recording from the microphone I'd have to have been using a different mixer (In my case, MacBook Pro Microphone) instead.
Where have I gone wrong here?