关于对音频的合并,左右声道的分离以及播放的操作

目录

一、音频的拆分,将mp3拆分为左右声道的wav文件

首先准备一个mp3文件到已知目录

读取文件,并拆分

结果

二、对音频文件的各项参数的判断

文件我们已经准备好,可以直接写代码,来获取参数

三、对音频的合并,将wav合并为wav、map3格式

结果:

四、wav双声道格式转换为mp3格式

结果:

五、播放音频

结果:

注:文中公共参数


一、音频的拆分,将mp3拆分为左右声道的wav文件

  • 首先准备一个mp3文件到已知目录

       

  • 读取文件,并拆分

/**
     * 提取单声道录音,并分别上传
     */
    @ResponseBody
    @RequestMapping("/extDoubleAudio.action")
    public void extDoubleAudio(){
        String originPath = "E:/audio/visit/origin/少侠不用刀_(剑网3九周年翻唱丐太ver)-洛少爷-70600494.mp3";
        String leftPath = "E:/audio/visit/left";
        String rightPath = "E:/audio/visit/right";
        //获取单声道录音
        try {
            AudioUtils.doubleChannelSplit(originPath,leftPath,rightPath);
        }catch (Exception e){
            logger.error("提取单声道录音出错e:{}",e);
        }
    }
/**
     * 提取左右声道的单声道录音
     * @param originPath
     * @param leftFilePath
     * @param rightFilePath
     * @return
     */
    public static File[] doubleChannelSplit(String originPath, String leftFilePath, String rightFilePath) {
        String name = UUID.randomUUID().toString();

        File leftFile = new File(leftFilePath);
        File rightFile = new File(rightFilePath);
        if (!leftFile.exists()) {
            leftFile.mkdir();
        }

        if (!rightFile.exists()) {
            rightFile.mkdir();
        }

        leftFilePath = leftFilePath + "/" + name + "left.wav";
        rightFilePath = rightFilePath + "/" + name + "right.wav";

        FFMPEGExecutor ffmpeg = locator.createExecutor();
        ffmpeg.addArgument("-i");
        ffmpeg.addArgument(originPath);
        ffmpeg.addArgument("-map_channel");
        ffmpeg.addArgument("0.0.0");
        ffmpeg.addArgument(leftFilePath);
        ffmpeg.addArgument("-map_channel");
        ffmpeg.addArgument("0.0.1");
        ffmpeg.addArgument(rightFilePath);
        BufferedReader br = null;
        try {
            ffmpeg.execute();
            br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()));
            String line;
            while ((line = br.readLine()) != null) {
                logger.info(line);
            }
            return new File[]{leftFile, rightFile};
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
  • 结果

        

既然我们拆分了左右声道,我们可以去判断是否分隔成功单声道

二、对音频文件的各项参数的判断

  • 文件我们已经准备好,可以直接写代码,来获取参数

/**
     * 查看音频各项参数
     * @throws IOException
     * @throws ReadOnlyFileException
     * @throws TagException
     * @throws InvalidAudioFrameException
     */
    @ResponseBody
    @RequestMapping("/checkAudioParam.action")
    public void checkAudioParam() throws IOException, ReadOnlyFileException, TagException, InvalidAudioFrameException {
        File originPath = new File("E:/audio/visit/origin/少侠不用刀_(剑网3九周年翻唱丐太ver)-洛少爷-70600494.mp3");
        File leftPath = new File("E:/audio/visit/left/91fdaae1-6010-4f60-836d-d20db78d986fleft.wav");
        File rightPath = new File("E:/audio/visit/right/91fdaae1-6010-4f60-836d-d20db78d986fright.wav");
        RandomAccessFile rdfOrigin = null;
        RandomAccessFile rdfLeft = null;
        RandomAccessFile rdfRight = null;
//            rdfOrigin = new RandomAccessFile(originPath,"r");
        rdfLeft = new RandomAccessFile(leftPath,"r");
        rdfRight = new RandomAccessFile(rightPath,"r");

        MP3File file = new MP3File(originPath);
        MP3AudioHeader header = file.getMP3AudioHeader();
        System.out.println("时长: " + header.getTrackLength()); //获得时长
        System.out.println("比特率: " + header.getBitRate()); //获得比特率
        System.out.println("音轨长度: " + header.getTrackLength()); //音轨长度
        System.out.println("格式: " + header.getFormat()); //格式,例 MPEG-1
        System.out.println("声道: " + header.getChannels()); //声道
        System.out.println("采样率: " + header.getSampleRate()); //采样率
        System.out.println("MPEG: " + header.getMpegLayer()); //MPEG
        System.out.println("MP3起始字节: " + header.getMp3StartByte()); //MP3起始字节
        System.out.println("精确的音轨长度: " + header.getPreciseTrackLength()); //精确的音轨长度
//            System.out.println("声音尺寸: " + toInt(read(rdfOrigin, 4, 4))); // 声音尺寸
//            System.out.println("音频格式 1 = PCM: " + toShort(read(rdfOrigin, 20, 2))); // 音频格式 1 = PCM
//            System.out.println("声道: " + toShort(read(rdfOrigin, 22, 2))); // 1 单声道 2 双声道
//            System.out.println("采样率: " + toInt(read(rdfOrigin, 24, 4)));  // 采样率、音频采样级别 8000 = 8KHz
//            System.out.println("每秒波形数量: " + toInt(read(rdfOrigin, 28, 4)));  // 每秒波形的数据量
//            System.out.println("采样帧大小: " + toShort(read(rdfOrigin, 32, 2)));  // 采样帧的大小
//            System.out.println("采样位数: " + toShort(read(rdfOrigin, 34, 2)));  // 采样位数
        System.out.println("---------------------------------------------------Origin-left数据分割线-----------------------------------------------");
        System.out.println("声音尺寸: " + toInt(read(rdfLeft, 4, 4))); // 声音尺寸
        System.out.println("音频格式 1 = PCM: " + toShort(read(rdfLeft, 20, 2))); // 音频格式 1 = PCM
        System.out.println("声道: " + toShort(read(rdfLeft, 22, 2))); // 1 单声道 2 双声道
        System.out.println("采样率: " + toInt(read(rdfLeft, 24, 4)));  // 采样率、音频采样级别 8000 = 8KHz
        System.out.println("每秒波形数量: " + toInt(read(rdfLeft, 28, 4)));  // 每秒波形的数据量
        System.out.println("采样帧大小: " + toShort(read(rdfLeft, 32, 2)));  // 采样帧的大小
        System.out.println("采样位数: " + toShort(read(rdfLeft, 34, 2)));  // 采样位数
        System.out.println("---------------------------------------------------left-right数据分割线-----------------------------------------------");
        System.out.println("声音尺寸: " + toInt(read(rdfRight, 4, 4))); // 声音尺寸
        System.out.println("音频格式 1 = PCM: " + toShort(read(rdfRight, 20, 2))); // 音频格式 1 = PCM
        System.out.println("声道: " + toShort(read(rdfRight, 22, 2))); // 1 单声道 2 双声道
        System.out.println("采样率: " + toInt(read(rdfRight, 24, 4)));  // 采样率、音频采样级别 8000 = 8KHz
        System.out.println("每秒波形数量: " + toInt(read(rdfRight, 28, 4)));  // 每秒波形的数据量
        System.out.println("采样帧大小: " + toShort(read(rdfRight, 32, 2)));  // 采样帧的大小
        System.out.println("采样位数: " + toShort(read(rdfRight, 34, 2)));  // 采样位数

//            rdfOrigin.close();
        rdfLeft.close();
        rdfRight.close();
    }



    public static int toInt(byte[] b) {
        return (((b[3] & 0xff) << 24) + ((b[2] & 0xff) << 16) + ((b[1] & 0xff) << 8) + ((b[0] & 0xff) << 0));
//        return ((b[3] << 24) + (b[2] << 16) + (b[1] << 8) + (b[0] << 0));
    }

    public static short toShort(byte[] b) {
        return (short)(((b[1] & 0xff) << 8) + ((b[0] & 0xff) << 0));
//        return (short)((b[1] << 8) + (b[0] << 0));
    }


    public static byte[] read(RandomAccessFile rdf, int pos, int length) throws IOException {
        rdf.seek(pos);
        byte result[] = new byte[length];
        for (int i = 0; i < length; i++) {
            result[i] = rdf.readByte();
        }
        return result;
    }

 注:因为mp3格式的使用富文本编辑器会出现数据不准的情况,所以,我们使用了MP3AudioHeader

  • 结果

 声道类型直通车   注:Joint Stereo代表立体声   ,而1代表了单声道

既然我们可以拆分音频,那么我们可不可以合并了,当然是可以了,我们来写代码

三、对音频的合并,将wav合并为wav、map3格式

/**
     * 合并单声道录音为双声道
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping("/mergeAudioDoubts.action")
    public void mergeAudioDoubts() throws Exception {
        byte[] left = Files.readAllBytes(Paths.get("E:/audio/visit/left", "91fdaae1-6010-4f60-836d-d20db78d986fleft.wav"));
        byte[] right = Files.readAllBytes(Paths.get("E:/audio/visit/right", "91fdaae1-6010-4f60-836d-d20db78d986fright.wav"));

        String originPathWav = "E:/audio/visit/mergeDoubts/少侠不用刀.wav";
        String originPathMp3 = "E:/audio/visit/mergeDoubts/少侠不用刀.mp3";
        AudioUtils.merge(left, right, originPathWav);
        AudioUtils.merge(left, right, originPathMp3);
    }
    /**
     * 下面的整个部分都属于合并接口
     * @param head
     * @return
     */
    private static boolean isWav(byte[] head) {
        return ("RIFF".equals(new String(head, 0, 4, ISO_8859_1)) &&
                "WAVE".equals(new String(head, 8, 4, ISO_8859_1)));
    }

    private static void fileTooSmall(byte[] file) throws Exception {
        if (file.length < HEAD_LENGTH + FORMAT_LENGTH) {
            logger.warn("file is too small, size if {}.", file.length);
            throw new Exception();
        }
    }

    private static int headSize() {
        return HEAD_LENGTH + FORMAT_LENGTH;
    }

    public static int fileSize(byte[] file) throws Exception {
        fileTooSmall(file);

        byte[] head = copyOfRange(file, 0, HEAD_LENGTH);

        if (isWav(head)) {
            return ByteBuffer.wrap(copyOfRange(head, 4, 8))
                    .order(LITTLE_ENDIAN)
                    .getInt() + 8;
        } else {
            logger.warn("file format error: expected {}, actual {}.",
                    "[82, 73, 70, 70, *, *, *, *, 87, 65, 86, 69]",
                    head);
            throw new Exception();
        }
    }

    public static AudioFormat fileFormat(byte[] file) throws Exception {
        fileTooSmall(file);

        byte[] head = copyOfRange(file, 0, HEAD_LENGTH);

        if (isWav(head)) {
            byte[] format = copyOfRange(file, 12, HEAD_LENGTH + FORMAT_LENGTH);
            String chuckID = new String(format, 0, 4, ISO_8859_1);
            int chunkSize = ByteBuffer.wrap(copyOfRange(format, 4, 8))
                    .order(LITTLE_ENDIAN).getInt();
            int audioFmt = ByteBuffer.wrap(copyOfRange(format, 8, 10))
                    .order(LITTLE_ENDIAN).getShort();
            int channels = ByteBuffer.wrap(copyOfRange(format, 10, 12))
                    .order(LITTLE_ENDIAN).getShort();
            int sampleRate = ByteBuffer.wrap(copyOfRange(format, 12, 16))
                    .order(LITTLE_ENDIAN).getInt();
            int byteRate = ByteBuffer.wrap(copyOfRange(format, 16, 20))
                    .order(LITTLE_ENDIAN).getInt();
            int frameSize = ByteBuffer.wrap(copyOfRange(format, 20, 22))
                    .order(LITTLE_ENDIAN).getShort();
            int sampleSizeInBits = ByteBuffer.wrap(copyOfRange(format, 22, 24))
                    .order(LITTLE_ENDIAN).getShort();

            return new AudioFormat(PCM_SIGNED, sampleRate,
                    sampleSizeInBits, channels, frameSize, sampleRate, false);
        } else {
            logger.warn("file is not a wav.");
            throw new Exception();
        }
    }

    public static void merge(final byte[] left, final byte[] right, final String path) throws Exception {
        int leftSize = fileSize(left);
        int rightSize = fileSize(right);
        int mergeSize = mergeSizeField(leftSize, rightSize);
        int mergeDataSize = mergeDataSize(leftSize, rightSize);

        try (RandomAccessFile file = new RandomAccessFile(path, "rw")) {
            file.write(mergeHead(left, mergeSize));
            file.write(dataChunkHead(mergeDataSize));
            int max = Math.max(leftSize, rightSize);
            for (int i = headSize() + 8; i < max + 8; i += 2) {
                file.write(read(left, i));
                file.write(read(right, i));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static byte[] read(final byte[] content, int offset) {
        if (content.length > offset) {
            return copyOfRange(content, offset, offset + 2);
        } else {
            return "\0\0".getBytes(ISO_8859_1);
        }
    }

    private static int mergeSizeField(int left, int right) {
        int max = Math.max(left - 8, right - 8);
        return max * 2;
    }

    private static int mergeDataSize(int left, int right) {
        int max = Math.max(left - headSize() - 8, right - headSize() - 8);
        return max * 2;
    }

    private static byte[] mergeHead(final byte[] left, final int mergeSize) throws Exception {
        AudioFormat format = fileFormat(left);
        ByteBuffer size = ByteBuffer.allocate(4).order(LITTLE_ENDIAN).putInt(mergeSize);

        ByteBuffer channels = ByteBuffer.allocate(2).order(LITTLE_ENDIAN).putShort((short) 2);
        ByteBuffer sampleRate = ByteBuffer.allocate(4).order(LITTLE_ENDIAN)
                .putInt((int) format.getSampleRate());
        ByteBuffer byteRate = ByteBuffer.allocate(4).order(LITTLE_ENDIAN)
                .putInt((int) format.getSampleRate() * 2 * format.getSampleSizeInBits() / 8);
        ByteBuffer blockAlign = ByteBuffer.allocate(2).order(LITTLE_ENDIAN)
                .putShort((short) (2 * format.getSampleSizeInBits() / 8));
        ByteBuffer bitsPerSample = ByteBuffer.allocate(2).order(LITTLE_ENDIAN)
                .putShort((short) format.getSampleSizeInBits());

        ByteBuffer head = ByteBuffer.allocate(headSize());
        head.put(left, 0, 4);

        head.put(size.array());
        head.put(left, 8, 14);

        head.put(channels.array());
        head.put(sampleRate.array());
        head.put(byteRate.array());
        head.put(blockAlign.array());
        head.put(bitsPerSample.array());
        return head.array();
    }

    private static byte[] dataChunkHead(final int length) {
        ByteBuffer head = ByteBuffer.allocate(8);
        head.put("data".getBytes(ISO_8859_1));
        ByteBuffer size = ByteBuffer.allocate(4).order(LITTLE_ENDIAN).putInt(length);
        head.put(size.array());
        return head.array();
    }
  • 结果

可以看出,mp3的文件并没有合并成功,声道类型依旧是单声道,也就是,我们如果需要mp3格式的音频文件,还需要从,wav去转换

结果:

四、wav双声道格式转换为mp3格式

/**
     * 音频格式 wav---mp3
     */
    @ResponseBody
    @RequestMapping("/changeAudioFormat.action")
    public void changeAudioFormat(){
        String wavFilePath = "E:/audio/visit/mergeDoubts/少侠不用刀.wav";
        String mp3FilePath = "E:/audio/visit/mergeDoubts/少侠不用刀-change.mp3";
        AudioUtils.wavChange2Mp3(wavFilePath,mp3FilePath);
    }
public static void wavChange2Mp3(String wavFilePath,String mp3FilePath){
        try {
            File wavFile = new File(wavFilePath);
            File mp3File = new File(mp3FilePath);
            AudioAttributes audio = new AudioAttributes();
            audio.setCodec("libmp3lame");
            audio.setBitRate(128000);
            audio.setChannels(2);
            audio.setSamplingRate(44100);

            EncodingAttributes encoding = new EncodingAttributes();
            encoding.setFormat("mp3");
            encoding.setAudioAttributes(audio);

            Encoder encoder = new Encoder(locator);
            encoder.encode(new MultimediaObject(wavFile,locator),mp3File,encoding);
        } catch (EncoderException e) {
            e.printStackTrace();
        }
    }

结果:

过程都弄完了,剩下的就是听音频了 ,听音乐就简单了,可以直接用audio标签

五、播放音频

<button class="playMusic">
                    播放
</button>
    //预览音频
    $(document).on('click', '.playMusic', function() {
        //注:如果是本地,因为处于安全,会出现前缀file://目前我还没有什么好的解决方案,所以,我是放的我自己的服务器音频文件
        var _src = "http://xxxx:8080/audio/0c5cb6f68228451fa20422554a23f1317498.wav";   
        previewaudio(_src);
    })

    //音频预览
    function previewaudio(obj) {
        var audioHtml = '<div style="padding:20px;margin:0 auto;">' +
            '<audio src="'+obj+'" width="68px" height="68px" controls></audio></div>';
        //弹出层
        layer.open({
            type: 1,
            shade: 0.8,
            offset: 'auto',
            area: ['auto','200px'],
            shadeClose:true,//点击外围关闭弹窗
            scrollbar: false,//不现实滚动条
            title: "音频预览", //不显示标题
            content: audioHtml
        });
    }

结果:

该录音并不是我测试用的音频文件

注:文中公共参数

    private static Logger logger = LoggerFactory.getLogger(AudioUtils.class);

    private static final int HEAD_LENGTH = 12;

    private static final int FORMAT_LENGTH = 24;

    private static FFMPEGLocator locator = new FFMPEGLocator() {
        @Override
        protected String getFFMPEGExecutablePath() {
            return "F:/ffmpeg/bin/ffmpeg.exe";      //你自己下载的ffmpeg路径
        }
    };

 非常感谢以下几位大佬的博客,因为借鉴太多,我就厚脸皮的都加上了,所以,各位大佬原谅我发布为原创

获取mp3的音频文件信息

mp3文件提取左右声道

获取wav音频文件信息

wav双声道转换为mp3格式

wav单声道合并为双声道

第二种合并方法

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值