应用 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
  • 最初进行并开释MediaMuxerMediaExtractor

最好放在子线程中操作。

/** * 提取视频 * * @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.gradlerepositories { 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音视频开发系列教程】