前言
最近辞职在找工作,才发现今年的就业形势太难了,在家里蹲的时候突然想写个小软件玩,要用到tts功能,就白嫖了微软的tts,不过tts返回的mp3比特率是48bit,java sound播放出来声音异常,不知道是哪里的问题,这是一个悲伤的故事,没办法只好用jave修改比特率,其间无意间发现了一篇关于java sound 节律纯音 的文章,觉得挺有意思,又刚好在用酷狗的3D环绕音听歌(突然间有些怀念天天动听了,j2me时代的听歌神器,可惜凉了),就想到用声音合成的方式生成3D环绕音,经过小半天的研究后实现。
代码实现
1、参考文章
blog.mazhangjing.comhttps://blog.mazhangjing.com/2019/04/27/java_sound/ps:网站突然打不开了,不知道是网络还是服务器的问题
2、依赖
代码中使用的歌曲是MP3格式,java sound原生不支持的,需要依赖相关库,依赖后java sound会自动调用
mp3spi1.9.5.jar |
|
|
3.实现代码
代码会对整首歌曲进行处理,然后可以播放和储存为文件。如果要实时处理,则需要修改代码,javasound解码后读取缓冲区数据,处理后再喂给播放管道。
package util;
import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.Date;
public class AudioSynth1 extends JFrame {
private AudioFormat audioFormat;
private AudioInputStream audioInputStream;
private SourceDataLine sourceDataLine;
private static float sampleRate = 16000.0F;
private static int channels = 1;
//一个缓冲器,用于在 16000 sampsec 下保存 2 秒单声道数据和 1 秒立体声数据,用于 16 位采样
private byte[] audioData = new byte[16000 * 4];//处理前数据存放的缓冲区
private byte[] audioDataCopy = new byte[16000 * 4];//处理后数据存放的缓冲区
private final JButton generateBtn = new JButton("开始处理");
private final JButton playOrFileBtn = new JButton("播放/文件");
private final JLabel elapsedTimeMeter = new JLabel("0000");
private final JRadioButton stereoPingpong = new JRadioButton("3D旋转测试");
private final JRadioButton listen = new JRadioButton("直接播放",true);
private final JTextField fileName = new JTextField("E:/test.wav",10);
public static void main(String[] args){
new AudioSynth1();
}
public AudioSynth1(){
final JPanel controlButtonPanel = new JPanel();
controlButtonPanel.setBorder(BorderFactory.createEtchedBorder());
final JPanel synButtonPanel = new JPanel();
final ButtonGroup synButtonGroup = new ButtonGroup();
final JPanel centerPanel = new JPanel();
final JPanel outputButtonPanel = new JPanel();
outputButtonPanel.setBorder(BorderFactory.createEtchedBorder());
final ButtonGroup outputButtonGroup = new ButtonGroup();
playOrFileBtn.setEnabled(false);
generateBtn.addActionListener(e -> {
try {
playOrFileBtn.setEnabled(false);
//adddata("E:/我的文件/音乐/邓丽君 - 小城故事.mp3");
adddata("C:\\Users\\tangtang1600\\dist\\tem.mp3");
System.out.println(audioData.length);
new SynGen().getSyntheticData(audioData, audioDataCopy);
playOrFileBtn.setEnabled(true);
} catch (Exception ex) {
ex.printStackTrace();
}
});
playOrFileBtn.addActionListener(e -> playOrFileData());
controlButtonPanel.add(generateBtn);
controlButtonPanel.add(playOrFileBtn);
controlButtonPanel.add(elapsedTimeMeter);
synButtonGroup.add(stereoPingpong);
synButtonPanel.setLayout(new GridLayout(0,1));
synButtonPanel.add(stereoPingpong);
centerPanel.add(synButtonPanel);
outputButtonGroup.add(listen);
JRadioButton file = new JRadioButton("文件");
outputButtonGroup.add(file);
outputButtonPanel.add(listen,BorderLayout.WEST);
outputButtonPanel.add(file, BorderLayout.CENTER);
outputButtonPanel.add(fileName,BorderLayout.EAST);
getContentPane().add(controlButtonPanel,BorderLayout.NORTH);
getContentPane().add(centerPanel, BorderLayout.CENTER);
getContentPane().add(outputButtonPanel, BorderLayout.SOUTH);
setTitle("Copyright 2023,tangtang1600");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300,275);
setVisible(true);
}
private void adddata(String filePath) throws Exception{
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File(filePath));
// 文件编码
AudioFormat audioFormat = audioInputStream.getFormat();
// 转换文件编码
if (audioFormat.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
sampleRate = audioFormat.getSampleRate();
channels = audioFormat.getChannels();
System.out.println("sampleRate:" + sampleRate);
System.out.println("channels:" + channels);
audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate, 16, audioFormat.getChannels(), audioFormat.getChannels() * 2, sampleRate, true);
// 将数据流也转换成指定编码
audioInputStream = AudioSystem.getAudioInputStream(audioFormat, audioInputStream);
}
// 打开输出设备
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED);
// 使数据行得到一个播放设备
SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
// 将数据行用指定的编码打开
sourceDataLine.open(audioFormat);
// 使数据行得到数据时就开始播放
for (AudioFormat format : dataLineInfo.getFormats()) {
System.out.println("信息:" + format.toString());
}
sourceDataLine.start();
int bytesPerFrame = audioInputStream.getFormat().getFrameSize();
// 将流数据逐渐写入数据行,边写边播
int numBytes = 1024 * bytesPerFrame;
byte[] audioBytes = new byte[numBytes];
while (audioInputStream.read(audioBytes) != -1) {
audioData = ArrayUtil.addAllforByte(audioData,audioBytes);
}
sourceDataLine.drain();
sourceDataLine.close();
audioInputStream.close();
if (channels == 2) {
audioDataCopy = new byte[audioData.length];
}else {
audioDataCopy = new byte[audioData.length*2];
}
System.out.println("size:" + audioDataCopy.length);
}
private void playOrFileData() {
try{
InputStream byteArrayInputStream = new ByteArrayInputStream(audioDataCopy);
boolean bigEndian = true;
boolean signed = true;
//Allowable 8000,11025,16000,22050,44100
int sampleSizeInBits = 16;
if (channels == 1){
channels = 2;
}
audioFormat = new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
audioInputStream = new AudioInputStream(byteArrayInputStream, audioFormat, audioDataCopy.length/audioFormat.getFrameSize());
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
System.out.println("listen isSelected:" + listen.isSelected());
if(listen.isSelected()){
new ListenThread().start();
} else{
generateBtn.setEnabled(false);
playOrFileBtn.setEnabled(false);
try{
File file = new File(fileName.getText());
if (!file.exists()){
file.createNewFile();
}
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE,file );
}catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
generateBtn.setEnabled(true);
playOrFileBtn.setEnabled(true);
}
}catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
class ListenThread extends Thread{
byte[] playBuffer = new byte[16384];
public void run(){
try{
generateBtn.setEnabled(false);
playOrFileBtn.setEnabled(false);
sourceDataLine.open(audioFormat);
sourceDataLine.start();
int cnt;
long startTime = new Date().getTime();
while((cnt = audioInputStream.read(playBuffer, 0, playBuffer.length)) != -1){
if(cnt > 0){
sourceDataLine.write(playBuffer, 0, cnt);
}
}
sourceDataLine.drain();
int elapsedTime = (int)(new Date().getTime() - startTime);
elapsedTimeMeter.setText("" + elapsedTime);
sourceDataLine.stop();
sourceDataLine.close();
generateBtn.setEnabled(true);
playOrFileBtn.setEnabled(true);
}catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
}
class SynGen{
ByteBuffer byteBufferCopy;
ByteBuffer byteBuffer;
ShortBuffer shortBufferCopy;
ShortBuffer shortBuffer;
int byteLength;
public void getSyntheticData(byte[] synDataBuffer,byte[] synDataBuffercopy){
byteBufferCopy = ByteBuffer.wrap(synDataBuffercopy);
shortBufferCopy = byteBufferCopy.asShortBuffer();
byteBuffer = ByteBuffer.wrap(synDataBuffer);
shortBuffer = byteBuffer.asShortBuffer();
byteLength = synDataBuffer.length;
stereoSurringRound();
}
public void stereoSurringRound(){
//channels = 2;//声道
int bytesPerSamp =channels*2;//帧所包含字节,基于声道,声道数*2
int sampLength = byteLength/bytesPerSamp;//帧数
double leftGain =0-sampleRate;//左增益-44100
double rightGain =0;//右增益0 44100-sampleRate
//我这里采用的mp3的采样率是44100,增益值设置为-44100到0
double leftGaintemp =0-sampleRate;;
double rightGaintemp =0;
System.out.println("sampLength:"+sampLength);
//将整首歌依据帧数分成28个步长,4个步长为一组环绕(趋向左-左-趋向右-右)
//既一首歌环绕28/4=7次
int stepLength = (int)sampLength/28;
System.out.println("step:"+stepLength);
int totalLength = 0;
for(int cnt = 0; cnt < sampLength; cnt++){
//根据帧数判断
if(totalLength>= 0 && totalLength < stepLength ){
//趋向左
leftGaintemp -= leftGain/stepLength;//0
rightGaintemp += leftGain/stepLength;//-> -44100
}
if(totalLength>= stepLength && totalLength <stepLength*2 ){
//左边增益
leftGaintemp = rightGain;//0
rightGaintemp = leftGain;//-44100
}
if(totalLength >= stepLength*2 && totalLength < stepLength*3){
//趋向右
leftGaintemp += leftGain/stepLength;//-> -44100
rightGaintemp -= leftGain/stepLength;//->0
}
if(totalLength >= stepLength*3 && totalLength < stepLength*4){
//右边增益
leftGaintemp = leftGain;// -44100
rightGaintemp = rightGain;//0
}
//为左通道生成数据
short sinValue =0;
if (channels == 2) {
sinValue= shortBuffer.get(cnt * 2);
}else {
sinValue= shortBuffer.get(cnt);
}
shortBufferCopy.put(
(short)(sinValue/sampleRate*(leftGaintemp+sampleRate)));
//为右通道生成数据
if (channels == 2) {
sinValue = shortBuffer.get(cnt * 2 + 1);
}
shortBufferCopy.put(
(short)(sinValue/sampleRate*(rightGaintemp+sampleRate)));
if(totalLength == stepLength*4-1){
totalLength = -1;
}
totalLength++;
}
}
}}
4、附件
mp3依赖包和代码里用到的歌