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音频处理的示例。

相关推荐
一只小闪闪11 分钟前
langchain4j搭建失物招领系统(六)---实现失物查询功能-RAG使用
java·人工智能·后端
雷渊19 分钟前
RocketMQ生产者的消息被消费后会永久放在磁盘里吗?
java·后端·面试
2401_8906661326 分钟前
免费送源码:Java+ssm+MySQL 校园二手书销售平台设计与实现 计算机毕业设计原创定制
java·spring boot·python·mysql·小程序·php·课程设计
言小乔.35 分钟前
202527 | RabbitMQ-基础 | 队列 | Direct + Fanout + Topic 交换机 | 消息转换器
java·微服务·消息队列·rabbitmq·mq·消息中间件
SoFlu软件机器人1 小时前
高并发场景下的 Java 性能优化
java·开发语言·性能优化
今天不学习明天变拉吉1 小时前
分页查询列表每页1000条的优化
java·数据库·mysql·性能优化
green5+11 小时前
卡码网55:右旋字符串
java·开发语言
huangsu_1231 小时前
java+postgresql+swagger-单表批量和循环insert、delete操作(八)
java·开发语言·数据库·postgresql
Java技术小馆2 小时前
如何处理消息堆积
java·面试·架构
zzzzz3692 小时前
SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”的解决方案
java·后端