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就做三件事:
- 创建输出文件——指定封装格式(比如MP4)和输出路径
- 添加轨道——告诉它你要往文件里塞几路流,每路流的格式是什么
- 写入数据——把编码后的数据包按时间顺序写进去
我在项目中遇到过这样一个场景:客户要求把摄像头采集的画面和麦克风采集的声音,实时合成为一个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 一张图看懂核心关系
为了让大家更直观地理解,我画了一张流程图:
从这张图可以看得很清楚:
- MediaExtractor 负责从源文件里「拆」出数据
- MediaMuxer 负责把数据「装」进新文件
- 中间可以加处理步骤,也可以直接透传(这叫「转封装」,不改变编码格式)
1.5 避坑指南
最后,分享几个我踩过的坑:
我曾经犯过的错:
- 轨道索引搞混:addTrack()返回的索引是从0开始的,写入数据时一定要用对的索引。我试过把视频数据写到音频轨道上,结果播放器直接罢工。
- 时间戳不连续:MediaMuxer要求写入的数据包时间戳必须是递增的。如果你做变速处理,一定要重新计算时间戳,否则封装出来的文件会卡顿。
- 忘记释放资源:MediaMuxer和MediaExtractor都持有底层资源,用完后必须release()。我早期写代码时经常忘记,导致内存泄漏,应用跑一会儿就OOM了。
我的建议:
刚开始学习时,先不要急着做复杂功能。从「读取一个视频文件,原样封装成另一个文件」开始,也就是转封装。这个流程走通了,你对MediaMuxer和MediaExtractor的理解就到位了。后面再慢慢加裁剪、变速、滤镜等功能。
好了,第一章就到这里。MediaMuxer其实没那么神秘,它就是帮你把音视频数据「打包」成文件的工具。搞清楚了它的定位,后面学起来就轻松多了。