Java实现音频录音&播放机功能

Java实现一个简单的音频录音和播放功能,使用Swing创建图形用户界面,利用Java Sound API进行音频处理。下面是对此程序的详细剖析:
一、程序结构

程序主要由以下几个部分组成:

  • RecorderFrm类:主框架类,继承自JFrame,负责UI界面和主要逻辑

  • Recorder内部类:实现Runnable接口,负责录音功能

  • 多个辅助方法:如getAudioFormat(), playMusic(), save(), open()等

二、程序主要功能

2.1 录音功能

  • 使用TargetDataLine从麦克风捕获音频数据
  • 音频数据存储在ByteArrayOutputStream中
  • 录音过程在单独线程中运行,避免阻塞UI

2.2 播放功能

可以选择并播放已保存的WAV文件;使用SourceDataLine进行音频播放。

2.3 文件操作

  • 可以将录音保存为WAV格式文件

  • 可以打开已有的WAV文件进行播放

  1. 关键组件分析
    3.1 音频格式设置
    getAudioFormat()方法定义了音频参数:
    编码:PCM_SIGNED
    采样率:44100Hz
    采样精度:16位
    单声道(声道数:1)
    帧大小:2字节
    帧率:44100
    字节序:小端(Little-endian)

3.2 录音流程

创建TargetDataLine

打开并启动数据线

循环读取音频数据到缓冲区

将数据写入ByteArrayOutputStream

停止时关闭资源

3.3 播放流程

选择音频文件

创建AudioInputStream

获取音频格式

创建并打开SourceDataLine

从输入流读取数据并写入数据线播放

3.4 文件保存

将ByteArrayOutputStream转换为ByteArrayInputStream

创建AudioInputStream

使用AudioSystem.write()方法将音频写入WAV文件

Java实现音频录音和播放机功能程序的源码:

cpp 复制代码
package Record;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
public class RecorderFrm  extends JFrame implements ActionListener {
	//队列queue,放置声道音频数据。录音用一个话筒,单声道。
	Queue<Short> qAudio = new LinkedList<>();
	//定义字节数组输出流。 用于保存音频数据。
    ByteArrayOutputStream bOutStm = null;
    Boolean stopflag = false; //停止录音的标志,用于控制录音线程的运行

    //定义所需要的组件
    JPanel panelA, panelB;
    JButton recordeBtn, stopBtn, playBtn;

    //构造函数
    public RecorderFrm() {
        //面板组件初始化
    	 panelA = new JPanel();
        panelB = new JPanel();

        //面板panelB上按钮组件的设置
        recordeBtn = new JButton("开始录音");
        recordeBtn.addActionListener(this); //给按钮注册监听
        recordeBtn.setActionCommand("recorde");
        
        stopBtn = new JButton("停止录音");
        stopBtn.addActionListener(this); //给按钮注册监听
        stopBtn.setActionCommand("stop");
        
        playBtn = new JButton("播放录音");
        playBtn.addActionListener(this); //给按钮注册监听
        playBtn.setActionCommand("play");
        
        //设置面板panelB
        panelB.setLayout(null);
        panelB.setLayout(new GridLayout(1, 4, 10, 10));
        panelB.add(recordeBtn);
        panelB.add(stopBtn);
        panelB.add(playBtn);
        //设置按钮的初始属性
        recordeBtn.setEnabled(true);
        stopBtn.setEnabled(false);
        playBtn.setEnabled(false);
        
        //在主窗体上布置面板组件
        add(panelA, BorderLayout.CENTER);
        add(panelB, BorderLayout.SOUTH);
        
        //设置窗口的属性
        setSize(400, 160);
        setTitle("音频录音播放机");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("recorde")) {
            //点击录音按钮后的动作
            recordeBtn.setEnabled(false);
            stopBtn.setEnabled(true); //启用停止按钮
            playBtn.setEnabled(false);
            recorde(); //调用录音的方法
        } else if (e.getActionCommand().equals("stop")) {
            //点击停止录音按钮的动作
            recordeBtn.setEnabled(true);
            stopBtn.setEnabled(false);
            playBtn.setEnabled(true); 
            stop(); //调用停止录音的方法
        } else if (e.getActionCommand().equals("play")) {
            play(); //调用播放录音的方法
        }
    }

    //开始录音
    public void recorde() {
    	//创建播放录音的线程
        Recorder recorder = new Recorder();
        Thread recorderTrd = new Thread(recorder);
        recorderTrd.start();
    }

    //停止录音。设置"停止"标志,并保存录音
    public void stop() { 
    	stopflag = true; 
    	save(); //保存音频文件到磁盘
    }

    //播放录音
    public void play() {
    	File file = open(); //选择音频文件
    	System.out.println("正在播放录音... ");
    	playMusic(file); //打开音频文件播放音乐
    }


    //保存录音到磁盘
    public void save() {
        File file = null; //文件名
        FileNameExtensionFilter filter; //后缀名过滤器
    	JFileChooser chooser;
    	
    	//创建文件选择器,指定当前目录为"D:\test"。
    	chooser = new JFileChooser("D:\\test");
    	//文件后缀名过滤器
    	filter = new FileNameExtensionFilter("音频文件(*.wav,*.WAV)","wav","WAV");   
    	chooser.setFileFilter(filter); //设置文件扩展名
    	
        ByteArrayInputStream bais = null; //定义字节数组输入流
        AudioInputStream ais = null; //定义音频输入流
        AudioFormat fmt = getAudioFormat(); //取得录音格式
        byte audioData[] = bOutStm.toByteArray();
        bais = new ByteArrayInputStream(audioData);
        long len = audioData.length / fmt.getFrameSize();
        ais = new AudioInputStream(bais,fmt,len);

        //写入文件
        try {
            int value=chooser.showDialog(this,"保存文件");                           
            if (value==JFileChooser.APPROVE_OPTION) { //判断用户是否保存文件
            /*  使用文件句柄(对象),接收返回值。保存文件通常只需处理单选 ***/
            	file =  chooser.getSelectedFile();
            	String pathStr = file.getAbsolutePath();
            	/***检查path中是否已包含文件扩展名***/
            	boolean flag = false;
            	for (String extension : filter.getExtensions()) {
					if (pathStr.contains(extension)) {
						flag=true;
						break;
					}
				}
            	if (!flag) {
            		pathStr += ".wav"; //若不包含扩展名,添加扩展名
            		file = new File(pathStr); //重新创建文件句柄
            	}
            	
            	System.out.println("保存文件的绝对路径: "+pathStr);
            	//保存文件操作
            	AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file);
            } 
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭流
            try {
                if (bais != null) bais.close();
                if (ais != null)  ais.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

	//打开,选择录音文件
	private File open() {
		File path = null;
		//设置文件过滤器
		FileNameExtensionFilter filter = null;
		filter = new FileNameExtensionFilter("音频文件(*.wav)","wav");
		JFileChooser chooser = new JFileChooser("D:\\test");//默认路径
		chooser.setFileFilter(filter);
		chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
		chooser.setMultiSelectionEnabled(false); //单选文件
		int result = chooser.showDialog(this, "打开音频文件");
		if (result==JFileChooser.APPROVE_OPTION) {
			path = chooser.getSelectedFile();
		}
		return path;
	}
	
    public static void main(String[] args) {
    	new RecorderFrm(); //创建实例
    }
    
	private void playMusic(File path){
		AudioInputStream inStream;
		AudioFormat format;
		SourceDataLine sourceDataLine;
		DataLine.Info info;
		try{
			int count;
			byte buf[] = new byte[1024];
			
			//获取音频输入流
		    inStream = AudioSystem.getAudioInputStream(path);
			//获取音频的编码格式
			format = inStream.getFormat();

			info = new DataLine.Info(SourceDataLine.class,
					format,AudioSystem.NOT_SPECIFIED);
			
			sourceDataLine = (SourceDataLine)AudioSystem.getLine(info);
			
			sourceDataLine.open(format);
			sourceDataLine.start();	
			//播放音频
			while((count = inStream.read(buf,0,buf.length)) != -1){
				sourceDataLine.write(buf,0,count);			
			}
			//播放结束,释放资源
			sourceDataLine.drain();
			sourceDataLine.close();
			inStream.close();
		}catch(UnsupportedAudioFileException ex){
			ex.printStackTrace();
		}catch(LineUnavailableException ex){
			ex.printStackTrace();
		}catch(IOException ex){
			ex.printStackTrace();
		}
	}
    
    //设置AudioFormat的参数
    public AudioFormat getAudioFormat() {
        //参数Encoding:音频编码格式
        AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
        /***其它参数说明
        float sampleRate = 44100f; //采样率
        int sampleSize = 16; //SampleSizeInBits: 采样精度
        int channels = 2; //声道数
        //FrameSize: 帧大小,每帧的字节数。
        int frameSize=4;//frameSize=(sampleSize/8)*channels
        float frameRate = 44100f; //FrameRate: 帧频,每秒的帧数。
        boolean bigEndian = false;
         * BigEndian: 与传输和存储相关的字节序列标志。
         * 有两种类型:Big-endian和Little-endian
         * 值false表示Little-endian;值true表示Big-endian。
         * ***/
        //AudioFormat(encoding,sampleRate,sampleSizeInBits,channels,frameSize,frameRate,bigEndian)
        //AudioFormat format = new AudioFormat(encoding, 44100, 16, 2, 4, 44100, false); 立体声
        AudioFormat format = new AudioFormat(encoding, 44100, 16, 1, 2, 44100, false);//单声道
        return format;
    }

    //录音类,因为要用到主类中的变量,所以将其做成内部类
    class Recorder implements Runnable {
    	DataLine.Info info = null;
        AudioFormat format = null;  //定义音频格式
        //定义目标数据行,可以从中读取音频数据,该 TargetDataLine 接口提供从目标数据行的缓冲区读取所捕获数据的方法。
        TargetDataLine tdLine = null;
    	
        //定义存放录音的字节数组,作为缓冲区
        byte bts[] = new byte[2048];
        
        //将字节数组包装到流里,最终存入到bOutStm中
        @Override //重写run函数
        public void run() {
        	bOutStm = new ByteArrayOutputStream();
            try {
            	format = getAudioFormat();
                info = new DataLine.Info(TargetDataLine.class, format);
                tdLine = (TargetDataLine) (AudioSystem.getLine(info));
                //打开具有指定格式的行,这样可使行获得所有所需的系统资源并变得可操作。
                tdLine.open(format);
                //允许某一数据行执行数据 I/O
                tdLine.start();
                /******/
                System.out.println("正在录音...");
                stopflag = false;
                int size = format.getFrameSize();
                while (stopflag != true) {
                    //当停止录音按钮未按下时,该线程一直执行
                    //从目标数据行读取音频数据到字节数组bts,cnt 是实际读取的字节数
                    int cnt = tdLine.read(bts, 0, bts.length);
                    if (cnt > 0) {
                    	bOutStm.write(bts, 0, cnt);
                    }
                 
                }
                System.out.println("录音结束!");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try { //关闭打开的字节数组流
                    if (bOutStm != null) bOutStm.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                	tdLine.drain();
                	tdLine.close();
                }
            }
        }
    }
}

总结 本例程的技术要点:

  • 使用Java Sound API进行底层音频操作

  • 多线程处理录音过程

  • Swing构建用户界面

  • 字节流处理音频数据

  • 文件对话框进行文件操作

这个程序很好地演示了Java中音频录制和播放的基本原理,代码结构清晰,适合作为学习Java音频处理的示例。

相关推荐
我叫汪枫1 分钟前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao9 分钟前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
Swift社区2 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT2 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy2 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss4 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续4 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0444 小时前
ReAct模式解读
java·ai
轮到我狗叫了5 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法