【Java】字节数组 pcm 与 wav 格式互转 (附原理概述)

news2024/10/6 22:25:45

前言

最近实现了一个文字转语音的功能,语音引擎返回的是pcm格式的数据。需要转化成wav格式前端才能播放。本文首先会给出解决方案,后续会讲背后的原理。

  • 场景
    在这里插入图片描述
  • git 仓库
    https://github.com/ChenghanY/pcm-wav-converter

1. pcm wav 转化工具类

入参和出参都为byte[],理论上有了 byte[] 就可以输出为文件,或者用于网络交互。
输出为文件的部分可以看 【Java】pcm 与 wav 格式互转工具类 (附测试用例)
在这里插入图片描述

  • 浏览器播放的短音频,区分一下声道数、采样率即可。
  • 讯飞api文档中 audio/L16;rate=8000 表示单声道8000的采样率
package com.james;

import javax.sound.sampled.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

public class AudioFormatConverter {

    /**
     * 采样率
     */
    private static final Integer RATE = 8000;

    /**
     * 声道
     */
    private static final Integer CHANNELS = 1;

    public static byte[] pcmToWav(byte[] pcmBytes) {
        return addHeader(pcmBytes, buildHeader(pcmBytes.length));
    }

    public static byte[] wavToPcm(byte[] wavBytes) {
        return removeHeader(changeFormatToWav(wavBytes));
    }

    private static byte[] addHeader(byte[] pcmBytes, byte[] headerBytes) {
        byte[] result = new byte[44 + pcmBytes.length];
        System.arraycopy(headerBytes, 0, result, 0, 44);
        System.arraycopy(pcmBytes, 0, result, 44, pcmBytes.length);
        return result;
    }

    private static byte[] changeFormatToWav(byte[] audioFileContent) {
        AudioFormat format = new AudioFormat(
                8_000,
                16,
                CHANNELS,
                true,
                false
        );

        try (final AudioInputStream originalAudioStream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(audioFileContent));
             final AudioInputStream formattedAudioStream = AudioSystem.getAudioInputStream(format, originalAudioStream);
             final AudioInputStream lengthAddedAudioStream = new AudioInputStream(formattedAudioStream, format, audioFileContent.length);
             final ByteArrayOutputStream convertedOutputStream = new ByteArrayOutputStream()) {
            AudioSystem.write(lengthAddedAudioStream, AudioFileFormat.Type.WAVE, convertedOutputStream);
            return convertedOutputStream.toByteArray();
        } catch (UnsupportedAudioFileException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static byte[] removeHeader(byte[] audioFileContent) {
        return Arrays.copyOfRange(audioFileContent, 44, audioFileContent.length);
    }

    private static byte[] buildHeader(Integer dataLength) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                writeChar(bos, new char[]{'R', 'I', 'F', 'F'});
                writeInt(bos, dataLength + (44 - 8));
                writeChar(bos, new char[]{'W', 'A', 'V', 'E'});
                writeChar(bos, new char[]{'f', 'm', 't', ' '});
                writeInt(bos, 16);
                writeShort(bos, 0x0001);
                writeShort(bos, CHANNELS);
                writeInt(bos, AudioFormatConverter.RATE);
                writeInt(bos, (short) (CHANNELS * 2) * RATE);
                writeShort(bos, (short) (CHANNELS * 2));
                writeShort(bos, 16);
                writeChar(bos, new char[]{'d', 'a', 't', 'a'});
                writeInt(bos, dataLength);
                return bos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void writeShort(ByteArrayOutputStream bos, int s) throws IOException {
        byte[] arr = new byte[2];
        arr[1] = (byte) ((s << 16) >> 24);
        arr[0] = (byte) ((s << 24) >> 24);
        bos.write(arr);
    }

    private static void writeInt(ByteArrayOutputStream bos, int n) throws IOException {
        byte[] buf = new byte[4];
        buf[3] = (byte) (n >> 24);
        buf[2] = (byte) ((n << 8) >> 24);
        buf[1] = (byte) ((n << 16) >> 24);
        buf[0] = (byte) ((n << 24) >> 24);
        bos.write(buf);
    }

    private static void writeChar(ByteArrayOutputStream bos, char[] id) {
        for (char c : id) {
            bos.write(c);
        }
    }
}

2. 原理概述

在这里插入图片描述

wav格式实际上就是在pcm数据上加了头部,让浏览器能够解析pcm数据,进而能播放音频。可以类比 TCP协议的报文头,报文头携带了数据长度、偏移量等元信息。

3. 重回代码

根据原理概述,把网上的代码重构了一下,明确语义后的形式,也就是上文的两个方法。

    public static byte[] pcmToWav(byte[] pcmBytes) {
        return addHeader(pcmBytes, buildHeader(pcmBytes.length));
    }

    public static byte[] wavToPcm(byte[] wavBytes) {
        return removeHeader(changeFormatToWav(wavBytes));
    }

后记

把一些测试资源放上来,后续整合到仓库中,提供完整的测试用例:

  1. 音频文件的下载地址
    https://samplelib.com/zh/sample-wav.html
    https://support.huaweicloud.com/sdkreference-sis/sis_05_0039.html

  2. pcm转mp3,播放后用于验证pcm文件的正确性
    https://www.yayapeiyin.com/pcm-to-mp3/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1875044.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

将CSV、Excel、XML文件转换为MySQL数据库

在平时的工作中&#xff0c;经常会遇到需要将文件数据导入到数据库中的情况。有些客户之前可能只使用Excel表格作为记录工具&#xff0c;但当数据量达到一定程度或者需要将数据导入到其他系统中时&#xff0c;就会很emo,因为Excel表格虽然方便&#xff0c;但在数据处理和管理方…

ORB-SLAM2同OpenMVS实现三维重建

ORB-SLAM2 位姿导出 Note: 为与OpenMVS进行对接本次进对ORB-SLAM2进行部分修改&#xff0c;使之可以为 OpenMVS提供稀疏点云、关键帧的位姿、内参&#xff0c;以及稀疏点云在各个View 中的可见性。 主要更改如下 . 在Map文件下增添如下函数 public: void Save(const string &a…

面包板的使用

概要 当你学习怎样去建立一个电路时&#xff0c;面包板是最基本、最简单的元件之一。由于板子上有很多小插孔&#xff0c;各种电子元器件可根据需要任意插入或拔出&#xff0c;免去了焊接的工序&#xff0c;节省了电路的组装时间&#xff0c;且元件可以重复使用&#xff0c;非…

论文学习:基于知识图谱的RAG进行客服问答

1.简介 文章名称&#xff1a; Retrieval-Augmented Generation with Knowledge Graphs for Customer Service Question Answering&#xff08;基于知识图谱的RAG进行客服问答&#xff09; 2.摘要ABSTRACT 在客户服务技术支持中&#xff0c;迅速准确地检索相关的过往问题对于有…

爬虫笔记17——selenium框架的使用

selenium框架的使用 1、python程序安装selenium框架2、下载Chrome谷歌驱动3、selenium的基本使用4、多个标签页切换顺序混乱的问题 1、python程序安装selenium框架 # 在安装过程中最好限定框架版本为4.9.1 # pip install selenium 没有制定版本&#xff0c;非镜像下载也会比较…

node.js环境安装和VUE-cli脚手架搭建

简介 node.js Node.js 是一个免费、开源、跨平台的 JavaScript 运行时环境&#xff0c;它让开发人员能够创建服务器、Web 应用、命令行工具和脚本。 VUE-cli Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统&#xff0c;提供&#xff1a; 通过 vue/cli 实现的交互式的项…

「C++系列」C++简介、应用领域

文章目录 一、C简介C 的主要特点&#xff1a;C 的学习建议&#xff1a; 二、C 应用领域三、C 有哪些优势四、C 初学案例1. Hello, World!2. 两数求和3. 判断奇偶4. 判断闰年5. 判断质数6. 求阶乘7. 斐波那契数列8. 延时程序&#xff08;简单示例&#xff09; 五、相关链接 一、…

xlsx插件简介

1. xlsx插件 1.1. 常用属性和方法 1.1.1. 创建新的工作簿1.1.2. 从数组生成工作表1.1.3. 添加工作表到工作簿1.1.4. 从HTML表格创建工作表1.1.5. 读取Excel文件1.1.6. 导出Excel文件1.1.7. 设置单元格样式 2. vue中如何使用xlsx 2.1. vue-xlsx的特点2.2. 常用属性和方法 2.2.1…

企业级堡垒机JumpServer

文章目录 JumpServer是什么生产应用场景 Docker安装JumpServer1.Docker安装2.MySQL服务安装3.Redis服务安装4.key生成5.JumpServer安装6.登录验证 系统设置邮箱服务器用户和用户组创建系统审计员资产管理用户创建资产节点资产授权查看用户的资产监控仪表盘 命令过滤器创建命令过…

基于STM32的智能家用电力管理系统

目录 引言环境准备智能家用电力管理系统基础代码实现&#xff1a;实现智能家用电力管理系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;电力管理与优化问题解决方案与优化收尾与总结 1. 引言 智能家用电力管理系统通…

EXCEL 复制后转置粘贴

nodepad 转置参考&#xff1a; https://editor.csdn.net/md/?articleId140014651 1. WPS复制后转置粘贴 复制-》右键-》顶部第一行-》粘贴行列转置&#xff0c;如下图&#xff1a; 2. Excel office365 本地版 2. Excel office365 在线版

Python特征工程 — 1.1 特征二值化

目录 1 特征二值化 1.1 特征二值化简介 1.2 实验数据集 2 阈值法 2.1 scikit-learn库实现阈值法二值化 2.2 pandas实现阈值法二值化 2.3 自定义函数实现阈值法二值化 3 其他方法实现二值化 3.1 中位数法 3.2 众数法 3.3 标准差法 1 特征二值化 1.1 特征二值化简介…

国产操作系统上netstat命令详解 _ 统信 _ 麒麟 _ 中科方德

原文链接&#xff1a;国产操作系统上netstat命令详解 | 统信 | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇在国产操作系统上使用netstat命令的详解文章。netstat是网络统计&#xff08;network statistics&#xff09;的缩写&#xff0c;它是一…

力扣每日一题 6/28 动态规划/数组

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;IT竞赛 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 2742.给墙壁刷油漆【困难】 题目&#xff1a; 给你两个长度为 n 下标从 0…

Hive SQL:实现炸列(列转行)以及逆操作(行转列)

目录 列转行行转列 列转行 函数&#xff1a; EXPLODE(ARRAY)&#xff1a;将ARRAY中的每一元素转换为每一行 EXPLODE(MAP)&#xff1a;将MAP中的每个键值对转换为两行&#xff0c;其中一行数据包含键&#xff0c;另一行数据包含值 数据样例&#xff1a; 1、将每天的课程&#…

vue全局方法plugins/utils

一、在src目录下创建一个plugins文件夹 test.ts文件存放创建的方法&#xff0c;index.ts用于接收所有自定义方法进行统一处理 二、编写自定义方法 // test.ts文件 export default {handleTest(val1: number, val2: number) {// 只是一个求和的方法return val1 val2;}, };三…

hive调优原理详解:案例解析参数配置(第17天)

系列文章目录 一、Hive常问面试函数&#xff08;掌握&#xff09; 二、Hive调优如何配置&#xff08;重点&#xff09; 文章目录 系列文章目录前言一、Hive函数&#xff08;掌握&#xff09;11、JSON数据处理12、炸裂函数13、高频面试题13.1 行转列13.2 列转行 14、开窗函数&a…

一些指标的学习

1.平均倒数排名&#xff08;MRR&#xff09; 1.定义 MRR 是衡量检索系统返回的结果列表中第一个相关结果位置的指标。具体来说&#xff0c;它是所有查询倒数排名的平均值。 2.计算步骤 对每个查询&#xff0c;找到第一个正确答案在结果列表中的排名 &#x1d445;&#x1d44…

鸿蒙登录页面及页面跳转的设计

目录 任务目标任务分析任务实施1.新建工程项目HMLogin2.设计登录页面Index.visual3.设计第二个页面SecondPage4.修改Index.ets代码5.修改SecondPage.ets代码6.运行工程 任务目标 设计一个简单的登录页面&#xff0c;要求可以将第一页的登录信息&#xff0c;传递到第二个页面&a…

VMware ESXi 技术

目录 一、VMware ESXi安装 1. 在VMware WorkStation中创建一台虚拟机 2. 进入VMware ESXi控制台 3. 配置VMware ESXi网络 二、使用Web网页端登录管理ESXi 1. 分配许可证密钥&#xff08;选做&#xff09; 2. 管理ESXi 三、VMware ESXi控制台 1. 创建虚拟机 2. 定制虚拟…