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

41 篇文章 4 订阅
24 篇文章 1 订阅

前言

最近实现了一个文字转语音的功能,语音引擎返回的是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/

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值