接到过这样的一个需求:给你一个视频(mp4)和一段音乐,合成一个新的视频,新的视频去掉原有的音频,而是用该音乐作为音频。看似简单的几句话,但接到这个需求的时候,真的非常头疼,其实这个真不简单。关于音视频的编辑,业界普遍使用的是FFmpeg库,但是如果要自己去编译优化得到一个稳定可使用FFmpeg库,需要花费巨大的人力和时间成本,这在我当时的情境下,并不现实。在网上查找资源时候,了解到,自从android 16之后,谷歌开始引入了音视频编解码库,并在android 18之后进行了完善,虽然稳定性上还是不足,但是在几乎每个版本更新后,都会对音视频库做一些改良,所以我决定android 的这种音视频库开始尝试。
啰嗦多了,开始转入正题。阅读本博文之前需要对android的多媒体编解码库有一定的了解,下面是几个必须要先去了解的关键类。这几个类的链接我都是推荐的谷歌的官方文档,不建议各位朋友去网上搜这些相关的类的所谓详解,因为绝大部分杂乱无章,错漏百出,完全就是误人误事,阅读官方文档和源码才是最好的方法。
MediaExtractor, 用于提取音视频数据,获取音视频文件的基本信息。
Mediacodec, 是最核心的编解码库,通过正确的配置就能够对音频和视频进行编码或者解码。
MediaMuxer,是将音视频合成器,通过MediaMuxer,将音频数据和视频数据分别写入同一个文件中的音频轨道和视频轨道,这样就生成了一个有声的视频文件。
这三个类是android音视频编解码最基本的类。一个音视频编解码的基本流程如图:
下面我们实现音视频混合需求:
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;
import com.example.administrator.recording.common.utils.MediaUtils;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* 音视频合成器
*/
public class Compounder {
private static int MP3_TYPE = 0;
private static int AAC_TYPE = 1;
private static int M4A_TYPE = 2;
private static int NOT_DC_EC_TYPE = 3;//不用重新编解码
private static int NOT_SUPPORT_TYPE = 4;//不支持类型
private String TAG = "mylog";
private int MAX_INPUT_SIZE = 10240;
private int VIDEO_READ_SAMPLE_SIZE = 524288;//512 * 1024
private int BIT_RATE = 65536; // 64 * 1024
private int SAMPLE_RATE = 44100;// acc sample rate
private Long TIMEOUT_US = 1000L;
private String mAudioPath = "/storage/emulated/0/010/aa.mp3";
private String mVideoPath = "/storage/emulated/0/010/aa.mp4";
private String mDstFilePath = "/storage/emulated/0/010/compound.mp4";
private MediaExtractor mVideoExtractor = new MediaExtractor();
private MediaExtractor mAudioExtractor = new MediaExtractor();
private MediaMuxer mMediaMuxer = null;
private int mAudioTrackIndex = -1;
private int mVideoTrackIndex = -1;
private int mAudioDEType = -1;
private long mMaxTimeStamp = 0;
private MediaFormat mVideoFormat;
private MediaFormat mAudioFormat;
private MediaCodec mDecoder;
private MediaCodec mEncoder;
private ByteBuffer[] mDecodeInputBuffer;
private ByteBuffer[] mDecodeOutputBuffer;
private ByteBuffer[] mEncodeInputBuffer;
private ByteBuffer[] mEncodeOutputBuffer;
/**
*
* @param videoPath 源视频路径
* @param audioPath 音频路径
* @param audioDEType 音频类型
* @param dstPath 目标文件路径
*/
private Compounder(String videoPath, String audioPath, int audioDEType, String dstPath){
mVideoPath = videoPath;
mAudioPath = audioPath;
mAudioDEType = audioDEType;
mDstFilePath = dstPath;
}
/**
*
* @param videoPath 源视频路径
* @param audioPath 音频路径
* @param dstPath 目标文件路径
* @return null || compounder
*/
public static Compounder createCompounder(String videoPath, String audioPath, String dstPath){
if(checkVideo(videoPath)){
int audioEDType = checkAudio(audioPath);
if(audioEDType != NOT_SUPPORT_TYPE)
return new Compounder(videoPath, audioPath, audioEDType, dstPath);
}
return null;
}
/**
* 检查音频是否合法
* @param audioPath
* @return
*/
private static int checkAudio(String audioPath) {
if(audioPath != null){
File file = new File(audioPath);
if(file.exists() && file.isFile()){
if(audioPath.endsWith(".mp3")){
return MP3_TYPE;
}
if(audioPath.endsWith(".aac")){
return AAC_TYPE;
}
if(audioPath.endsWith(".m4a")){
return M4A_TYPE;
}
}
}
return -1;
}
/**
* 检查视频是否符合要求
* @param videoPath
* @return
*/
private static boolean checkVideo(String videoPath) {
if(videoPath != null && videoPath.endsWith(".mp4")){
File file = new File(videoPath);
if(file.exists() && file.isFile()){
return true;
}
}
return false;
}
/**
* 开始合成
*/
public void start(){