Android 音视频开发 - 用MediaExtractor和MediaMuxer分离合成视频

835 阅读2分钟

序言

一个音视频文件是由音频和视频组成的,Android 提供了 MediaExtractor 和 MediaMuxer 类,用来把音频或视频单独抽取出来,然后合成新的视频。

我们分别看一下 API 的使用,实现分离 MP4 视频文件,然后再合成分离的音视频功能。

MediaExtractor 的使用主要有这么几步:

  1. 设置数据源
  2. 获取通道数,找到想要的轨道和通道格式,并切换过去
  3. 循环读取每帧的样本数据
  4. 完成后释放资源

MediaMuxer 的使用和 MediaExtractor 类似:

  1. 设置目标文件路径和音视频格式
  2. 添加感兴趣的通道
  3. 开始合成,循环写入样本数据
  4. 完成后释放资源

下面是个提取并合成视频的示例,MediaExtractor 和 MediaMuxer 一起使用,相当于把原视频的声音通道去掉,生成一个无声的视频文件。

    // 分离视频的纯视频 输入视频 input.mp4,分离保存的视频 output_video.mp4
    private void extractVideo() {
        MediaExtractor mediaExtractor = new MediaExtractor();
        MediaMuxer mediaMuxer = null;
        try {
            File fileDir = FileUtils.getFileDir(this);
            // 设置视频源
            mediaExtractor.setDataSource(new File(fileDir, VIDEO_SOURCE_PATH).getAbsolutePath());
            int videoIndex = -1;
            int maxInputSize = -1;
            int frameRate = -1;
            // 获取数据源的轨道数
            int trackCount = mediaExtractor.getTrackCount();
            // 循环轨道数,找到我们想要的视频轨
            for (int i = 0; i < trackCount; i++) {
                MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
                // 主要描述mime类型的媒体格式
                String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
                // //找到视频轨
                if (mimeType.startsWith("video/")) {
                    videoIndex = i;
                    // 获取视频最大的输入大小
                    maxInputSize = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                    // 获取视频的帧率
                    frameRate = trackFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
                }
            }
            logger.info("frameRate:{}, maxInputSize:{}", frameRate, maxInputSize);
            //切换视频的信道
            mediaExtractor.selectTrack(videoIndex);
            MediaFormat trackFormat = mediaExtractor.getTrackFormat(videoIndex);
            mediaMuxer = new MediaMuxer(new File(fileDir, OUTPUT_VIDEO).getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            //将视频轨添加到 MediaMuxer,并返回新的轨道
            int trackIndex = mediaMuxer.addTrack(trackFormat);
            ByteBuffer byteBuffer = ByteBuffer.allocate(maxInputSize);
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            // 开始合成
            mediaMuxer.start();
            while (true) {
                // 检索当前编码的样本并将其存储在字节缓冲区中
                int readSampleSize = mediaExtractor.readSampleData(byteBuffer, 0);
                //  如果没有可获取的样本则退出循环
                if (readSampleSize < 0) {
                    mediaExtractor.unselectTrack(videoIndex);
                    break;
                }
                // 设置样本编码信息
                bufferInfo.size = readSampleSize;
                bufferInfo.offset = 0;
                bufferInfo.flags = mediaExtractor.getSampleFlags();
                bufferInfo.presentationTimeUs += 1000 * 1000 / frameRate;
                //写入样本数据
                mediaMuxer.writeSampleData(trackIndex, byteBuffer, bufferInfo);
                //推进到下一个样本,类似快进
                mediaExtractor.advance();
            }

            logger.info("finish extract video");
        } catch (IOException e) {
            logger.error(e);
        } finally {
            if (mediaMuxer != null) {
                mediaMuxer.stop();
                mediaMuxer.release();
            }
            mediaExtractor.release();
        }
    }

同样地,分离音频和合成音视频的过程和上面差不多,代码在 GitHub 上,欢迎各位参阅。