Android 的音视频分离与合成需要用到 MediaExtractor 和 MediaMuxer,MediaExtractor 可以从一个媒体文件中提取出音频或视频流,并以数据块(buffer)的形式提供给应用程序,MediaMuxer 则可以将音频或视频数据存储到容器中,并将其写入到指定的输出文件中。
分离
MediaExtractor 可以用来分离容器中的视频轨道和音频轨道,支持多种常见的媒体格式,例如 MP4,3GP,WebM,FLV,MPEG-TS 等等。
主要 API 如下:
- setDataSource(String path):设置媒体文件的路径
- getTrackCount():获取媒体文件中的音视频轨道数量
- getTrackFormat(int index):获取指定音视频轨道的格式
- selectTrack(int index):选择指定音视频轨道
- readSampleData(ByteBuffer byteBuf, int offset):读取一帧数据
- advance():读取下一帧数据
- release():释放资源
举个例子,现在有个 mp4 格式的音视频,需要将它的视频和音频分离开来,怎么做呢?这就可以使用 MediaExtractor ,大概的使用步骤是:设置数据源,获取轨道数,选择特定的轨道,然后循环读取每帧的样本数据,完成后释放资源即可,代码如下:
private fun separateVideo() {
val mediaExtractor = MediaExtractor()
// 源视频存放的路径
val filePath = getExternalFilesDir(null)!!.absolutePath + "/test_video.mp4"
try {
// 设置数据源,可以是本地文件或者网络地址。
mediaExtractor.setDataSource(filePath)
// 获取轨道数
val trackCount = mediaExtractor.trackCount
// 遍历轨道,查看音频轨道或视频轨道信息
for (i in 0 until trackCount) {
// 获取某一个轨道的媒体格式
val trackFormat = mediaExtractor.getTrackFormat(i)
val keyMime = trackFormat.getString(MediaFormat.KEY_MIME)
if (keyMime.isNullOrEmpty()) {
continue
}
// 通过 MIME 信息识别音频轨道和视频轨道
if (keyMime.startsWith("video/")) {
val outputFile = getOutputFile(mediaExtractor, i, "/video.mp4")
Log.i(TAG, "video file path:${outputFile.absolutePath}")
} else if (keyMime.startsWith("audio/")) {
val outputFile = getOutputFile(mediaExtractor, i, "/audio.aac")
Log.i(TAG, "audio file path:${outputFile.absolutePath}")
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}
其中,getOutputFile 方法就是确定音频轨道或视频轨道后的文件输出,代码如下:
@Throws(IOException::class)
private fun getOutputFile(mediaExtractor: MediaExtractor, i: Int, outputName: String): File {
val trackFormat = mediaExtractor.getTrackFormat(i)
mediaExtractor.selectTrack(i)
// 文件保存路径
val outputFile =
File(getExternalFilesDir(Environment.DIRECTORY_MUSIC)!!.absolutePath + outputName)
if (outputFile.exists()) {
outputFile.delete()
}
val mediaMuxer =
MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
// 添加轨道信息
mediaMuxer.addTrack(trackFormat)
// 开始合成
mediaMuxer.start()
// 设置每一帧的大小
val buffer = ByteBuffer.allocate(500 * 1024)
val bufferInfo = MediaCodec.BufferInfo()
var sampleSize: Int
// 循环读取每帧样本数据
while (mediaExtractor.readSampleData(buffer, 0).also { sampleSize = it } > 0) {
bufferInfo.apply {
flags = mediaExtractor.sampleFlags
offset = 0
size = sampleSize
presentationTimeUs = mediaExtractor.sampleTime
}
// 通过 mediaExtractor 解封装的数据通过 writeSampleData 写入到对应的轨道
mediaMuxer.writeSampleData(0, buffer, bufferInfo)
// 读取下一帧数据
mediaExtractor.advance()
}
// 提取完毕
mediaExtractor.unselectTrack(i)
mediaMuxer.stop()
mediaMuxer.release()
return outputFile
}
执行程序之后,我们就会发现 test_video.mp4 这个文件被分离成了视频和音频两个文件。
合成
现在,我们把源音视频文件 test_video.mp4 删了,通过 audio.aac 音频文件和 video.mp4 视频文件合成一个音视频文件 test_video.mp4,这就使用到了 MediaMuxer,其实上面的代码也有用到,只是用途不同而已。MediaMuxer 除了可以生成音频或视频文件,还可以把音频与视频合成一个音视频文件。
主要 API 如下:
- MediaMuxer(String path, int format):path 为输出文件的名称,format 指输出文件的格式
- addTrack(MediaFormat format):添加轨道
- start():开始合成文件
- writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo):把ByteBuffer 中的数据写入到在构造器设置的文件中
- stop():停止合成文件
- release():释放资源
MediaMuxer 大概的使用步骤是:设置目标文件路径和音视频格式,添加要合成的轨道,包括音频轨道和视频轨道,然后开始合成,循环写入每帧样本数据,完成后释放即可,代码如下:
private fun compositeVideo(): String? {
val videoFile = File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "video.mp4")
val audioFile = File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "audio.aac")
// 输出文件
val outputFile = File(getExternalFilesDir(null), "test_video.mp4")
if (outputFile.exists()) {
outputFile.delete()
}
if (!videoFile.exists() || !audioFile.exists()) {
return null
}
val videoExtractor = MediaExtractor()
val audioExtractor = MediaExtractor()
try {
val mediaMuxer =
MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
var videoTrackIndex = 0
var audioTrackIndex = 0
//添加视频轨道
videoExtractor.setDataSource(videoFile.absolutePath)
val videoTrackCount = videoExtractor.trackCount
for (i in 0 until videoTrackCount) {
val trackFormat = videoExtractor.getTrackFormat(i)
val mimeType = trackFormat.getString(MediaFormat.KEY_MIME)
if (mimeType.isNullOrEmpty()) {
continue
}
if (mimeType.startsWith("video/")) {
videoExtractor.selectTrack(i)
videoTrackIndex = mediaMuxer.addTrack(trackFormat)
break
}
}
// 添加音频轨道
audioExtractor.setDataSource(audioFile.absolutePath)
val audioTrackCount = audioExtractor.trackCount
for (i in 0 until audioTrackCount) {
val trackFormat = audioExtractor.getTrackFormat(i)
val mimeType = trackFormat.getString(MediaFormat.KEY_MIME)
if (mimeType.isNullOrEmpty()) {
continue
}
if (mimeType.startsWith("audio/")) {
audioExtractor.selectTrack(i)
audioTrackIndex = mediaMuxer.addTrack(trackFormat)
break
}
}
// 开始合成
mediaMuxer.start()
val byteBuffer = ByteBuffer.allocate(500 * 1024)
val bufferInfo = MediaCodec.BufferInfo()
var videoSampleSize: Int
while (videoExtractor.readSampleData(byteBuffer, 0).also { videoSampleSize = it } > 0) {
bufferInfo.apply {
flags = videoExtractor.sampleFlags
offset = 0
size = videoSampleSize
presentationTimeUs = videoExtractor.sampleTime
}
mediaMuxer.writeSampleData(videoTrackIndex, byteBuffer, bufferInfo)
videoExtractor.advance()
}
var audioSampleSize: Int
val audioBufferInfo = MediaCodec.BufferInfo()
while (audioExtractor.readSampleData(byteBuffer, 0).also { audioSampleSize = it } > 0) {
audioBufferInfo.apply {
flags = audioExtractor.sampleFlags
offset = 0
size = audioSampleSize
presentationTimeUs = audioExtractor.sampleTime
}
mediaMuxer.writeSampleData(audioTrackIndex, byteBuffer, audioBufferInfo)
audioExtractor.advance()
}
// 释放资源
videoExtractor.release()
audioExtractor.release()
mediaMuxer.stop()
mediaMuxer.release()
return outputFile.absolutePath
} catch (e: IOException) {
e.printStackTrace()
return null
}
}
音视频开发的重要性
在当今的移动应用市场中,音视频功能已成为吸引用户、提升用户体验的重要手段。通过集成音视频技术,开发者可以:
- 增强互动性:实时音视频通话功能让用户感觉彼此更近。
- 提升教育效果:在线教育应用通过视频讲解和实时互动,提高学习效率。
- 丰富内容展示:视频内容比静态图片和文字更能吸引用户的注意力。
核心音视频技术
1. MediaCodec API
MediaCodec API提供了对音频和视频编解码器的访问,使得开发者能够对音视频数据进行编码和解码操作。这是处理音视频数据的基础,也是实现高质量音视频应用的关键。
2. ExoPlayer
ExoPlayer是一个开源的、可扩展的音视频播放器,支持广泛的音视频格式。它提供了比Android自带的MediaPlayer更加灵活和强大的功能,如自适应流播放、多音频轨道支持等。
3. WebRTC
WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音对话或视频对话的API。在Android开发中,WebRTC可以用来实现点对点的音视频通话功能。
4. OpenGL ES
OpenGL ES是嵌入式系统上的OpenGL 3D图形库的子集,它为Android应用提供了强大的2D和3D图形渲染能力。在音视频开发中,OpenGL ES常用于实现视频滤镜、水印等特效。
实战案例:打造一个简单的视频播放器
为了更好地理解音视频开发,让我们来看一个简单的视频播放器的实现步骤:
-
创建一个布局文件:定义视频播放器的界面,通常包括一个SurfaceView来显示视频。
-
初始化ExoPlayer:在Activity或Fragment中创建ExoPlayer实例,并配置音视频源。
-
准备视频源:可以是本地文件、网络URL或者自定义的MediaSource。
-
绑定视频到SurfaceView:使用ExoPlayer的
setSurface
方法将视频输出到SurfaceView。 -
控制播放:实现播放、暂停、停止等控制逻辑。
-
处理生命周期:确保在Activity或Fragment的生命周期事件中正确管理ExoPlayer的创建、释放等。
结语
音视频开发为Android带来了无限可能,从基础的播放功能到复杂的实时通讯,开发者可以利用Android提供的强大API和第三方库,打造出功能丰富、用户体验卓越的多媒体应用。随着技术的不断进步,音视频开发将继续在Android平台上扮演重要角色,为用户带来更加精彩的移动体验。
音视频的学习之路
不少人在音视频初级入门过程中只是接触Android多媒体展示相关的API,通过单独的列举和使用这些API,只能让你对Android音视频处理有一个基本的轮廓,知识点都是零散的,根本没有有效的途径将所有知识点串联起来。
这样对于音视频的了解和控制就仅仅局限于最外层的API了,在深入学习之前,往往这些API就已经把脑袋都弄大了,而且,仅仅停留在使用API的层次,不能让你适应不断变化的需求。
如果最开始的方向都错了,那么不管你如何努力,都学不好音视频!
而如果是跟着正确的学习路线一步步深挖,那么一切都不是问题!
这里给大家推荐一份音视频开发进阶文档,让初学者可以比较“柔顺丝滑”地入门,即使是老司机也能得到不少收获。【扫描下方二维码即可免费领取!!】👇👇

第1章 Android音视频硬解码篇
- 1.1 音视频基础知识
- 1.2 音视频硬解码流程:封装基础解码框
- 1.3 音视频播放:音视频同步
- 1.4 音视频解封和封装:生产一个MP4
第2章 使用OpenGL渲染视频画面篇
- 2.1 初步了解OpenGL ES
- 2.2 使用OpenGL渲染视频画面
- 2.3 OpenGL渲染多视频,实现画中画
- 2.4 深入了解OpenGL之EGL
- 2.5.2 FBO简介
- 2.6 Android音视频硬编码:生成一个MP4
第3章 Android FFmpeg音视频解码篇
- 3.1 FFmpeg so库编译
- 3.2 Android 引入FFmpeg
- 3.3 Android FFmpeg视频解码播放
- 3.4Android FFmpeg+OpenSL ES音频解码播放
- 3.5 Android FFmpeg+OpenGL ES播放视频
- 3.6 FFmpeg简单合成MP4:视屏解封与重新封装
- 3.7 Android FFmpeg 视频编码
第4章 直播系统聊天技术
- 4.1 百万在线的美拍直播弹幕系统的实时推送技术实践之路
- 4.2 阿里电商IM消息平台,在群聊、直播场景下的技术实践
- 4.3 微信直播聊天室单房间1500万在线的消息架构演进之路
- 4.4 百度直播的海量用户实时消息系统架构演进实践
- 4.5 微信小游戏直播在Android端的跨进程渲染推流实践
第5章 阿里IM技术分享
- 5.1 企业级IM王者——钉钉在后端架构上的过人之处
- 5.2 闲鱼IM基于Flutter的移动端跨端改造实践
- 5.3 闲鱼亿级IM消息系统的架构演进之路
- 5.4 闲鱼亿级IM消息系统的可靠投递优化实践
完整学习资料领取方式:扫描下方二维码领取~~~
