Java SourceDataLine 播放音频

本文介绍了Java的SourceDataLine接口及其在音频播放中的应用,包括依赖的库、AudioInputStream的使用、音频格式支持、控制功能如FloatControl以及Player接口的实现和测试示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SourceDataLine 是 Java 音频 API 中的一部分,它用于表示音频数据的输出线。它是 DataLine 接口的子接口,而 DataLine 又是 Line 接口的子接口。这些接口都属于 javax.sound.sampled 包,用于从音频源读取音频数据并将其传输到音频输出设备(例如扬声器)。它提供了一种将音频数据写入音频缓冲区并将其传输到音频输出设备的机制。

SourceDataLine 主要用于播放音频数据。它提供了一个输出音频数据的通道,你可以将音频数据写入这个通道,然后通过音频设备播放出来。通常,你会使用 AudioOutputStream 或类似的类来创建 SourceDataLine 的实例。

音频格式支持情况
*.wav(JDK 原生支持)
*.pcm(JDK 原生支持)
*.au(JDK 原生支持)
*.aiff(JDK 原生支持)
*.mp3mp3spi.jar
*.flacjflac-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();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值