音频的裁剪、合成、给MP4文件混合背景音频

学习目标:

音频的裁剪、合成、小例子

学习内容:

一、音频的裁剪
1.程序思路
把一段mp3文件裁剪生成一个新的mp3文件,我们通过对一段视频进行解析出pcm码流,然后输出对应的时间段的pcm视频流,然后在这段视频流前面添加wav头部,生成mp3文件,这样就裁剪完成了。
有关添加wav头部的实现请看下面的链接:
音频之WAV格式编码解析
解析mp3文件代码如下:

MediaCodec mediaCodec = null;
            MediaExtractor mediaExtractor = null;
            File pcmFile = null;
            try {
                mediaExtractor = new MediaExtractor();
                mediaExtractor.setDataSource(this.resPath);
                int audioTrack = findTrack(mediaExtractor, "audio/");
                mediaExtractor.selectTrack(audioTrack);
                mediaExtractor.seekTo(startTime, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
                MediaFormat audioFormat = mediaExtractor.getTrackFormat(audioTrack);
                int maxBufferSize = 100_000;
                if (audioFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
                    maxBufferSize = audioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                }
                ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
                //初始化解码器
                mediaCodec = MediaCodec.createDecoderByType(audioFormat.getString(MediaFormat.KEY_MIME));
                mediaCodec.configure(audioFormat, null, null, 0);
                mediaCodec.start();
                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                int decodeInputIndex, outPutIndex;
                pcmFile = new File(Environment.getExternalStorageDirectory(), "out.pcm");
                pcmChannel = new FileOutputStream(pcmFile).getChannel();
                while (isStart) {
                    decodeInputIndex = mediaCodec.dequeueInputBuffer(100_000);
                    if (decodeInputIndex >= 0) {
                        long sampleTimeUs = mediaExtractor.getSampleTime();
                        if (sampleTimeUs == -1) {
                            break;
                        } else if (sampleTimeUs < startTime) {
                            mediaExtractor.advance();
                            continue;
                        } else if (sampleTimeUs > endTime) {
                            break;
                        }
                        //音频数据放入buffer
                        info.size = mediaExtractor.readSampleData(buffer, 0);
                        info.presentationTimeUs = sampleTimeUs;
                        info.flags = mediaExtractor.getSampleFlags();
                        //放入mediaCodec解析
                        ByteBuffer inputBuffer = mediaCodec.getInputBuffer(decodeInputIndex);
                        byte[] content = new byte[buffer.remaining()];
                        buffer.get(content);
                        inputBuffer.put(content);
                        mediaCodec.queueInputBuffer(decodeInputIndex, 0, info.size, info.presentationTimeUs, info.flags);
                        mediaExtractor.advance();
                    }
                    outPutIndex = mediaCodec.dequeueOutputBuffer(info, 100_000);
                    //读取数据
                    while (outPutIndex >= 0) {
                        ByteBuffer outBuffer = mediaCodec.getOutputBuffer(outPutIndex);
                        pcmChannel.write(outBuffer);
                        mediaCodec.releaseOutputBuffer(outPutIndex, false);
                        outPutIndex = mediaCodec.dequeueOutputBuffer(info, 100_000);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                stopClip();
            }

            try {
                if (pcmChannel != null) {
                    pcmChannel.close();
                }
                if (mediaExtractor != null) {
                    mediaExtractor.release();
                }
                if (mediaCodec != null) {
                    mediaCodec.stop();
                    mediaCodec.release();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } 
            //添加wav头部  
            new PcmToWavUtil(44100,  AudioFormat.CHANNEL_IN_STEREO,
                    2, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(pcmFile.getAbsolutePath()
                    , this.outPath);

具体代码demo如下
demo代码链接

二、音频的合成
1.程序思路
这里我们读取视频里的音频轨道和一段音频,进行合并,当然还是需要添加wav头部,这样就完成了。
合并mp3关键代码如下:

//合成两个pcm文件
public class AudioMixThread {

    String musicPath;
    String videoPath;
    String mixedPath;
    int startTime;
    int endTime;
    int musicVolume;
    int videoVolume;

    public AudioMixThread(String musicPath, String videoPath, String mixedPath, int startTime, int endTime, int musicVolume, int videoVolume) {
        this.musicPath = musicPath;
        this.videoPath = videoPath;
        this.startTime = startTime;
        this.endTime = endTime;
        this.musicVolume = musicVolume;
        this.videoVolume = videoVolume;
        this.mixedPath = mixedPath;
    }

    public void mix() {
        if (startTime < 0 || endTime < 0 || startTime > endTime) return;
        FileInputStream musicInput = null;
        FileInputStream videoInput = null;
        FileOutputStream mixedStream = null;
        try {
            float musicVol = formatVolume(musicVolume);
            float videoVol = formatVolume(videoVolume);
            byte[] musicVolBuffer = new byte[2048];
            byte[] videoVolBuffer = new byte[2048];
            byte[] result = new byte[2048];
            musicInput = new FileInputStream(musicPath);
            videoInput = new FileInputStream(videoPath);
            mixedStream = new FileOutputStream(mixedPath);
            short tempVideo, tempMusic;
            int tempResult;
            boolean endMusic = false, endVideo = false;
            while (!endMusic || !endVideo) {
                if (!endMusic) {
                    endMusic = (musicInput.read(musicVolBuffer) == -1);
                    //音乐的pcm数据  写入到 buffer3
                    System.arraycopy(musicVolBuffer, 0, result, 0, musicVolBuffer.length);
                }
                if (!endVideo) {
                    endVideo = (videoInput.read(videoVolBuffer) == -1);
                    //声音的值  跳过下一个声音的值    一个声音 2 个字节
                    for (int i = 0; i < videoVolBuffer.length; i += 2) {
                        //或运算
                        tempMusic = (short) ((musicVolBuffer[i] & 0xff) | (musicVolBuffer[i + 1] & 0xff) << 8);
                        tempVideo = (short) ((videoVolBuffer[i] & 0xff) | (videoVolBuffer[i + 1] & 0xff) << 8);
                        tempResult = (int) (tempMusic * musicVol + tempVideo * videoVol);//音乐和 视频声音 各占一半
                        if (tempResult > Short.MAX_VALUE) {
                            tempResult = Short.MAX_VALUE;
                        } else if (tempResult < Short.MIN_VALUE) {
                            tempResult = Short.MIN_VALUE;
                        }
                        result[i] = (byte) (tempResult & 0xFF);
                        result[i + 1] = (byte) ((tempResult >>> 8) & 0xFF);
                    }
                    mixedStream.write(result);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (musicInput != null) {
                    musicInput.close();
                }
                if (videoInput != null) {
                    videoInput.close();
                }
                if (mixedStream != null) {
                    mixedStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //音量格式化&提高精度
    private static float formatVolume(int volume) {
        return volume / 100f * 1;
    }
}

具体代码demo如下
Demo链接

三、给MP4文件混合背景音频
1.程序思路
这里我们读取视频里的音频轨道和一段mp3音频,进行合并输出到新的音频轨道,读取视频里的视频轨道输出到新的视频轨道,这样就完成了。
关键代码如下:

public class MusicProcess {
    private static int TIMEOUT = 1000;

    public static void mixAudioTrack(
            final Activity activity,
            final String musicPath,//音频路径
            final String videoPath,//视频路径
            final String mixedPath,//生成结果路径
            final Integer startTimeUs,//截取开始时间
            final Integer endTimeUs,//截取结束时间
            int musicVolume,//音频声音大小
            int videoVolume,//视频声音大小l
            final String mixedMusicAndAudioPath//新生成的视频
    ) throws Exception {

        ExecutorService service = Executors.newFixedThreadPool(3);
        final CountDownLatch latch = new CountDownLatch(2);
        final File videoPcmFile = new File(Environment.getExternalStorageDirectory(), "video.pcm");
        final File musicPcmFile = new File(Environment.getExternalStorageDirectory(), "music.pcm");
        final File mixPcmFile = new File(Environment.getExternalStorageDirectory(), "mixed.pcm");
        //解析pcm
        service.execute(() -> {
            try {
                decodeToPCM(videoPath, videoPcmFile.getAbsolutePath(), startTimeUs, endTimeUs);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        });
        //解析pcm
        service.execute(() -> {
            try {
                decodeToPCM(musicPath, musicPcmFile.getAbsolutePath(), startTimeUs, endTimeUs);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        });
        latch.await();
        service.execute(() -> {
            //开始混合两个pcm
            AudioMixThread thread = new AudioMixThread(musicPcmFile.getAbsolutePath(), videoPcmFile.getAbsolutePath(), mixPcmFile.getAbsolutePath(), startTimeUs, endTimeUs, musicVolume, videoVolume);
            thread.mix();
            //生成混合后的mp3文件
            new PcmToWavUtil(44100, AudioFormat.CHANNEL_IN_STEREO,
                    2, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(mixPcmFile.getAbsolutePath()
                    , mixedPath);
            try {
                mixVideoAndMusic(videoPath, mixedMusicAndAudioPath, startTimeUs, endTimeUs, new File(mixedPath));
                ((MainActivity)activity).startPlay();
            } catch (IOException e) {
                e.printStackTrace();
            }
            Log.d("oicq", "mix sucessful!");
        });
    }

    //    MP3 截取并且输出  pcm
    @SuppressLint("WrongConstant")
    public static void decodeToPCM(String musicPath, String outPath, int startTime, int endTime) throws Exception {
        if (endTime < startTime) {
            return;
        }
//    MP3  (zip  rar    ) ----> aac   封装个事 1   编码格式
//        jie  MediaExtractor = 360 解压 工具
        MediaExtractor mediaExtractor = new MediaExtractor();

        mediaExtractor.setDataSource(musicPath);
        int audioTrack = selectTrack(mediaExtractor, "audio/");

        mediaExtractor.selectTrack(audioTrack);
// 视频 和音频
        mediaExtractor.seekTo(startTime, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
// 轨道信息  都记录 编码器
        MediaFormat oriAudioFormat = mediaExtractor.getTrackFormat(audioTrack);
        int maxBufferSize;
        if (oriAudioFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
            maxBufferSize = oriAudioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
        } else {
            maxBufferSize = 100 * 1000;
        }
        ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
//        h264   H265  音频
        MediaCodec mediaCodec = MediaCodec.createDecoderByType(oriAudioFormat.getString((MediaFormat.KEY_MIME)));
//        设置解码器信息    直接从 音频文件
        mediaCodec.configure(oriAudioFormat, null, null, 0);
        File pcmFile = new File(outPath);
        FileChannel writeChannel = new FileOutputStream(pcmFile).getChannel();
        mediaCodec.start();
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        int outputBufferIndex = -1;
        while (true) {
            int decodeInputIndex = mediaCodec.dequeueInputBuffer(100000);
            if (decodeInputIndex >= 0) {
                long sampleTimeUs = mediaExtractor.getSampleTime();

                if (sampleTimeUs == -1) {
                    break;
                } else if (sampleTimeUs < startTime) {
//                    丢掉 不用了
                    mediaExtractor.advance();
                    continue;
                } else if (sampleTimeUs > endTime) {
                    break;
                }
//                获取到压缩数据
                info.size = mediaExtractor.readSampleData(buffer, 0);
                info.presentationTimeUs = sampleTimeUs;
                info.flags = mediaExtractor.getSampleFlags();

//                下面放数据  到dsp解码
                byte[] content = new byte[buffer.remaining()];
                buffer.get(content);
//                输出文件  方便查看
//                FileUtils.writeContent(content);
//                解码
                ByteBuffer inputBuffer = mediaCodec.getInputBuffer(decodeInputIndex);
                inputBuffer.put(content);
                mediaCodec.queueInputBuffer(decodeInputIndex, 0, info.size, info.presentationTimeUs, info.flags);
//                释放上一帧的压缩数据
                mediaExtractor.advance();
            }

            outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 100_000);
            while (outputBufferIndex >= 0) {
                ByteBuffer decodeOutputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex);
                writeChannel.write(decodeOutputBuffer);//MP3  1   pcm2
                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 100_000);
            }
        }
        writeChannel.close();
        mediaExtractor.release();
        mediaCodec.stop();
        mediaCodec.release();
    }

    //找出对应的轨道
    private static int selectTrack(MediaExtractor mediaExtractor, String fo) {
        //获取每条轨道
        int numTracks = mediaExtractor.getTrackCount();
        for (int i = 0; i < numTracks; i++) {
            MediaFormat format = mediaExtractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith(fo)) {
                return i;
            }
        }
        return -1;
    }

    public static void mixVideoAndMusic(String videoInput, String output, Integer startTimeUs, Integer endTimeUs, File wavFile) throws IOException {
        //初始化一个视频封装容器
        MediaMuxer mediaMuxer = new MediaMuxer(output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

        MediaExtractor mediaExtractor = new MediaExtractor();
        mediaExtractor.setDataSource(videoInput);

        //视频的视频轨道
        int video_videoIndex = selectTrack(mediaExtractor, "video/");
        //视频的音频轨道
        int video_audioIndex = selectTrack(mediaExtractor, "audio/");

        //新建视频轨道
        MediaFormat videoFormat = mediaExtractor.getTrackFormat(video_videoIndex);
        int mixedVideoTrack = mediaMuxer.addTrack(videoFormat);

        //新建音频轨道
        MediaFormat audioFormat1 = mediaExtractor.getTrackFormat(video_audioIndex);
        int audioBitRate = 16_000;
        if(audioFormat1.containsKey(MediaFormat.KEY_BIT_RATE)) {
            audioBitRate = audioFormat1.getInteger(MediaFormat.KEY_BIT_RATE);
        }
        audioFormat1.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
        //添加一个空的音频轨道  轨道格式取自 视频文件,跟视频所有信息一样
        int mixedAudioTrack = mediaMuxer.addTrack(audioFormat1);
        mediaMuxer.start();



        //处理音频
        MediaExtractor audioExtrator = new MediaExtractor();
        audioExtrator.setDataSource(wavFile.getAbsolutePath());
        int audioTrack = selectTrack(audioExtrator,"audio/");
        audioExtrator.selectTrack(audioTrack);
        MediaFormat audioFormat = audioExtrator.getTrackFormat(audioTrack);
        //最大一帧的大小
        int maxBufferSize;
        if (audioFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
            maxBufferSize = audioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
        } else {
            maxBufferSize = 100 * 1000;
        }

        MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100, 2);
        //比特率
        encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, audioBitRate);
        //音质等级
        encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        //最大一帧大小
        encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxBufferSize);
        //初始化音频编码器
        MediaCodec audioMediaCodecEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
        audioMediaCodecEncoder.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        audioMediaCodecEncoder.start();
        ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        boolean encodeDone = false;
        while (!encodeDone) {
            int inputBufferIndex = audioMediaCodecEncoder.dequeueInputBuffer(10000);
            if (inputBufferIndex >= 0) {
                long sampleTime = audioExtrator.getSampleTime();
                if (sampleTime < 0) {
                    //pts小于0 来到了文件末尾 通知编码器 不用编码了
                    audioMediaCodecEncoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                } else {
                    int flags = audioExtrator.getSampleFlags();
                    int size = audioExtrator.readSampleData(buffer, 0);
                    ByteBuffer inputBuffer = audioMediaCodecEncoder.getInputBuffer(inputBufferIndex);
                    inputBuffer.clear();
                    inputBuffer.put(buffer);
                    inputBuffer.position(0);
                    audioMediaCodecEncoder.queueInputBuffer(inputBufferIndex, 0, size, sampleTime, flags);
                    //前进到下一个样本
                    audioExtrator.advance();
                }
            }
            //获取编码完的数据
            int outputBufferIndex = audioMediaCodecEncoder.dequeueOutputBuffer(info, TIMEOUT);
            while (outputBufferIndex >= 0) {
                if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                    encodeDone = true;
                    break;
                }
                ByteBuffer encodeOutputBuffer = audioMediaCodecEncoder.getOutputBuffer(outputBufferIndex);
                //将编码好的数据(aac)输出
                mediaMuxer.writeSampleData(mixedAudioTrack, encodeOutputBuffer, info);
                encodeOutputBuffer.clear();
                audioMediaCodecEncoder.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = audioMediaCodecEncoder.dequeueOutputBuffer(info, TIMEOUT);
            }
        }

        //处理视频
        if (video_audioIndex >= 0) {
            mediaExtractor.unselectTrack(video_audioIndex);
        }
        mediaExtractor.selectTrack(video_videoIndex);
        mediaExtractor.seekTo(startTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
        maxBufferSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
        buffer = ByteBuffer.allocateDirect(maxBufferSize);
        while (true) {
            long sampleTimeUs = mediaExtractor.getSampleTime();
            if (sampleTimeUs == -1) {
                break;
            }
            if (sampleTimeUs < startTimeUs) {
                mediaExtractor.advance();
                continue;
            }
            if (endTimeUs != null && sampleTimeUs > endTimeUs) {
                break;
            }
            info.presentationTimeUs = sampleTimeUs - startTimeUs + 600;
            info.flags = mediaExtractor.getSampleFlags();
            //读取视频文件的数据  画面 数据
            info.size = mediaExtractor.readSampleData(buffer, 0);
            if (info.size < 0) {
                break;
            }
            //视频轨道  画面写完了
            mediaMuxer.writeSampleData(mixedVideoTrack, buffer, info);
            mediaExtractor.advance();
        }
        try {
            audioExtrator.release();
            mediaExtractor.release();
            audioMediaCodecEncoder.stop();
            audioMediaCodecEncoder.release();
            mediaMuxer.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里生成的新的mp4文件,用windows10自带播放器播放只有音频没有画面,用Vlan播放器可以播放。

Demo链接

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值