接到过这样的一个需求:给你一个视频(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(){
if(mAudioDEType == MP3_TYPE) {
initDecodeAudio();
initEncodeAudio();
if (initMuxer()) {
if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
decodeInputBuffer();//解码--->编码--->合成输出文件
}
}
}else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
if(initMuxer()){
handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
}
}
release();
Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
}
/**
* 初始化混合器
*/
private boolean initMuxer() {
try {
if((mVideoFormat = MediaUtils.getMediaFormat(mVideoExtractor, MediaUtils.VIDEO_TYPE, mVideoPath)) != null) {
mMediaMuxer = new MediaMuxer(mDstFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
mVideoTrackIndex = mMediaMuxer.addTrack(mVideoFormat);//设置视频格式
Log.e(TAG, " video long is : " + mVideoFormat.getLong(MediaFormat.KEY_DURATION));
mMaxTimeStamp = mVideoFormat.getLong(MediaFormat.KEY_DURATION);
if(mAudioDEType == NOT_DC_EC_TYPE){
MediaFormat audioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
if(audioFormat != null) {
mAudioTrackIndex = mMediaMuxer.addTrack(audioFormat);
}else{
return false;
}
}
else if(mAudioDEType == MP3_TYPE) {
if(mDecoder == null || mEncoder == null){
return false;
}
mAudioTrackIndex = mMediaMuxer.addTrack(mEncoder.getOutputFormat());//设置目标的音频格式
}
mMediaMuxer.start();
return true;
}
} catch (IOException e) {
e.printStackTrace();
mMediaMuxer = null;
}
return false;
}
/**
* 获取输出的解码后的buffer
*/
private void decodeOutputBuffer() {
BufferInfo info = new BufferInfo();
int outputIndex = mDecoder.dequeueOutputBuffer(info, -1);
if (outputIndex >= 0) {
byte[] chunk = new byte[info.size];
mDecodeOutputBuffer[outputIndex].position(info.offset);
mDecodeOutputBuffer[outputIndex].limit(info.offset + info.size);
mDecodeOutputBuffer[outputIndex].get(chunk);
mDecodeOutputBuffer[outputIndex].clear();
mDecoder.releaseOutputBuffer(outputIndex, false);
if (chunk.length > 0) {
encodData(chunk, info.presentationTimeUs);
}
}
}
/**
* 编码PCM数据
* @param input pcm数据
* @param presentationTimeUs 时间戳
*/
private synchronized void encodData(byte[] input, long presentationTimeUs) {
int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = mEncodeInputBuffer[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mEncoder.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0);
}
BufferInfo bufferInfo = new BufferInfo();
int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
int outBitsSize = bufferInfo.size;
ByteBuffer outputBuffer = mEncodeOutputBuffer[outputBufferIndex];
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + outBitsSize);
outputBuffer.position(bufferInfo.offset);
mMediaMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo);
mEncoder.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
}
}
/**
* 输入要解码的buffer
* @return 输入是否成功
*/
private void decodeInputBuffer() {
while (true) {
int inputBufIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
if (inputBufIndex >= 0) {
mDecodeInputBuffer[inputBufIndex].clear();
long presentationTimeUs = mAudioExtractor.getSampleTime();
int sampleSize = mAudioExtractor.readSampleData(mDecodeInputBuffer[inputBufIndex], 0);
if (sampleSize > 0) {
if (presentationTimeUs > mMaxTimeStamp) {
return;//超过最大时间戳的音频不处理
}
mDecoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, 0);
mAudioExtractor.advance();
decodeOutputBuffer();
}
}
}
}
/**
* 初始化编码器
* @return 初始化是否成功
*/
private boolean initEncodeAudio() {
try {
//设置目标音频格式
mEncoder = MediaCodec.createByCodecName("OMX.google.aac.encoder");
MediaFormat mediaFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", SAMPLE_RATE, 2);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_INPUT_SIZE);
mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mEncoder.start();
mEncodeInputBuffer = mEncoder.getInputBuffers();
mEncodeOutputBuffer = mEncoder.getOutputBuffers();
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "initEncodeAudio---" + e.getMessage());
}
return false;
}
/**
* 初始化音频解码器
*/
private boolean initDecodeAudio() {
try {
mAudioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
String mime = mAudioFormat.getString(MediaFormat.KEY_MIME);
Log.e(TAG, "设置decoder的音频格式是:" + mime);
mDecoder = MediaCodec.createDecoderByType(mime);
mDecoder.configure(mAudioFormat, null, null, 0);
mDecoder.start();
mDecodeInputBuffer = mDecoder.getInputBuffers();
mDecodeOutputBuffer = mDecoder.getOutputBuffers();
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "initDecodeAudio---" + e.getMessage());
}
return false;
}
/**
* 写入提取的数据到目标文件
* @param mediaMuxer
* @param trackIndex
* @param extractor
*/
private boolean handleTrack(MediaMuxer mediaMuxer, int trackIndex, MediaExtractor extractor) {
if(mediaMuxer == null || trackIndex < 0 || extractor == null){
return false;
}
ByteBuffer inputBuffer = ByteBuffer.allocate(VIDEO_READ_SAMPLE_SIZE);
BufferInfo info = new BufferInfo();
int sampleSize;
while ((sampleSize = extractor.readSampleData(inputBuffer, 0)) > 0){
info.offset = 0;
info.size = sampleSize;
info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
info.presentationTimeUs = extractor.getSampleTime();
if(mMaxTimeStamp < info.presentationTimeUs){
break;
}
extractor.advance();
mediaMuxer.writeSampleData(trackIndex, inputBuffer, info);
}
return true;
}
/**
* 释放资源
*/
private void release() {
try {
if(mEncoder != null) {
mEncoder.stop();
mEncoder.release();
}
if(mMediaMuxer != null) {
mMediaMuxer.stop();
mMediaMuxer.release();
}
if(mDecoder != null) {
mDecoder.stop();
mDecoder.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码看上去比较多,为了更好的理解,下面我将详细解释具体步骤,首先是初始化混合器:通过统一的createCompounder创建混合器,创建混合器除了一些常规的文件检查之外,还有一个很重要的是音乐类型的检查,因为MediaMuxer是不能直接写入mp3音乐格式的数据,但是MediaMuxer支持aac和m4a格式的音乐直接写入合成文件。同时因为MediaMuxer对mp4视频数据也支持直接写入,所以对于视频数据不需要重新编解码所以需要在创建混合器的时候检查一下音乐类型。
/**
*
* @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;
}
创建好混合器Compounder之后,调用start函数就开始混合流程:
/**
* 开始合成
*/
public void start(){
if(mAudioDEType == MP3_TYPE) {
initDecodeAudio();
initEncodeAudio();
if (initMuxer()) {
if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
decodeInputBuffer();//解码--->编码--->合成输出文件
}
}
}else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
if(initMuxer()){
handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
}
}
release();
Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
}
为了更好的理解,下面是混合的流程图:
下面我将一一详述各个步骤。再次强调一遍哈!
当音频是m4a或者aac格式的时候,混合器可以直接把音频数据写入文件。当音频数据是mp3格式的时候,需要将音频数据编解码成m4a或者aac格式,才能进行音视频数据混合
先看音频为m4a和aac这条线:流程图如下:
/**
* 开始合成
*/
public void start(){
if(mAudioDEType == MP3_TYPE) {
initDecodeAudio();
initEncodeAudio();
if (initMuxer()) {
if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
decodeInputBuffer();//解码--->编码--->合成输出文件
}
}
}
//===========m4a/aac主线流程代码====================
else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
if(initMuxer()){
handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
}
}
release();
Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
}
//=====================================
1,初始化混合器
初始化混合器,首先要获取原视频文件的视频格式
/**
* 初始化混合器
*/
private boolean initMuxer() {
try {
//获取视频格式
mVideoFormat = MediaUtils.getMediaFormat(mVideoExtractor,
MediaUtils.VIDEO_TYPE, mVideoPath);
if(mVideoFormat != null) {
//设置mp4输出格式
mMediaMuxer = new MediaMuxer(mDstFilePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
//加入视频轨道,设置视频格式
mVideoTrackIndex = mMediaMuxer.addTrack(mVideoFormat);
mMaxTimeStamp = mVideoFormat.getLong(MediaFormat.KEY_DURATION);
if(mAudioDEType == NOT_DC_EC_TYPE){
//获取音频格式
MediaFormat audioFormat = MediaUtils.getMediaFormat(mAudioExtractor,
MediaUtils.AUDIO_TYPE, mAudioPath);
if(audioFormat != null) {
mAudioTrackIndex = mMediaMuxer.addTrack(audioFormat);
}else{
return false;
}
}
else if(mAudioDEType == MP3_TYPE) {
if(mDecoder == null || mEncoder == null){
return false;
}
//加入音频轨道,设置目标的音频格式
mAudioTrackIndex = mMediaMuxer.addTrack(mEncoder.getOutputFormat());
}
mMediaMuxer.start();
return true;
}
} catch (IOException e) {
e.printStackTrace();
mMediaMuxer = null;
}
return false;
}
这个步骤涉及到一个获取多媒体数据格式的函数,下面也贴出对应的代码:
/**
* 获取媒体编码格式
* @param extractor
* @param type
* @param mediaPath
* @return
*/
public static MediaFormat getMediaFormat(MediaExtractor extractor, String type, String mediaPath){
if(extractor == null || mediaPath == null || mediaPath.equals("")){
return null;
}
try {
MediaFormat format;
extractor.setDataSource(mediaPath);
for(int i = 0; i < extractor.getTrackCount(); i++){
format = extractor.getTrackFormat(i);
if(format.getString(MediaFormat.KEY_MIME).startsWith(type)){
extractor.selectTrack(i);
return format;
}
}
}catch (IOException e){
Log.e(TAG, e.getMessage());
}
return null;
}
通过MediaMuxer.addTrack来标明音频轨道和视频数据轨道。这个是非常重要的,因为只有加入了音视频对应的数据轨道,后面才能把对应的数据写入对应的轨道。
2,写入视频数据 && 3,写入音频数据
通过MediaExtractor提取MP4数据,因为MediaMuxer可以直接合成mp4数据,所以此处不用对mp4数据重新编解码,同样的MediaMuxer也支持直接合成m4a和aac数据,所以也不用重新编解码音频数据。此处为了避免代码重复,写了一个通用的handleTrack函数来写入指定的音视频数据,直接通过MediaExtractor提取数据,然后通过MediaMuxer写入数据。
/**
* 开始合成
*/
public void start(){
if(mAudioDEType == MP3_TYPE) {
initDecodeAudio();
initEncodeAudio();
if (initMuxer()) {
//写入视频数据
if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
decodeInputBuffer();//mp3音频:解码--->编码--->合成输出文件
}
}
}else if (mAudioDEType == NOT_DC_EC_TYPE){//音频为m4a/aac直接合成文件
if(initMuxer()){
//写入视频数据
handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
//写入音频数据
handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
}
}
release();
Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
}
/**
* 写入提取的数据到目标文件
* @param mediaMuxer 数据合成器
* @param trackIndex 数据轨道
* @param extractor 数据提取器
*/
private boolean handleTrack(MediaMuxer mediaMuxer, int trackIndex, MediaExtractor extractor) {
if(mediaMuxer == null || trackIndex < 0 || extractor == null){
return false;
}
ByteBuffer inputBuffer = ByteBuffer.allocate(VIDEO_READ_SAMPLE_SIZE);
BufferInfo info = new BufferInfo();
int sampleSize;
while ((sampleSize = extractor.readSampleData(inputBuffer, 0)) > 0){
info.offset = 0;
info.size = sampleSize;
info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
info.presentationTimeUs = extractor.getSampleTime();
if(mMaxTimeStamp < info.presentationTimeUs){
break;
}
extractor.advance();
mediaMuxer.writeSampleData(trackIndex, inputBuffer, info);
}
return true;
}
4,释放资源,合成结束
合成完成之后,务必要释放资源,否则容易引起内存泄漏,以及一系列崩溃
/**
* 释放资源
*/
private void release() {
try {
if(mEncoder != null) {
mEncoder.stop();
mEncoder.release();
}
if(mMediaMuxer != null) {
mMediaMuxer.stop();
mMediaMuxer.release();
}
if(mDecoder != null) {
mDecoder.stop();
mDecoder.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
说完m4a/aac这条线,接下来说的是mp3这条线:
MediaMuxer不支持直接合成mp3音频数据,所以需要先对mp3数据进行解码成pcm数据,然后编码成m4a或者aac数据,才能够写入合成文件。
/**
* 开始合成
*/
public void start(){
//========================Mp3主线代码流程======================
if(mAudioDEType == MP3_TYPE) {
initDecodeAudio();
initEncodeAudio();
if (initMuxer()) {
if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
decodeInputBuffer();//解码--->编码--->合成输出文件
}
}
}
//=========================================================
else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
if(initMuxer()){
handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
}
}
release();
Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
}
流程图如下:
1,初始化音频解码器
通过MediaExtractor获取音频数据格式,并用它来初始化解码器,初始化解码器后,需要获取对应的输入数据缓存和输出数据缓存
/**
* 初始化音频解码器
*/
private boolean initDecodeAudio() {
try {
mAudioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
String mime = mAudioFormat.getString(MediaFormat.KEY_MIME);
Log.e(TAG, "设置decoder的音频格式是:" + mime);
mDecoder = MediaCodec.createDecoderByType(mime);
mDecoder.configure(mAudioFormat, null, null, 0);
mDecoder.start();//记得需要start
//解码器输入数据缓存
mDecodeInputBuffer = mDecoder.getInputBuffers();
//解码器输出数据缓存
mDecodeOutputBuffer = mDecoder.getOutputBuffers();
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "initDecodeAudio---" + e.getMessage());
}
return false;
}
2,初始化音频解码器
指定一个m4a或者aac编码器,代码中使用的是OMX.google.aac.encoder,这个编码器绝大部分android手机都有,是google自带的编码器,所以属于软编码,但是音频数据编码速度很快,所以不用太在意软硬编码的问题(想编码视频数据朋友就得注意了,硬编码才是第一选择),同样的,初始化完后需要获取对应的编码器输入数据缓存和编码器输出数据缓存。
/**
* 初始化编码器
* @return 初始化是否成功
*/
private boolean initEncodeAudio() {
try {
//设置目标音频格式
mEncoder = MediaCodec.createByCodecName("OMX.google.aac.encoder");
MediaFormat mediaFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", SAMPLE_RATE, 2);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_INPUT_SIZE);
mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mEncoder.start();//记得需要start
//编码器数据输入缓存
mEncodeInputBuffer = mEncoder.getInputBuffers();
//编码器数据输出缓存
mEncodeOutputBuffer = mEncoder.getOutputBuffers();
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "initEncodeAudio---" + e.getMessage());
}
return false;
}
3,初始化混合器
和m4a/aac主线一样,请参考上面描述
4,写入视频数据
和m4a/aac主线一样,请参考上面描述
5,解码mp3数据为pcm && 6,编码mp3数据为m4a&&7,写入音频数据
解码–编码–写入音频数据,三个步骤是线性同步的,因为不可能把数据都缓存在内从中,那样音频稍微大一点,内存就爆了。解码完一定的数据,就要把解码好的数据输出,然后进行编码,接着把编码好的数据进行输出,最后把编码好的m4a数据写入mp4文件对应的音频轨道。下面是解码-编码-写入音频数据的流程图:
下面是具体的代码流程:
/**
* 输入要解码的buffer
* @return 输入是否成功
*/
private void decodeInputBuffer() {
while (true) {
int inputBufIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
if (inputBufIndex >= 0) {
mDecodeInputBuffer[inputBufIndex].clear();
long presentationTimeUs = mAudioExtractor.getSampleTime();
//读取数据到解码器输入缓存
int sampleSize =
mAudioExtractor.readSampleData(mDecodeInputBuffer[inputBufIndex], 0);
if (sampleSize > 0) {
if (presentationTimeUs > mMaxTimeStamp) {
return;//超过最大时间戳的音频不处理
}
//将对数据进行解码
mDecoder.queueInputBuffer(inputBufIndex, 0, sampleSize,
presentationTimeUs, 0);
mAudioExtractor.advance();
//输出解码后数据,然后进行编码输出------important
decodeOutputBuffer();
}
}
}
}
/**
* 获取输出的解码后的buffer
*/
private void decodeOutputBuffer() {
BufferInfo info = new BufferInfo();
//输出解码的pcm数据
int outputIndex = mDecoder.dequeueOutputBuffer(info, -1);
if (outputIndex >= 0) {
byte[] chunk = new byte[info.size];
mDecodeOutputBuffer[outputIndex].position(info.offset);
mDecodeOutputBuffer[outputIndex].limit(info.offset + info.size);
//将解码数据转为byte
mDecodeOutputBuffer[outputIndex].get(chunk);
mDecodeOutputBuffer[outputIndex].clear();
//必须要释放解码器缓存
mDecoder.releaseOutputBuffer(outputIndex, false);
if (chunk.length > 0) {
//将pcm数据提供给编码器进行编码
encodData(chunk, info.presentationTimeUs);
}
}
}
/**
* 编码PCM数据
* @param input pcm数据
* @param presentationTimeUs 时间戳
*/
private synchronized void encodData(byte[] input, long presentationTimeUs) {
int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = mEncodeInputBuffer[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
//将pcm数据输入编码器缓存
mEncoder.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0);
}
BufferInfo bufferInfo = new BufferInfo();
//编码器中输出m4a数据
int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
int outBitsSize = bufferInfo.size;
ByteBuffer outputBuffer = mEncodeOutputBuffer[outputBufferIndex];
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + outBitsSize);
outputBuffer.position(bufferInfo.offset);
//将编码器输出缓存写入到mp4文件---写入音频数据
mMediaMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo);
mEncoder.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
}
}
上面就是一个很完整的解码–编码流程,请各位朋友仔细看代码注释,这些地方都是关键步骤,通过这些步骤能够很清晰的明白代码逻辑。
8,结束合成,释放资源
释放资源时候,需要释放所有的编解码器,以及混合器。
/**
* 释放资源
*/
private void release() {
try {
if(mEncoder != null) {
mEncoder.stop();
mEncoder.release();
}
if(mMediaMuxer != null) {
mMediaMuxer.stop();
mMediaMuxer.release();
}
if(mDecoder != null) {
mDecoder.stop();
mDecoder.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
好了,上面就是音视频混合的所有流程,有疑问的朋友欢迎一起沟通交流,共同进步,发现代码有问题的朋友,也非常欢迎指出来,大家一起学习。