一个音视频文件是由音频和视频组成的,Android 提供了 MediaExtractor 和 MediaMuxer 类,用来把音频或视频单独抽取出来,然后合成新的视频。
我们分别看一下 API 的使用,实现分离 MP4 视频文件,然后再合成分离的音视频功能。
MediaExtractor 的使用主要有这么几步:
- 设置数据源
- 获取通道数,切换到想要的轨道
- 循环读取每帧的样本数据
- 完成后释放资源
MediaMuxer 的使用和 MediaExtractor 类似:
- 设置目标文件路径和音视频格式
- 添加要合成的轨道,包括音轨和视轨
- 开始合成,循环写入每帧样本数据
- 完成后释放资源
MediaFormat 封装了媒体数据(音频,视频,字幕)格式的信息,所有信息都以键值对形式表示。MediaFormat 中定义的 key 对于不同媒体数据并不是全部通用的,某些 key 只适用于特定媒体数据。
通用 Keys
KEY_MIME 格式类型
KEY_MAX_INPUT_SIZE 输出缓冲区的最大字节数
KEY_BIT_RATE 比特率
Video Keys
KEY_WIDTH 视频宽度
KEY_HEIGHT 视频高度
KEY_DURATION 内容持续时间(以微妙为单位)
KEY_CORLOR_FORMAT 视频内容颜色空间
KEY_FRAME_RATE 视频帧率
KEY_I_FRAME_INTERVAL 关键之间的时间间隔
KEY_ROTATION 视频旋转顺时针角度
KEY_BITRATE 码率/比特率(画质和文件体积)
KEY_BITRATE_MODE 比特率模式
Audio Keys
KEY_CHANNEL_COUNT 通道数
KEY_SAMPLE_RATE 采样率
KEY_DURATION 内容持续时间(以微妙为单位
下面是个提取并合成视频的示例,MediaExtractor 和 MediaMuxer 一起使用,相当于把原视频的声音通道去掉,生成一个无声的视频文件。
// 分离视频的视频轨,输入视频 input.mp4,输出视频 output_video.mp4
private void extractVideo() {
logger.info("extractVideo() start");
MediaExtractor mediaExtractor = new MediaExtractor();
MediaMuxer mediaMuxer = null;
try {
File fileDir = FileUtils.getFileDir(this);
// 设置视频源
mediaExtractor.setDataSource(new File(fileDir, VIDEO_SOURCE).getAbsolutePath());
// 轨道索引 ID
int videoIndex = -1;
// 视频轨道格式信息
MediaFormat mediaFormat = null;
// 数据源的轨道数(一般有视频,音频,字幕等)
int trackCount = mediaExtractor.getTrackCount();
// 循环轨道数,找到我们想要的视频轨
for (int i = 0; i < trackCount; i++) {
mediaFormat = mediaExtractor.getTrackFormat(i);
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
// //找到要分离的视频轨
if (mimeType.startsWith("video/")) {
videoIndex = i;
break;
}
}
if (mediaFormat == null || videoIndex < 0) {
return;
}
// 最大缓冲区字节数
int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
// 格式类型
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
// 视频的比特率
int bitRate = mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE);
// 视频宽度
int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
// 视频高度
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
// 内容持续时间(以微妙为单位)
long duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
// 视频的帧率
int frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
// 视频内容颜色空间
int colorFormat = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
mediaFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
}
// 关键之间的时间间隔
int iFrameInterval = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_I_FRAME_INTERVAL)) {
iFrameInterval = mediaFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL);
}
// 视频旋转顺时针角度
int rotation = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_ROTATION)) {
rotation = mediaFormat.getInteger(MediaFormat.KEY_ROTATION);
}
// 比特率模式
int bitRateMode = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_BITRATE_MODE)) {
bitRateMode = mediaFormat.getInteger(MediaFormat.KEY_BITRATE_MODE);
}
logger.info("mimeType:{}, maxInputSize:{}, bitRate:{}, width:{}, height:{}" +
", duration:{}ms, frameRate:{}, colorFormat:{}, iFrameInterval:{}" +
", rotation:{}, bitRateMode:{}", mimeType, maxInputSize, bitRate, width, height
, duration / 1000, frameRate, colorFormat, iFrameInterval, rotation, bitRateMode);
//切换视频的轨道
mediaExtractor.selectTrack(videoIndex);
String outPath = new File(fileDir, OUTPUT_VIDEO).getAbsolutePath();
mediaMuxer = new MediaMuxer(outPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
//将视频轨添加到 MediaMuxer,并返回新的轨道
int trackIndex = mediaMuxer.addTrack(mediaFormat);
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 = mediaExtractor.getSampleTime();
//写入样本数据
mediaMuxer.writeSampleData(trackIndex, byteBuffer, bufferInfo);
//推进到下一个样本,类似快进
mediaExtractor.advance();
}
logger.info("finish extract video, path:{}", outPath);
Toast.makeText(this, "分离视频完成", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
logger.error(e);
} finally {
if (mediaMuxer != null) {
mediaMuxer.stop();
mediaMuxer.release();
}
mediaExtractor.release();
}
}
同样地,分离音频和合成音视频的过程和上面差不多,代码在 GitHub 上,欢迎参阅。
参考文章:
- Android 视频分离和合成(MediaMuxer和MediaExtractor)
- Android用MediaExtractor和MediaMuxer合成音视频
- 使用 MediaExtractor 及 MediaCodec 解码音视频