应用 MediaExtractor 和 MediaMuxer 解析和封装 mp4 文件
简介
MP4 或称 MPEG- 4 第 14 局部是一种规范的数字多媒体容器格局。
MP4 中的音频格式通常为 AAC(audio/mp4a-latm)
MediaExtractor
MediaExtractor 可用于拆散多媒体容器中视频 track 和音频 track
setDataSource()
设置数据源,数据源能够是本地文件地址,也能够是网络地址getTrackFormat(int index)
来获取各个 track 的MediaFormat
,通过MediaFormat
来获取 track 的详细信息,如:MimeType、分辨率、采样频率、帧率等等selectTrack(int index)
通过下标抉择指定的通道readSampleData(ByteBuffer buffer, int offset)
获取以后编码好的数据并存在指定好偏移量的 buffer 中
MediaMuxer
MediaMuxer 可用于混合根本码流。将所有的信道的信息合成一个视频。目前输入格局反对 MP4,Webm,3GP。从 Android Nougat 开始反对向 MP4 中混入 B-frames。
提取并输入 MP4 文件中的视频局部
从一个 MP4 文件中提取出视频,失去不含音频的 MP4 文件。
实现流程,首先是应用 MediaExtractor
提取,而后应用 MediaMuxer
输入 MP4 文件。
MediaExtractor
设置数据源,找到并抉择视频轨道的格局和下标MediaMuxer
设置输入格局为MUXER_OUTPUT_MPEG_4
,增加后面选定的格局,调用start()
启动MediaExtractor
读取帧数据,不停地将帧数据和相干信息传入MediaMuxer
- 最初进行并开释
MediaMuxer
和MediaExtractor
最好放在子线程中操作。
/**
* 提取视频
*
* @param sourceVideoPath 原始视频文件
* @throws Exception 出错
*/
public static void extractVideo(String sourceVideoPath, String outVideoPath) throws Exception {MediaExtractor sourceMediaExtractor = new MediaExtractor();
sourceMediaExtractor.setDataSource(sourceVideoPath);
int numTracks = sourceMediaExtractor.getTrackCount();
int sourceVideoTrackIndex = -1; // 原始视频文件视频轨道参数
for (int i = 0; i < numTracks; ++i) {MediaFormat format = sourceMediaExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
Log.d(TAG, "MediaFormat:" + mime);
if (mime.startsWith("video/")) {sourceMediaExtractor.selectTrack(i);
sourceVideoTrackIndex = i;
Log.d(TAG, "selectTrack index=" + i + "; format:" + mime);
break;
}
}
MediaMuxer outputMediaMuxer = new MediaMuxer(outVideoPath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
outputMediaMuxer.addTrack(sourceMediaExtractor.getTrackFormat(sourceVideoTrackIndex));
outputMediaMuxer.start();
ByteBuffer inputBuffer = ByteBuffer.allocate(1024 * 1024 * 2); // 调配的内存要尽量大一些
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int sampleSize;
while ((sampleSize = sourceMediaExtractor.readSampleData(inputBuffer, 0)) >= 0) {long presentationTimeUs = sourceMediaExtractor.getSampleTime();
info.offset = 0;
info.size = sampleSize;
info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
info.presentationTimeUs = presentationTimeUs;
outputMediaMuxer.writeSampleData(sourceVideoTrackIndex, inputBuffer, info);
sourceMediaExtractor.advance();}
outputMediaMuxer.stop();
outputMediaMuxer.release(); // 进行并开释 MediaMuxer
sourceMediaExtractor.release();
sourceMediaExtractor = null; // 开释 MediaExtractor
}
如果下面的 ByteBuffer
调配的空间太小,readSampleData(inputBuffer, 0)
可能会呈现 IllegalArgumentException
异样。
提取 MP4 文件中的音频局部,获取音频文件
基于 Java MP4 Parser
提取出 AAC 文件的办法
Java MP4 Parser
– https://github.com/sannies/mp4parser Java 实现读、写和创立 MP4 容器。然而和编解码音视频有区别。这里次要是提取与再合成。
下载 isoparser-1.1.22.jar
并增加进工程中;尝试过 gradle 间接导入,但不胜利
找到视频文件中所有的音轨,将它们提取进去写入新的文件中
public void extractAudioFromMP4(String outAudioPath, String sourceMP4Path) throws IOException {Movie movie = MovieCreator.build(sourceMP4Path);
List<Track> audioTracks = new ArrayList<>();
for (Track t : movie.getTracks()) {if (t.getHandler().equals("soun")) {audioTracks.add(t);
}
}
Movie result = new Movie();
if (audioTracks.size() > 0) {result.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
}
Container out = new DefaultMp4Builder().build(result);
FileChannel fc = new RandomAccessFile(outAudioPath, "rw").getChannel();
out.writeContainer(fc);
fc.close();}
在红米手机上测试胜利。从 MP4 文件(时长约 2 分 20 秒)中提取出的 AAC 文件可在手机上间接播放。
将 AAC 音轨换到另一个 MP4 文件
MediaExtractor 能够间接从提取 AAC 文件或 MP4 文件中提取 ACC 音轨,MediaMuxer 来写入新的 MP4 文件。
提供音频的文件能够是 MP4 文件,也能够是 AAC 文件;另一个提供视频,混合输入新的 MP4 文件。
生成的视频的长度由提供视频的文件决定。
/**
* @param outputVideoFilePath 输入视频文件门路
* @param videoProviderPath 提供视频的 MP4 文件 时长以此为准
* @param audioProviderPath 提供音频的文件
* @throws Exception 运行异样 例如读写文件异样
*/
public static void replaceAudioForMP4File(String outputVideoFilePath, String videoProviderPath,
String audioProviderPath)
throws Exception {
MediaMuxer mediaMuxer = new MediaMuxer(outputVideoFilePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
// 视频 MediaExtractor
MediaExtractor mVideoExtractor = new MediaExtractor();
mVideoExtractor.setDataSource(videoProviderPath);
int videoTrackIndex = -1;
for (int i = 0; i < mVideoExtractor.getTrackCount(); i++) {MediaFormat format = mVideoExtractor.getTrackFormat(i);
if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {mVideoExtractor.selectTrack(i);
videoTrackIndex = mediaMuxer.addTrack(format);
Log.d(TAG, "Video: format:" + format);
break;
}
}
// 音频 MediaExtractor
MediaExtractor audioExtractor = new MediaExtractor();
audioExtractor.setDataSource(audioProviderPath);
int audioTrackIndex = -1;
for (int i = 0; i < audioExtractor.getTrackCount(); i++) {MediaFormat format = audioExtractor.getTrackFormat(i);
if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {audioExtractor.selectTrack(i);
audioTrackIndex = mediaMuxer.addTrack(format);
Log.d(TAG, "Audio: format:" + format);
break;
}
}
mediaMuxer.start(); // 增加完所有轨道后 start
long videoEndPreTimeUs = 0;
// 封装视频 track
if (-1 != videoTrackIndex) {MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
info.presentationTimeUs = 0;
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
int sampleSize;
while ((sampleSize = mVideoExtractor.readSampleData(buffer, 0)) >= 0) {
info.offset = 0;
info.size = sampleSize;
info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
info.presentationTimeUs = mVideoExtractor.getSampleTime();
videoEndPreTimeUs = info.presentationTimeUs;
mediaMuxer.writeSampleData(videoTrackIndex, buffer, info);
mVideoExtractor.advance();}
}
Log.d(TAG, "视频 videoEndPreTimeUs" + videoEndPreTimeUs);
// 封装音频 track
if (-1 != audioTrackIndex) {MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
info.presentationTimeUs = 0;
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
int sampleSize;
while ((sampleSize = audioExtractor.readSampleData(buffer, 0)) >= 0 &&
audioExtractor.getSampleTime() <= videoEndPreTimeUs) {
info.offset = 0;
info.size = sampleSize;
info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
info.presentationTimeUs = audioExtractor.getSampleTime();
mediaMuxer.writeSampleData(audioTrackIndex, buffer, info);
audioExtractor.advance();}
}
mVideoExtractor.release(); // 开释 MediaExtractor
audioExtractor.release();
mediaMuxer.stop();
mediaMuxer.release(); // 开释 MediaMuxer}
Video: format:{csd-1=java.nio.ByteArrayBuffer[position=0,limit=9,capacity=9], mime=video/avc, frame-rate=30, height=1080, width=1920, max-input-size=1572864, isDMCMMExtractor=1, durationUs=12425577, csd-0=java.nio.ByteArrayBuffer[position=0,limit=20,capacity=20]}
Audio: format:{max-input-size=5532, aac-profile=2, mime=audio/mp4a-latm, durationUs=340101875, csd-0=java.nio.ByteArrayBuffer[position=0,limit=2,capacity=2], channel-count=2, sample-rate=44100}
MP3 转换为 AAC
应用 AndroidAudioConverter
AndroidAudioConverter – https://github.com/adrielcafe/AndroidAudioConverter
基于 FFmpeg 的第三方库。反对格局有 AAC, MP3, M4A, WMA, WAV 和 FLAC
应用办法:
app/build.gradle
repositories {
maven {url "https://jitpack.io"}
}
dependencies {compile 'com.github.adrielcafe:AndroidAudioConverter:0.0.8'}
申请读写内部存储权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
在 Application
类中加载库
public class MuxerApp extends Application {
@Override
public void onCreate() {super.onCreate();
AndroidAudioConverter.load(this, new ILoadCallback() {
@Override
public void onSuccess() {// Great!}
@Override
public void onFailure(Exception error) {// FFmpeg is not supported by device}
});
}
}
应用转换性能
final String sourceMP3Path = SOURCE_PATH + File.separator + "music1.mp3";
Log.d(TAG, "转换开始" + sourceMP3Path);
File srcFile = new File(sourceMP3Path);
IConvertCallback callback = new IConvertCallback() {
@Override
public void onSuccess(File convertedFile) {Log.d(TAG, "onSuccess:" + convertedFile);
}
@Override
public void onFailure(Exception error) {Log.e(TAG, "onFailure:", error);
}
};
AndroidAudioConverter.with(getApplicationContext())
// Your current audio file
.setFile(srcFile)
// Your desired audio format
.setFormat(AudioFormat.AAC)
// An callback to know when conversion is finished
.setCallback(callback)
// Start conversion
.convert();
在三星 Note4 上测试,转换 13MB 的 MP3 文件用了大概 3 分 18 秒。
【Android 音视频开发系列教程】