Java 解析摩尔斯电码并生成音频流文件

Java 解析摩尔斯电码并生成音频流文件,第1张

旁白

这周老大安排了一个活儿,前端传一组摩尔斯电码过来,将其装成音频流通过WebSocket推给前端进行播放,由于场景的特殊还需要控制"点" "划"播报的时间长短。当时心想Java还能生成这个玩意儿?通过查阅了大量的资料后,确定了Java的确可以实现,不得不感叹这Java是真滴厉害。相关资料已放到下方链接。
音频基础知识
摩尔斯电码表
Java 生成摩尔斯电码音频流代码

转音频代码

初始化摩尔斯电码映射

public class MorseEncoder {
    /**
     * 存放摩尔斯电码
     */
    private final Map<String,String> codeMap = new HashMap<>();

    /**
     * 点 (控制播报速率倍速)
     */
    private Integer dotRatio = 1;

    /**
     * 划 (控制播报速率倍速)
     */
    private Integer rowRatio = 3;

    /**
     * 大间隔  (控制播报速率倍速)
     */
    private Integer blankRatio = 5;

    private final String dotString = ".";

    private final String rowString = "-";

    /**
     * 小间隔
     */
    private final String minInterval = "@";

    /**
     * 大间隔
     */
    private final String maxInterval = "/";

    private final String blank = " ";

    @PostConstruct
    public void initMap(){
        //初始化HashMap 中间有空格
        codeMap.put("A",". -");
        codeMap.put("B","- . . .");
        codeMap.put("C","- . - .");
        codeMap.put("D","- . .");
        codeMap.put("E",".");
        codeMap.put("F",". . - .");
        codeMap.put("G","- - .");
        codeMap.put("H",". . . .");
        codeMap.put("I",". .");
        codeMap.put("J",". - - -");
        codeMap.put("K","- . -");
        codeMap.put("L",". - . .");
        codeMap.put("M","- -");
        codeMap.put("N","- .");
        codeMap.put("O","- - -");
        codeMap.put("P",". - - .");
        codeMap.put("Q","- - . -");
        codeMap.put("R",". - .");
        codeMap.put("S",". . .");
        codeMap.put("T","-");
        codeMap.put("U",". . -");
        codeMap.put("V",". . . -");
        codeMap.put("W",". - -");
        codeMap.put("X","- . . -");
        codeMap.put("Y","- . - -");
        codeMap.put("Z","- - . .");
        codeMap.put("1",". - - - -");
        codeMap.put("2",". . - - -");
        codeMap.put("3",". . . - -");
        codeMap.put("4",". . . . -");
        codeMap.put("5",". . . . .");
        codeMap.put("6","- . . . .");
        codeMap.put("7","- - . . .");
        codeMap.put("8","- - - . .");
        codeMap.put("9","- - - - .");
        codeMap.put("0","- - - - -");
    }
}

字符串转电码

 /**
     *
     * @param args 字码
     * @param type 类型 1 单词 ["A","B","C"]2 词组 ["ABC","DEFG"]
     * @return
     */
    public String string2MorseCode(List<String> args,Integer type){
        StringBuilder result = new StringBuilder();
        if(type.compareTo(1) == 0 ){
            for (String arg : args) {
                String morse = codeMap.get(arg.toUpperCase());
                if(morse == null){
                    throw new RuntimeException(arg+"不是摩尔斯电码");
                }
                result.append(morse)
                        .append(minInterval);
            }
        }else {
            for (String arg : args) {
                for (int i = 0; i < arg.length(); i++) {
                    String code = String.valueOf(arg.charAt(i));
                    String morse = codeMap.get(code.toUpperCase());
                    if(morse == null){
                        throw new RuntimeException(arg+"不是摩尔斯电码");
                    }
                    result.append(morse)
                            .append(minInterval);

                }
                result.append(maxInterval);
            }
        }
        return result.toString();
    }

电码转音频

 /**
     * 将摩尔斯点码转成音频
     * @param codeString 摩尔斯电码
     * @param reta 速率
     * @return 音频流
     */
    public byte[] codeConvert2Sound(String codeString,int reta){
        int dot = reta * dotRatio;
        int row = reta * rowRatio;
        int blank = reta * blankRatio;
        //存放byte
        ArrayList<Byte> rawData = new ArrayList<Byte>();
        //计算每一个音波
        for(int i=0; i<codeString.length(); i++){
            // 时长
            int soundLength = 0;
            // 频率
            int frequency = 1450;
            String code = Character.toString(codeString.charAt(i));

            if (code.equals(rowString)) {
                soundLength = row;
            } else if (code.equals(dotString)) {
                soundLength = dot;
            } else if (code.equals(minInterval)) {
                soundLength = row;
                //将频率设置成 0
                frequency = 0;
            } else if (code.equals(maxInterval)) {
                soundLength = blank;
                //将频率设置成 0
                frequency = 0;
            }else if(code.equals(this.blank)){
                soundLength = dot;
                //将频率设置成 0
                frequency = 0;
            }

            // add beeps for letters
            for ( int k = 0; k < soundLength * (float)44100 / 1000; k++ ) {
                double angle = k / ( (float)44100 / frequency ) * 2.0 * Math.PI;
                rawData.add( (byte)( Math.sin( angle ) * 100 ) );
            }

            // add break between chars
            /*for (int j=0; j < 2000; j++){
                rawData.add((byte) 0);
            }*/
        }
        //将list转array
        byte[] audio = new byte[rawData.size()];
        for (int i=0; i<rawData.size(); i++){
            audio[i] = rawData.get(i);
        }
        //save2File(audio);
        return audio;
    }

存入到文件中 wav格式

  /**
     * 保存到文件中
     * @param audio
     */
    private void save2File(byte[] audio) {
        InputStream byteArrayInputStream = new ByteArrayInputStream(audio);
        AudioFormat audioFormat = new AudioFormat( (float)44100, 8, 1, true, false );
        AudioInputStream audioInputStream = new AudioInputStream( byteArrayInputStream, audioFormat, audio.length / audioFormat.getFrameSize() );

        try {
            String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH-mm-ss"));
            AudioSystem.write( audioInputStream, AudioFileFormat.Type.WAVE, new File( "D:\morse-code-"+format +".wav" ));
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

生成WAV头文件

    /**
      2      * @param totalAudioLen  不包括header的音频数据总长度
      3      * @param longSampleRate 采样率,也就是录制时使用的频率、音频采样级别 8000 = 8KHz
      4      * @param channels       audioRecord的声道数1/2
      5      * @param audioFormat    采样精度; 譬如 16bit
      6      * @throws IOException 写文件错误
      7      */
    public static byte[] writeWavFileHeader(long totalAudioLen, long longSampleRate,
                                            int channels, int audioFormat) throws IOException {
        byte[] header = generateWavFileHeader(totalAudioLen, longSampleRate, channels, audioFormat);
        return header;
    }

 /**
 15      * @param totalAudioLen  不包括header的音频数据总长度
 16      * @param longSampleRate 采样率,也就是录制时使用的频率
 17      * @param channels       audioRecord的频道数量
 18      * @param audioFormat    采样精度; 譬如 16bit
 19      */
        private static byte[] generateWavFileHeader(long totalAudioLen, long longSampleRate, int channels,int audioFormat) {

            long totalDataLen = totalAudioLen + 36;

            long byteRate = longSampleRate * 2 * channels;

            byte[] header = new byte[44];

            header[0] = 'R'; // RIFF

            header[1] = 'I';

            header[2] = 'F';

            header[3] = 'F';

            //文件长度  4字节文件长度,这个长度不包括"RIFF"标志(4字节)和文件长度本身所占字节(4字节),即该长度等于整个文件长度 - 8

            header[4] = (byte) (totalDataLen & 0xff);

            header[5] = (byte) ((totalDataLen >> 8) & 0xff);

            header[6] = (byte) ((totalDataLen >> 16) & 0xff);

            header[7] = (byte) ((totalDataLen >> 24) & 0xff);

            //fcc type:4字节 "WAVE" 类型块标识, 大写

            header[8] = 'W';

            header[9] = 'A';

            header[10] = 'V';

            header[11] = 'E';

            //FMT Chunk   4字节 表示"fmt" chunk的开始,此块中包括文件内部格式信息,小写, 最后一个字符是空格

            header[12] = 'f'; // 'fmt '

            header[13] = 'm';

            header[14] = 't';

            header[15] = ' ';//过渡字节

            //数据大小  4字节,文件内部格式信息数据的大小,过滤字节(一般为00000010H)

            header[16] = 16;

            header[17] = 0;

            header[18] = 0;

            header[19] = 0;

            //编码方式 10H为PCM编码格式   FormatTag:2字节,音频数据的编码方式,1:表示是PCM 编码

            header[20] = 1; // format = 1

            header[21] = 0;

            //通道数  Channels:2字节,声道数,单声道为1,双声道为2

            header[22] = (byte) channels;

            header[23] = 0;

            //采样率,每个通道的播放速度

            header[24] = (byte) (longSampleRate & 0xff);

            header[25] = (byte) ((longSampleRate >> 8) & 0xff);

            header[26] = (byte) ((longSampleRate >> 16) & 0xff);

            header[27] = (byte) ((longSampleRate >> 24) & 0xff);

            //音频数据传送速率,采样率*通道数*采样深度/8

            //4字节,音频数据传送速率, 单位是字节。其值为采样率×每次采样大小。播放软件利用此值可以估计缓冲区的大小

            //byteRate = sampleRate * (bitsPerSample / 8) * channels

            header[28] = (byte) (byteRate & 0xff);

            header[29] = (byte) ((byteRate >> 8) & 0xff);

            header[30] = (byte) ((byteRate >> 16) & 0xff);

            header[31] = (byte) ((byteRate >> 24) & 0xff);

            // 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数

            header[32] = (byte) (2 * channels);

            header[33] = 0;

            //每个样本的数据位数

            //2字节,每个声道的采样精度; 譬如 16bit 在这里的值就是16。如果有多个声道,则每个声道的采样精度大小都一样的;

            header[34] = (byte) audioFormat;

            header[35] = 0;

            //Data chunk

            //ckid:4字节,数据标志符(data),表示 "data" chunk的开始。此块中包含音频数据,小写;

            header[36] = 'd';

            header[37] = 'a';

            header[38] = 't';

            header[39] = 'a';

            //音频数据的长度,4字节,audioDataLen = totalDataLen - 36 = fileLenIncludeHeader - 44

            header[40] = (byte) (totalAudioLen & 0xff);

            header[41] = (byte) ((totalAudioLen >> 8) & 0xff);

            header[42] = (byte) ((totalAudioLen >> 16) & 0xff);

            header[43] = (byte) ((totalAudioLen >> 24) & 0xff);

            return header;

        }

测试

@Test
    public void morseCodeTest() throws IOException {
        MorseEncoder encoder = new MorseEncoder();
        List<String> args = new ArrayList<>();
        args.add("ttt");
        args.add("000");
        String code = encoder.string2MorseCode(args, 2);
        byte[] bytes = encoder.codeConvert2Sound(code, 100);

        byte[] header = encoder.writeWavFileHeader(bytes.length, 44100, 1, 8);

        ByteArrayBuilder builder  = new ByteArrayBuilder();
        builder.write(header);
        builder.write(bytes);

        byte[] data = builder.toByteArray();

        File file = new File("D:\test.wav");
        OutputStream os = new FileOutputStream(file);
        os.write(data);


    }

效果 播放出来的效果也是跟预期的一样

代码地址
https://gitee.com/wsl__cn/morse-code-convert-audio.git

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/733570.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存