音视频5.4——两个MP3混音合成一个MP3

音视频开发路线:

Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门

demo地址:

GitHub - wygsqsj/videoPath: 音视频学习路线demo

音频

上几个节点我们只是通过对音频的操作熟悉了MediaCodec、MediaExtractor、MediaMuxer的使用,这一节说一下音频的一些基本概念,并将这几个API联合起来,将两个mp3合并成一个mp3文件。

首先声音是一种波形,我们在很多音乐软件或者剪辑软件中都看到音频的展示形式就是波形

音视频5.4——两个MP3混音合成一个MP3_第1张图片

波形是有周期的,比如从波的最高点到下一个最高点就可以看作一个周期,我们的麦克风就是将音频转换成数字存储下来,但是如果波形中每个点都进行采集我们的音频数据就会超级大,是不可取的,所以对应波形来说,采样点要在满足尽量还原波形和减少采样点数之间取一个平衡值。

 音量:音量是通过波形的振幅控制,即波的上下范围越大,音量越高,对应到我们的音频数据上来说就是对原数据进行乘法运算就行,音量0就是音频数据 * 0;音量的单位是db

音调:声音在固定时间中的周期变化次数,也就是频率,我们说某个人音特别高就是他的频率更快,这种周期变化次数的单位就是HZ,人的耳朵能接受的hz上限是44100,常用的采样率有:8kHz/11.025kHz/22.05kHz/16kHz/37.8kHz/44.1kHz/48kHz

通道:采集音频时的通道数量,如果要做立体声,比如影院那种几个喇叭环绕你,如果你的声音是单声道采集的,是没办法把声音拆分成6个部分环绕播放,通道越多,采集的数据就越丰富

对于音频我们用两个字节存储,波形我们通过存储值得正负来还原,音频得表示范围在:-32768~32767之间,我们在初始化音频采集得时候需要指定音频数据的格式,各个格式的摆放顺序如下,记住返回的音频数据格式里低8位在前,高8位在后就可以了

音视频5.4——两个MP3混音合成一个MP3_第2张图片

混音

基本思路初:

1.始化两个分离器MediaExtractor,两个解码器MediaCodec,一个合成器MediaMuxer,一个编码器MediaCodec

2.从两个MP3中读取数据解码成pcm原始文件,对两个pcm文件进行合成

3.将合成后的pcm数据重新编码生成mp3文件

注意点就是PCM文件的混音方法,此处我们仅仅是简单的实现了混音,原理就是从音频1中取出两个byte,音频2中取出两个byte,因为2个byte代表一个完整的音频采样,在音频数据的排列中低8位在前,高8位在后,所以先把高8位和低8位顺序回复正常,获得正常的采样数据

 temp1 = (short) ((mixAudio1[i] & 0xff) | ((mixAudio1[i + 1] & 0xff) << 8));

将2个音频相加即可得到混音后的数据,因为两个short类型的音频相加之后,可能会超过short表示的数值,超过的我们就丢弃了,所以可能会丢失数据,其他的混音方式可以参考:

Android中一种效果奇好的混音方法详解 - 云+社区 - 腾讯云

private void mixingAudio() {
    Log.i(LOG_TAG, "当前audio1集合大小:" + audioQueue1.size());
    Log.i(LOG_TAG, "当前audio2集合大小:" + audioQueue2.size());

    byte[] mixAudio1 = audioQueue1.poll();
    byte[] mixAudio2 = audioQueue2.poll();
    if (mixAudio1 != null && mixAudio2 != null) {
        Log.i(LOG_TAG, "audio1混合数据大小:" + mixAudio1.length);
        Log.i(LOG_TAG, "audio2混合数据大小:" + mixAudio2.length);
        audioData3 = new byte[mixAudio1.length];
        //一个声音采样占2个字节,用short标识即可
        //将两个short字节相加,得到混合后的音频
        for (int i = 0; i < mixAudio1.length; i += 2) {
            //声音数据的排列顺序为低8位在前,高8位在后,此处还原为真实的数据,即低8位放到后面,高8位放到前面
            temp1 = (short) ((mixAudio1[i] & 0xff) | ((mixAudio1[i + 1] & 0xff) << 8));
            if (i + 1 < mixAudio2.length) {
                temp2 = (short) ((mixAudio2[i] & 0xff) | ((mixAudio2[i + 1] & 0xff) << 8));
                //声音大小的控制通过振幅来控制,用当前字节*对应的音量即可得到
                temp1 = (short) (temp1 * volume1 / 100f);
                temp2 = (short) (temp2 * volume2 / 100f);
                temp3 = temp1 + temp2;
                //超出的部分舍弃掉
                if (temp3 > 32767) {
                    temp3 = 32767;
                } else if (temp3 < -32768) {
                    temp3 = -32768;
                }
            } else {
                temp3 = temp1;
            }
            //存入新的数据中
            audioData3[i] = (byte) (temp3 & 0xff);
            audioData3[i + 1] = (byte) ((temp3 >> 8) & 0xff);
        }
        audioQueue3.add(audioData3);
    } else {
        if (hasAudio1 && !hasAudio2 && mixAudio1 != null) {
            Log.i(LOG_TAG, "当前音频1未写入:" + mixAudio1.length);
            audioQueue3.add(mixAudio1);
        }
        if (!hasAudio1 && hasAudio2 && mixAudio2 != null) {
            Log.i(LOG_TAG, "当前音频2未写入:" + mixAudio2.length);
            audioQueue3.add(mixAudio2);
        }
    }
}

代码示例,具体代码参见videoPath/Demo5Activity.java at master · wygsqsj/videoPath · GitHub

//Muxer获取音频轨道,得到format,取出数据
getTrack1();
getTrack2();
//构建MediaCodec解码音频成pcm数据
initMediaCodec1();
initMediaCodec2();
//构建编码器
initEncodeMediaCodec();

while (!finishWrite) {
    //解码成pcm
    decodeAudio1();
    decodeAudio2();
    //将两个pcm数据相加混合成新的数据
    mixingAudio();
    //重新编码写入新的mp3文件中
    writeNewAudio();
}

你可能感兴趣的:(音视频,音视频,android,java)