java 录音并包装成wav格式的文件方法-与-WAV解析-大小端-寄存器!,提取wav数据音频,一篇讲完!

5 篇文章 0 订阅
4 篇文章 0 订阅

简单了解什么是WAV?

它是微软公司为Windows系统开发的一种标准的数字音频可,直接存储声音波形,并且它的波形曲线还原的真实感很好,但是也有缺点:存储磁盘空间大,多用于存储简短的声音片段。

是PC机上最为流行的声音文件格式。


WAV解析样本

格式大小:采样率一般是:44.1K,16bit采样精度,存储:WAV格式大小 = 44.1KHz(采样率) X 16bit(采样位数) X 2(双声道) X  播放时间

WAV格式是没有压缩无损的,MP3格式是按1:12压缩保存的。

WAV结构

由三个块组成:如图:

前44个字节是音频文件的说明,也就是从44个字节后开始就是正式数据了

WAV文件'一般'由3个区块组成:RIFF chunk、Format chunk和Data chunk,当然不包括文件中还有一些附加块。

wav文件中的数据块是小端存储-但是Java以大端存储-所以在进制转换时要转换成大端:

Java大端:

默认网络传输字节为大端,java 全部为大端(与平台无关)

小端存储:字节或半字节的最低位字节(Least Significant Bit,LSB)放置于于内存最低位字节地址上。(通俗:低字节数据存储在低地址

大端存储:数据低位保存在内存的高地址中,数据的高位保存在内存的低地址中。

(通俗:低字节数据存储在低地址)

区别

1,简单:大端存储或小端存储都是由系统设计的,区别在于低地址存储的数据,因此可以写程序进行判断。

2,详情:计算机中以字节为存储单位,每个地址单元都对应着一个字节,对于大于8位的数据类型,就会产生在寄存器中存放顺序问题。

显示例子:

什么是寄存器(Register:

基本寄存器和移位寄存器两大类。基本寄存器只能并行送入数据,也只能并行输出。移位寄存器中的数据可以在移位脉冲作用下依次逐位右移或左移,数据既可以并行输入、并行输出,也可以串行输入、串行输出,还可以并行输入、串行输出,或串行输入、并行输出,十分灵活,用途也很广。


寄存器作用了解:是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。 

 寄存器又分为内部寄存器与外部寄存器,所谓内部寄存器,其实也是一些小的存储单元,也能存储数据。但同存储器相比,寄存器又有自己独有的特点: 

功能:是存储二进制,它是由具有存储功能的触发器组合起来构成的。

寄存器中:

        1,从前往后,先低字节,后高地址,一个存储单元,存放一个字节。

大端:

        十进制数字:4328719365

        十六进制:0x0102030405

        :[0x01(低地址),0x02,0x03,0x04,0x05]

注意:这里很好展示了第一个单元是低地址!

小端:

        还是一样十进制:4328719365

        :[0x05(低地址),0x04,0x03,0x02,0x01]

注意:无论是小端存储或者大端存储,它读取到的永远是:0x0102030405

小端转进制成大端:

我们看个例子:

//转成大端
public static byte[] int_To_Byte_BigEnding(int num) {
    byte[] D = new byte[4];
    D[3] = (byte) (num & 0xff);
    D[2] = (byte) (num >> 8 & 0xff);
    D[1] = (byte) (num >> 16 & 0xff);
    D[0] = (byte) (num >> 24 & 0xff);
    return D;
}

大端转小端:

//转成大端
public static byte[] int_To_Byte_SmallEnding(int num) {
    byte[] D = new byte[4];
    D[0] = (byte) (num & 0xff);
    D[1] = (byte) (num >> 8 & 0xff);
    D[2] = (byte) (num >> 16 & 0xff);
    D[3] = (byte) (num >> 24 & 0xff);
    return D;
}

是不是明白道理就简单多了?--有人问oxff是什么意思?

oxff意思:

首先了解一下, &表示只有两个位同时为1,才能得到1, 0x代表16进制数,而0xff表示的数二进制1111 1111 占一个字节.和其进行&操作的数,最低8位,不会发生变化.

而这里只是为了取它的高低.

比如:

out.write(num&0xff)

取低八位写入高地址中


out.write((num>>8)&0xff)

取高八位写入地

正题:

了解完成后我们就可以开始,wav的讲解了。

首部地址:

什么是RIFF:是一种在标记块中存储数据的元格式,把资料储存在被标记的区块中,

前4个字节为资源交换文件标志。

1.整个WAV都是由RIFF:数据块组成,可以将整个WAV文件看作一个完整的RIFF数据块,在这个RIFF数据块中又包含若干的子块存放不同的信息。

2.首部结构如下:

截取方式:截取方式也很简单,我们以二进制读取后,以ASCII读取字串:
读取:
    public @NotNull static <V> ByteArrayOutputStream Io_P_OUT(V PATH) throws IOException {
        FileInputStream obtainData = new FileInputStream(PATH.toString());
        int StorageByte;
        byte[] StorageDataHome = new byte[(int) new File(PATH.toString()).length()];
        ByteArrayOutputStream RETURN_DATA = new ByteArrayOutputStream();
        while ((StorageByte = obtainData.read(StorageDataHome)) > 0) {
            RETURN_DATA.write(StorageDataHome, 0, StorageByte);
            RETURN_DATA.flush();
        }
        obtainData.close();
        return RETURN_DATA;
    }
System.out.println(new String(__response.Io_P_OUT("PATH").toByteArray(),StandardCharsets.US_ASCII));

这样就可以看到如下信息了:


Format-Chunk:
0x0C4String'fmt'标志
0x104UInteger块长度
0x12 2 UShortPCM格式类别 
0x142UShort声道数目 
0x184UInteger采样率 
0x1C4UInteger传输速率 
0x1E2UShort数据块对齐 
0x202UShort每样本比特数 
0x222UShort可选

 附加块Fact-Chunk:

当使用压缩编码的WAV文件时,必须要有Fact-Chunk,这个块中只有一个数据,就是声道的采样总数.

Fact chunk前4个字节为"fact(ID)",然后是4字节的大小,表示本块包含数据的多少-不包含ID和SIZE.

偏移量字节数数据类型内容
0x264String'fact'标志
0x2A4UInteger块长度
0x2E4UInteger附加信息

数据块Data-Chunk:

0x324String'data'文件标志(0x64617461)

0x36

4

UInteger

数据块总长

 前一段是数据ID,后一段是:代表Data数据块的长度。

DataChunk块:头部和实际数据组成,数据块标示(4bytes)+数据块长度(4bytes)+实际数据。

编码包括了两方面内容,一是按一定格式存储数据,二是采用一定的算法压缩数据。

WAV格式对音频流的编码没有硬性规定,支持非压缩的PCM。

上代码,提取转换代码:

            @NotNull public static boolean _W(byte[] source,byte[] DATA,int Start ,int         End){
        if (DATA != null) {
            if (source.length-End >= (0)) {
                System.arraycopy(DATA, Start, source, 0, End);
                return true;
            }
            else {
                return false;
            }
        } else {
            return false;
        }
       }        



        --小端转大端读取

public static byte[] Io_S_Ending_B(String Path) throws IOException {FileChannel F =(FileChannel) Files.newByteChannel(java.nio.file.Path.of(Path), java.nio.file.StandardOpenOption.READ);
    ByteBuffer B = ByteBuffer.allocate((int)     F.size());B.order(ByteOrder.LITTLE_ENDIAN);F.read(B);B.flip();byte[] Data=new             byte[B.remaining()];
    B.get(Data);return Data;}
        

                byte[]sub=__response.Io_S_Ending_B("路径XX.wav");
        byte[] pcm=new byte[sub.length];
        __response._W(pcm,sub,44,pcm.length-44);
        AudioFormat.Encoding A=new AudioFormat.Encoding("PCM_SIGNED");
        SourceDataLine auline;
        AudioFormat af = new AudioFormat(A,48000, 16, 2,(16/8)*2,48000,false);
        try {
            auline = AudioSystem.getSourceDataLine(af);
        } catch (LineUnavailableException e) {
            throw new RuntimeException(e);
        }
        try {
            auline.open(af);
        } catch (LineUnavailableException e) {
            throw new RuntimeException(e);
        }
        auline.start();
        ByteArrayInputStream D=new ByteArrayInputStream(pcm);

        byte[] DD = new byte[1024];
        while(D.read(DD)!=-1){
            auline.write(DD,0,DD.length);
        }

录音与转换

其实这和刚才的读取wav数据块一样是pcm音频数据

录音我们需要使用到TargetDataLine官方的API数据从声卡中捕获数据。

他的采样率一般是44.1K,16bit采样精度,双声道速率则为44.1K×16×2小端端序,wav格式保存,之前说过wav->对音频流的编码没有硬性规定,支持非压缩的PCM。脉冲编码调制格式。

        ThreadPoolExecutor core = new ThreadPoolExecutor(1, 2, 100, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(10));
        //这里我直接使用线程池了,为了保证录音时,我们可以输入end结束,后续可以自行更改。
     
        //第一次输入
        System.out.println("Start开始end关闭");
        Scanner aa = new Scanner(System.in);
        String a = aa.next();
        if (a.equalsIgnoreCase("Start")) {
            core.submit(() -> {
                float a1 = 44000F;
                //采样
                int a2 = 16;
                //样本中的中位数8,16
                int a3 = 2;
                //声道
                boolean a4 = true;
                //签名
                boolean a5 = false;
                //给定参数
                //TargetDataLine是一种可以从中 DataLine 读取音频数据的类型。最常见的示例是从音频捕获设备获取数据的数据线
                //DataLine(简单来说就是捕获数据的)
                //AudioFormat通道样本
                AudioFormat.Encoding A=new AudioFormat.Encoding("PCM_SIGNED");
                DataLine.Info a6 = new DataLine.Info(TargetDataLine.class, new AudioFormat(
                        A
                        ,
                        a1,
                        a2,
                        a3,
                        (a2 / 8) * a3,
                        a1, a5));

                //音频编码 
                //每秒样本数 
                //每个样本中的位数 
                // 通道数(1 个用于单声道,2 个用于立体声,依此类推)
                //每帧中的字节数 
                //每秒帧数 
                //指示单个样本的数据是否以大端字节顺序存储(false 表示小端
                //通俗来讲就是匹配拿数据,最后一步时将数据以我们限定的格式输出
                TargetDataLine b = null;
                try {
                    b = (TargetDataLine) AudioSystem.getLine(a6);
                } catch (LineUnavailableException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("开始录音");
                AudioFileFormat.Type a8 = null;
                //指定的文件类型
                File a9 = null;
                //声明文件地址
                a8 = AudioFileFormat.Type.WAVE;
                //类型

                a9 = new File("保存路径");
                //文件地址
                try {
                    b.open();
                    //音频所用格式
                    b.start();
                    AudioSystem.write(new AudioInputStream(b), a8, a9);
                    //写入文件数据
                } catch (Exception ignored) {
                }
            });
        }
        //第二次输入
        Scanner aaaa = new Scanner(System.in);
        String aaaaa = aaaa.next();
        if (aaaaa.equalsIgnoreCase("end")) {
            System.exit(0);
        }

操作:输入不分大小start开始录音,end结束录音。


前面讲了提取音频的方法也可以自己练习一下,播放一下自己录的音频,提取各类信息。

完结

一起和Atomic君学习!。

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
要将收到RTP流的音频数据解析提取并写入本地WAV文件,可以按照以下步骤进行: 1. 首先,使用Java RTP库从网络中接收RTP数据包。可以使用Java RTP库中的RTPManager类和SessionAddress类来实现这一步骤。 2. 然后,使用Java音频库来解析RTP数据包中的音频数据。可以使用Java音频库中的AudioSystem类和相关的音频流类(如AudioInputStream和AudioFormat)来实现这一步骤。 3. 接下来,将解析出的音频数据写入本地WAV文件中。可以使用Java IO库中的File类、FileOutputStream类和相关的数据流类(如DataOutputStream)来实现这一步骤。 4. 最后,使用Java音频库来播放本地WAV文件中的音频数据。可以使用Java音频库中的Clip类来实现这一步骤。 以下是代码示例: ```java import javax.sound.sampled.*; import javax.media.*; import javax.media.protocol.*; import javax.media.format.*; import java.io.*; public class RTPAudioPlayer { private static final int RTP_PORT = 1234; // RTP口号 private static final String OUTPUT_FILE = "output.wav"; // 输出文件名 public static void main(String[] args) { try { // 创建RTP会话 RTPManager manager = RTPManager.newInstance(); manager.initialize(new SessionAddress(), null, null); manager.addReceiveStreamListener(new MyReceiveStreamListener()); // 创建本地WAV文件输出流 AudioFileFormat.Type fileType = AudioFileFormat.Type.WAVE; File outputFile = new File(OUTPUT_FILE); AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false); AudioInputStream audioInputStream = new AudioInputStream(new ByteArrayInputStream(new byte[0]), format, 0); AudioSystem.write(audioInputStream, fileType, outputFile); // 开始接收RTP数据 manager.addTarget(new SessionAddress("localhost", RTP_PORT, "127.0.0.1", RTP_PORT)); manager.waitForSessions(); } catch (Exception e) { e.printStackTrace(); } } private static class MyReceiveStreamListener implements ReceiveStreamListener { public void update(ReceiveStreamEvent event) { if (event instanceof NewReceiveStreamEvent) { try { // 获取新的RTP数据流并解析音频数据 ReceiveStream stream = event.getReceiveStream(); DataSource dataSource = stream.getDataSource(); Processor processor = Manager.createProcessor(dataSource); processor.configure(); while (processor.getState() != Processor.Configured) { Thread.sleep(100); } TrackControl[] trackControls = processor.getTrackControls(); for (TrackControl trackControl : trackControls) { if (trackControl.getFormat() instanceof AudioFormat) { trackControl.setEnabled(true); break; } } processor.realize(); while (processor.getState() != Processor.Realized) { Thread.sleep(100); } DataSource audioDataSource = processor.getDataOutput(); AudioInputStream audioInputStream = new AudioInputStream(audioDataSource.getInputStream(), new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false), AudioSystem.NOT_SPECIFIED); // 将音频数据写入本地WAV文件 File outputFile = new File(OUTPUT_FILE); FileOutputStream fileOutputStream = new FileOutputStream(outputFile, true); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream); byte[] buffer = new byte[4096]; int bytesRead = 0; while ((bytesRead = audioInputStream.read(buffer)) != -1) { dataOutputStream.write(buffer, 0, bytesRead); } dataOutputStream.close(); bufferedOutputStream.close(); fileOutputStream.close(); // 播放本地WAV文件 AudioInputStream playbackAudioInputStream = AudioSystem.getAudioInputStream(outputFile); Clip clip = AudioSystem.getClip(); clip.open(playbackAudioInputStream); clip.start(); } catch (Exception e) { e.printStackTrace(); } } } } } ``` 在实际的应用中,需要根据具体的需求进行适当的修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AtomicJun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值