Java SourceDataLine 播放音频
SourceDataLine 是 Java 音频 API 中的一部分,它用于表示音频数据的输出线。它是 DataLine 接口的子接口,而 DataLine 又是 Line 接口的子接口。这些接口都属于 javax.sound.sampled 包,用于从音频源读取音频数据并将其传输到音频输出设备(例如扬声器)。它提供了一种将音频数据写入音频缓冲区并将其传输到音频输出设备的机制。
SourceDataLine 主要用于播放音频数据。它提供了一个输出音频数据的通道,你可以将音频数据写入这个通道,然后通过音频设备播放出来。通常,你会使用 AudioOutputStream 或类似的类来创建 SourceDataLine 的实例。
音频格式 | 支持情况 |
---|---|
*.wav | (JDK 原生支持) |
*.pcm | (JDK 原生支持) |
*.au | (JDK 原生支持) |
*.aiff | (JDK 原生支持) |
*.mp3 | mp3spi.jar |
*.flac | jflac-codec.jar |
FloatControl.Type 是 Java Sound API 中的一个内部类,定义了 FloatControl 可以支持的控制类型。 FloatControl 提供了一种方式来控制以浮点数表示的参数,这些参数通常是音频系统的某些方面。常见的控制类型包括音量控制、平衡控制、增益控制等。
FloatControl.Type | 类型 |
---|---|
MASTER_GAIN | 主增益控制。调整整个音频系统的增益(音量)。 |
AUX_SEND | 辅助发送电平控制。调整发送到辅助总线的电平。 |
AUX_RETURN | 辅助返回电平控制。调整从辅助总线返回的电平。 |
REVERB_SEND | 混响发送电平控制。调整发送到混响效果器的电平。 |
PAN | 声道平衡控制。调整立体声左右声道的平衡。 |
BALANCE | 声道平衡控制。类似于 PAN 控制,调整音频信号在左右声道之间的平衡。 |
SAMPLE_RATE | 采样率控制。调整音频流的采样率。 |
1 依赖
<!-- 如果需要解码播放mp3文件则引入这个jar包 -->
<dependency>
<groupId>com.googlecode.soundlibs</groupId>
<artifactId>mp3spi</artifactId>
<version>1.9.5.4</version>
</dependency>
<!-- 如果需要解码播放flac文件则引入这个jar包 -->
<dependency>
<groupId>org.jflac</groupId>
<artifactId>jflac-codec</artifactId>
<version>1.5.2</version>
</dependency>
2 接口
package com.xu.music.player.player;
import java.io.File;
import java.net.URL;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
/**
* 音频播放
*
* @author hyacinth
* @date 2024年6月4日19点07分
* @since SWT-V1.0.0.0
*/
public interface Player {
/**
* 加载音频
*
* @param url 音频文件url
* @throws Exception 异常
* @date 2019年10月31日19:06:39
*/
void load(URL url) throws Exception;
/**
* 加载音频
*
* @param file 音频文件
* @throws Exception 异常
* @date 2019年10月31日19:06:39
*/
void load(File file) throws Exception;
/**
* 加载音频
*
* @param path 文件路径
* @throws Exception 异常
* @date 2019年10月31日19:06:39
*/
void load(String path) throws Exception;
/**
* 加载音频
*
* @param stream 音频文件输入流
* @throws Exception 异常
* @date 2019年10月31日19:06:39
*/
void load(AudioInputStream stream) throws Exception;
/**
* 加载音频
*
* @param encoding Encoding
* @param stream AudioInputStream
* @throws Exception 异常
* @date 2019年10月31日19:06:39
*/
void load(AudioFormat.Encoding encoding, AudioInputStream stream) throws Exception;
/**
* 加载音频
*
* @param format AudioFormat
* @param stream AudioInputStream
* @throws Exception 异常
* @date 2019年10月31日19:06:39
*/
void load(AudioFormat format, AudioInputStream stream) throws Exception;
/**
* 暂停播放
*
* @date 2019年10月31日19:06:39
*/
void pause();
/**
* 继续播放
*
* @param duration 音频位置
* @date 2019年10月31日19:06:39
*/
void resume(long duration);
/**
* 开始播放
*
* @date 2019年10月31日19:06:39
*/
void play();
/**
* 结束播放
*
* @date 2019年10月31日19:06:39
*/
void stop();
/**
* 设置音量
*
* @param volume 音量
* @date 2019年10月31日19:06:39
*/
void volume(float volume);
/**
* 获取音频播放位置
*
* @return 播放位置
* @date 2019年10月31日19:06:39
*/
double position();
/**
* 获取音频总时长
*
* @return 音频总时长
* @date 2019年10月31日19:06:39
*/
double duration();
/**
* 是否正在播放
*
* @return 是否正在播放
* @date 2019年10月31日19:06:39
*/
boolean playing();
/**
* 是否正在
*
* @return 是否正在播放
* @date 2019年10月31日19:06:39
*/
boolean pausing();
}
3 实现
3.1 SdlFftPlayer.java
package com.xu.music.player.player;
import java.io.File;
import java.net.URL;
import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.xu.music.player.constant.Constant;
import com.xu.music.player.hander.DataBaseError;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;
/**
* SourceDataLine 音频播放
*
* @author hyacinth
* @date 2024年6月4日19点07分
* @since SWT-V1.0.0.0
*/
@Slf4j
public class SdlFftPlayer implements Player {
/**
* 原始数据
*/
protected static final Deque<Double> SRC = new LinkedList<>();
/**
* 频谱
*/
public static final Deque<Double> TRANS = new LinkedList<>();
/**
* 线程池
*/
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1);
/**
* FFT
*/
private final FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
/**
* SourceDataLine
*/
private SourceDataLine data = null;
/**
* AudioInputStream
*/
private AudioInputStream audio = null;
/**
* 音频时长
*/
private double duration = 0.0D;
/**
* FloatControl
*/
private FloatControl control = null;
/**
* 暂停
*/
private volatile boolean paused = false;
/**
* 播放
*/
private volatile boolean playing = false;
private SdlFftPlayer() {
}
public static SdlFftPlayer create() {
return SingletonHolder.PLAYER;
}
private static class SingletonHolder {
private static final SdlFftPlayer PLAYER = new SdlFftPlayer();
}
@Override
public void load(URL url) throws Exception {
if (this.playing) {
stop();
}
load(AudioSystem.getAudioInputStream(url));
}
@Override
public void load(File file) throws Exception {
if (this.playing) {
stop();
}
if (!file.exists()) {
throw new DataBaseError("File does not exist");
}
String name = file.getName();
if (CharSequenceUtil.endWithIgnoreCase(name, ".mp3")) {
AudioInputStream stream = new MpegAudioFileReader().getAudioInputStream(file);
load(stream);
return;
}
if (CharSequenceUtil.endWithIgnoreCase(name, ".flac")) {
AudioInputStream stream = AudioSystem.getAudioInputStream(file);
load(stream);
return;
}
load(AudioSystem.getAudioInputStream(file));
}
@Override
public void load(String path) throws Exception {
if (this.playing) {
stop();
}
load(new File(path));
}
@Override
public void load(AudioInputStream stream) throws Exception {
if (this.playing) {
stop();
}
AudioFormat format = stream.getFormat();
format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate(), 16, format.getChannels(),
format.getChannels() * 2, format.getSampleRate(), false);
stream = AudioSystem.getAudioInputStream(format, stream);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, stream.getFormat(), AudioSystem.NOT_SPECIFIED);
data = (SourceDataLine) AudioSystem.getLine(info);
data.open(stream.getFormat());
this.audio = stream;
}
@Override
public void load(AudioFormat.Encoding encoding, AudioInputStream stream) throws Exception {
if (this.playing) {
stop();
}
load(AudioSystem.getAudioInputStream(encoding, stream));
}
@Override
public void load(AudioFormat format, AudioInputStream stream) throws Exception {
if (this.playing) {
stop();
}
load(AudioSystem.getAudioInputStream(format, stream));
}
@Override
public void pause() {
this.paused = true;
}
@Override
public void resume(long duration) {
this.paused = false;
synchronized (this) {
notifyAll();
}
}
private void start() {
try {
this.playing = true;
this.data.start();
if (this.data.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
control = (FloatControl) this.data.getControl(FloatControl.Type.MASTER_GAIN);
}
this.duration = getAudioDuration(this.audio, this.audio.getFormat());
byte[] buff = new byte[4];
int channels = this.audio.getFormat().getChannels();
float rate = this.audio.getFormat().getSampleRate();
while (audio.read(buff) != -1 && playing) {
synchronized (this) {
while (this.paused) {
wait();
}
}
setSpectrum(buff, channels, (int) rate);
this.data.write(buff, 0, 4);
}
data.drain();
data.stop();
} catch (Exception e) {
log.error("SdlFftPlayer 播放异常!", e);
}
}
@Override
public void play() {
if (this.playing) {
return;
}
if (null == this.audio || null == this.data) {
return;
}
EXECUTOR.submit(this::start);
}
@Override
public void stop() {
this.playing = false;
if (null == this.audio || null == this.data) {
return;
}
this.data.stop();
IoUtil.close(this.audio);
IoUtil.close(this.data);
}
@Override
public void volume(float volume) {
if (null == control) {
return;
}
if (volume < control.getMinimum() || volume > control.getMaximum()) {
return;
}
control.setValue(volume);
}
@Override
public double position() {
return data.getFramePosition();
}
@Override
public double duration() {
return this.duration;
}
@Override
public boolean playing() {
return this.playing;
}
@Override
public boolean pausing() {
return this.paused;
}
/**
* 计算音频时长
*
* @param audio 音频流
* @param format 音频格式
* @return 音频时长
* @date 2019年10月31日19:06:39
*/
public static double getAudioDuration(AudioInputStream audio, AudioFormat format) {
return audio.getFrameLength() * format.getFrameSize() / (format.getSampleRate() * format.getChannels() * (format.getSampleSizeInBits() / 8.0));
}
private void setSpectrum(byte[] buff, int channels, int sample) {
if (buff.length != 4) {
return;
}
// Stereo
if (channels == 2) {
if (sample == 16) {
short left = (short) ((buff[1] << 8) | (buff[0] & 0xFF));
short right = (short) ((buff[3] << 8) | (buff[2] & 0xFF));
putSrc((left + right) / 2.0 / 32768.0);
return;
}
// Assuming 8-bit samples
double left = (buff[0] & 0xFF) / 128.0 - 1.0;
double right = (buff[1] & 0xFF) / 128.0 - 1.0;
putSrc((left + right) / 2.0);
return;
}
// Mono
if (sample == 16) {
putSrc((short) ((buff[1] << 8) | (buff[0] & 0xFF)) / 32768.0);
return;
}
// Assuming 8-bit samples
putSrc((buff[0] & 0xFF) / 128.0 - 1.0);
}
private void putDst(double value) {
TRANS.add(value);
if (TRANS.size() > Constant.SPECTRUM_TOTAL_NUMBER) {
TRANS.removeFirst();
}
}
private void putSrc(double value) {
synchronized (SRC) {
SRC.add(value);
if (SRC.size() > Constant.SPECTRUM_TOTAL_NUMBER) {
SRC.removeFirst();
Complex[] complex = fft.transform(SRC.stream().mapToDouble(Double::doubleValue).toArray(), TransformType.FORWARD);
for (int i = 0; i < Constant.SPECTRUM_TOTAL_NUMBER; i++) {
putDst(complex[i].abs());
}
}
}
}
}
3.2 SdlPlayer.java
package com.xu.music.player.player;
import java.io.File;
import java.net.URL;
import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.xu.music.player.constant.Constant;
import com.xu.music.player.hander.DataBaseError;
import com.xu.music.player.hander.MusicPlayerError;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* SourceDataLine 音频播放
*
* @author hyacinth
* @date 2024年6月4日19点07分
* @since SWT-V1.0.0.0
*/
public class SdlPlayer implements Player {
/**
* 频谱
*/
public static final Deque<Double> SRC = new LinkedList<>();
/**
* 线程池
*/
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1);
/**
* SourceDataLine
*/
private SourceDataLine data = null;
/**
* AudioInputStream
*/
private AudioInputStream audio = null;
/**
* FloatControl
*/
private FloatControl control = null;
/**
* 音频时长
*/
private double duration = 0.0D;
/**
* 暂停
*/
private volatile boolean paused = false;
/**
* 播放
*/
private volatile boolean playing = false;
private SdlPlayer() {
}
public static SdlPlayer create() {
return SingletonHolder.PLAYER;
}
private static class SingletonHolder {
private static final SdlPlayer PLAYER = new SdlPlayer();
}
@Override
public void load(URL url) throws Exception {
if (this.playing) {
stop();
}
load(AudioSystem.getAudioInputStream(url));
}
@Override
public void load(File file) throws Exception {
if (this.playing) {
stop();
}
if (!file.exists()) {
throw new DataBaseError("File does not exist");
}
String name = file.getName();
if (CharSequenceUtil.endWithIgnoreCase(name, ".mp3")) {
AudioInputStream stream = new MpegAudioFileReader().getAudioInputStream(file);
load(stream);
return;
}
if (CharSequenceUtil.endWithIgnoreCase(name, ".flac")) {
AudioInputStream stream = AudioSystem.getAudioInputStream(file);
load(stream);
return;
}
load(AudioSystem.getAudioInputStream(file));
}
@Override
public void load(String path) throws Exception {
if (this.playing) {
stop();
}
load(new File(path));
}
@Override
public void load(AudioInputStream stream) throws Exception {
if (this.playing) {
stop();
}
AudioFormat format = stream.getFormat();
format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate(), 16, format.getChannels(),
format.getChannels() * 2, format.getSampleRate(), false);
stream = AudioSystem.getAudioInputStream(format, stream);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, stream.getFormat(), AudioSystem.NOT_SPECIFIED);
data = (SourceDataLine) AudioSystem.getLine(info);
data.open(stream.getFormat());
this.audio = stream;
}
@Override
public void load(AudioFormat.Encoding encoding, AudioInputStream stream) throws Exception {
if (this.playing) {
stop();
}
load(AudioSystem.getAudioInputStream(encoding, stream));
}
@Override
public void load(AudioFormat format, AudioInputStream stream) throws Exception {
if (this.playing) {
stop();
}
load(AudioSystem.getAudioInputStream(format, stream));
}
@Override
public void pause() {
this.paused = true;
}
@Override
public void resume(long duration) {
this.paused = false;
synchronized (this) {
notifyAll();
}
}
private void start() {
try {
this.playing = true;
this.data.start();
if (this.data.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
control = (FloatControl) this.data.getControl(FloatControl.Type.MASTER_GAIN);
}
this.duration = getAudioDuration(this.audio, this.audio.getFormat());
byte[] buff = new byte[4];
int channels = this.audio.getFormat().getChannels();
float rate = this.audio.getFormat().getSampleRate();
while (audio.read(buff) != -1 && playing) {
synchronized (this) {
while (this.paused) {
wait();
}
}
setSpectrum(buff, channels, (int) rate);
this.data.write(buff, 0, 4);
}
data.drain();
data.stop();
} catch (Exception e) {
throw new MusicPlayerError(e.getMessage(), e);
}
}
@Override
public void play() {
if (this.playing) {
return;
}
if (null == this.audio || null == this.data) {
return;
}
EXECUTOR.submit(this::start);
}
@Override
public void stop() {
this.playing = false;
if (null == this.audio || null == this.data) {
return;
}
this.data.stop();
IoUtil.close(this.audio);
IoUtil.close(this.data);
}
@Override
public void volume(float volume) {
if (null == control) {
return;
}
if (volume < control.getMinimum() || volume > control.getMaximum()) {
return;
}
control.setValue(volume);
}
@Override
public double position() {
return data.getFramePosition();
}
@Override
public double duration() {
return this.duration;
}
@Override
public boolean playing() {
return false;
}
@Override
public boolean pausing() {
return false;
}
/**
* 计算音频时长
*
* @param audio 音频流
* @param format 音频格式
* @return 音频时长
* @date 2019年10月31日19:06:39
*/
public static double getAudioDuration(AudioInputStream audio, AudioFormat format) {
return audio.getFrameLength() * format.getFrameSize() / (format.getSampleRate() * format.getChannels() * (format.getSampleSizeInBits() / 8.0));
}
private void setSpectrum(byte[] buff, int channels, int sample) {
if (buff.length != 4) {
return;
}
if (channels == 2) {
// Stereo
processStereoBuff(buff, sample);
return;
}
// Mono
processMonoBuff(buff, sample);
}
private void processStereoBuff(byte[] buff, int sample) {
if (sample == 16) {
// Left channel
put(convert16(buff, 0));
// Right channel
put(convert16(buff, 2));
return;
}
// Left channel
put(convert8(buff[0]));
// Right channel
put(convert8(buff[1]));
}
private void processMonoBuff(byte[] buff, int sample) {
if (sample == 16) {
put(convert16(buff, 0));
put(convert16(buff, 2));
return;
}
for (byte b : buff) {
put(b & 0xFF);
}
}
private double convert16(byte[] buff, int offset) {
return (short) ((buff[offset + 1] << 8) | (buff[offset] & 0xFF));
}
private double convert8(byte sample) {
return (sample & 0xFF) / 128.0 - 1.0;
}
private void put(double value) {
synchronized (SRC) {
SRC.add(value);
if (SRC.size() > Constant.SPECTRUM_TOTAL_NUMBER) {
SRC.removeFirst();
}
}
}
}
4 测试
package com.xu.music.player.player;
/**
* Java 音频播放
*
* @author hyacinth
* @date 2019年10月31日19:06:39
*/
public class Test {
public static void main(String[] args) throws Exception {
SourceDataLinePlayer player = SourceDataLinePlayer.createPlayer();
player.load("D:\\Kugou\\梦涵 - 加减乘除.mp3");
player.play();
}
}