1. MediaMuxer初探:什么是MediaMuxer?它的核心作用是什么?MediaMuxer与MediaExtractor的关系

各位同学,欢迎来到《Android音视频编辑:MediaMuxer从入门到精通》的第一章。

说实话,我刚开始接触Android音视频开发时,面对一堆API也是一头雾水。MediaMuxer、MediaExtractor、MediaCodec……这些名字看着就让人头大。但后来我发现,只要搞清楚了它们各自「管什么」,整个体系就清晰了。

今天我们就从MediaMuxer开始聊起。

1.1 什么是MediaMuxer?

MediaMuxer,直译过来就是「媒体混合器」。你想想看,我们平时录制的视频文件,里面既有画面(视频轨道),又有声音(音频轨道),还可能包含字幕轨道。这些轨道数据是怎么「打包」成一个.mp4文件的?

嗯,这就是MediaMuxer干的事。

用一句话概括:MediaMuxer负责将编码后的音视频数据,按照指定格式封装成多媒体文件

我个人习惯把它比作「打包工人」。你给它视频流、音频流,它帮你装进一个盒子里,贴上标签(格式),最后输出一个完整的文件。

核心要点:

  • MediaMuxer 是 写入端,只负责封装,不负责编码
  • 它接收的是 已经编码好的数据(比如H.264视频流、AAC音频流)
  • 输出格式支持:MP4、WebM、3GPP 等

1.2 MediaMuxer的核心作用

说白了,MediaMuxer就做三件事:

  1. 创建输出文件——指定封装格式(比如MP4)和输出路径
  2. 添加轨道——告诉它你要往文件里塞几路流,每路流的格式是什么
  3. 写入数据——把编码后的数据包按时间顺序写进去

我在项目中遇到过这样一个场景:客户要求把摄像头采集的画面和麦克风采集的声音,实时合成为一个MP4文件。当时我第一反应就是用MediaMuxer。你猜怎么着?核心代码其实就几十行。

来看一个最简示例:

// 1. 创建MediaMuxer,指定输出格式为MP4
MediaMuxer muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

// 2. 添加视频轨道(假设已经配置好MediaFormat)
int videoTrackIndex = muxer.addTrack(videoFormat);

// 3. 添加音频轨道
int audioTrackIndex = muxer.addTrack(audioFormat);

// 4. 开始封装
muxer.start();

// 5. 写入数据(循环中不断写入)
muxer.writeSampleData(videoTrackIndex, byteBuffer, bufferInfo);
muxer.writeSampleData(audioTrackIndex, byteBuffer, bufferInfo);

// 6. 结束
muxer.stop();
muxer.release();

你看,流程非常清晰。但这里有个坑——必须先添加所有轨道,再调用start()。我曾经有一次忘了这个顺序,结果运行时直接崩溃,排查了半天才发现是轨道还没加完就start了。

注意:

  • addTrack() 必须在 start() 之前调用
  • 一旦 start() 之后,就不能再添加新轨道了
  • writeSampleData() 必须在 start() 之后、stop() 之前调用

1.3 MediaMuxer与MediaExtractor的关系

聊完MediaMuxer,咱们得说说它的「好搭档」——MediaExtractor。

MediaExtractor是干嘛的?它是解封装器,负责从多媒体文件中读取音视频数据。说白了,MediaMuxer是「装进去」,MediaExtractor是「取出来」。

你想想看,如果你要做视频剪辑,是不是得先读取源文件?这时候MediaExtractor就派上用场了。它把MP4文件里的视频轨道和音频轨道分别提取出来,然后你才能做处理。处理完之后呢?再用MediaMuxer封装成新文件。

我经常把它们比作「拆箱工人」和「装箱工人」:

  • MediaExtractor:从文件里拆出原始数据流
  • MediaMuxer:把处理后的数据流装进新文件

它们俩配合使用,就能实现很多功能:视频裁剪、音频替换、画中画、变速播放……

来看一个典型的配合流程:

// 读取源文件
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(inputPath);

// 选择视频轨道
int videoTrack = selectTrack(extractor, "video/");
extractor.selectTrack(videoTrack);

// 创建输出
MediaMuxer muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
int outVideoIndex = muxer.addTrack(extractor.getTrackFormat(videoTrack));
muxer.start();

// 读取并写入
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
while (true) {
    int sampleSize = extractor.readSampleData(buffer, 0);
    if (sampleSize < 0) break;
    info.offset = 0;
    info.size = sampleSize;
    info.presentationTimeUs = extractor.getSampleTime();
    info.flags = extractor.getSampleFlags();
    muxer.writeSampleData(outVideoIndex, buffer, info);
    extractor.advance();
}

muxer.stop();
muxer.release();
extractor.release();

小技巧:

实际开发中,我建议把MediaExtractor和MediaMuxer的创建、释放逻辑封装成工具类。这样每次做音视频处理时,只需要关注中间的处理逻辑,不用重复写样板代码。

1.4 一张图看懂核心关系

为了让大家更直观地理解,我画了一张流程图:

MediaMuxer 与 MediaExtractor 核心关系图 源文件 input.mp4 MediaExtractor 解封装(读取) 拆出视频流、音频流 中间处理 裁剪/变速/滤镜等 (可选步骤) 也可直接透传 MediaMuxer 封装(写入) 合成视频流+音频流 输出文件 output.mp4 虚线表示可直接从Extractor到Muxer,实现「转封装」

从这张图可以看得很清楚:

  • MediaExtractor 负责从源文件里「拆」出数据
  • MediaMuxer 负责把数据「装」进新文件
  • 中间可以加处理步骤,也可以直接透传(这叫「转封装」,不改变编码格式)

1.5 避坑指南

最后,分享几个我踩过的坑:

我曾经犯过的错:

  • 轨道索引搞混:addTrack()返回的索引是从0开始的,写入数据时一定要用对的索引。我试过把视频数据写到音频轨道上,结果播放器直接罢工。
  • 时间戳不连续:MediaMuxer要求写入的数据包时间戳必须是递增的。如果你做变速处理,一定要重新计算时间戳,否则封装出来的文件会卡顿。
  • 忘记释放资源:MediaMuxer和MediaExtractor都持有底层资源,用完后必须release()。我早期写代码时经常忘记,导致内存泄漏,应用跑一会儿就OOM了。

我的建议:

刚开始学习时,先不要急着做复杂功能。从「读取一个视频文件,原样封装成另一个文件」开始,也就是转封装。这个流程走通了,你对MediaMuxer和MediaExtractor的理解就到位了。后面再慢慢加裁剪、变速、滤镜等功能。

好了,第一章就到这里。MediaMuxer其实没那么神秘,它就是帮你把音视频数据「打包」成文件的工具。搞清楚了它的定位,后面学起来就轻松多了。


公众号:蓝海资料掘金营,微信deep3321