刚刚实现了音视频合成的需求,趁热打铁记录一下遇到的问题
需求描述:
将给定的音频及视频合成一个视频,如果音频时长小于视频,则重复播放;如果大于视频时长则截取到视频结束。
采用的是MediaMuxer、MediaCodec原生方案。
MediaCodec主要处理音频格式的问题,音频可能有mp3 m4a等一些格式,解析得到的音频编码有一个是安卓不支持的编码格式,如audio/mpeg,添加音轨时就会抛出异常。所以这里统一用MediaCodec解析成audio/aac格式,解析方法参考了网上的解析
/**** @param audioPath* @param audioStartTimeUs -1 表示不截取* @param audioEndTimeUs -1 表示不截取* @return*/public String audioToAAC(String audioPath,long audioStartTimeUs,long audioEndTimeUs){long a=System.currentTimeMillis();int audioExtractorTrackIndex=-1;int audioMuxerTrackIndex=-1;int channelCount=1;int sourceSampleRate = 0;String newAudioAAc = "";long sourceDuration=0;try {File tempFlie = new File(audioPath);String tempName = tempFlie.getName();String suffix = tempName.substring(tempName.lastIndexOf(".") + 1);
// newAudioAAc= tempFlie.getParentFile().getAbsolutePath() + "/" + tempName.replace(suffix, "aac");newAudioAAc= Objects.requireNonNull(tempFlie.getParentFile()).getAbsolutePath() + "/" + tempName.replace(suffix, "aac");
// muxer = new MediaMuxer(newAudioAAc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);Log.i(TAG,"audioPath=="+audioPath+",newAudioAAC=="+newAudioAAc+",newAudioAAc=="+newAudioAAc);//音频信息获取MediaExtractor audioExtractor = new MediaExtractor();audioExtractor.setDataSource(audioPath);int trackCount = audioExtractor.getTrackCount();MediaFormat sourceFormat=null;String sourceMimeType="";int timeOutUs=300;for (int i = 0; i < trackCount; i++) {sourceFormat = audioExtractor.getTrackFormat(i);sourceMimeType = sourceFormat.getString(MediaFormat.KEY_MIME);channelCount=sourceFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);sourceSampleRate = sourceFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);sourceDuration = sourceFormat.getLong(MediaFormat.KEY_DURATION);if (sourceMimeType.startsWith("audio/")) { //找到音轨Log.i(TAG,"sourceMimeType=="+sourceMimeType+",channelCount=="+channelCount+",sourceSampleRate=="+sourceSampleRate);audioExtractorTrackIndex = i;break;}}//初始化解码器MediaCodec audioDecoder = null;audioDecoder = MediaCodec.createDecoderByType(sourceMimeType);audioDecoder.configure(sourceFormat, null, null, 0);audioDecoder.start();//初始化编码MediaCodec mEncorder = null;mEncorder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100, channelCount);format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC) ;format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024*1024 * 10);mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);mEncorder.start();audioExtractor.selectTrack(audioExtractorTrackIndex);MediaCodec.BufferInfo sourceAudioBufferInfo = new MediaCodec.BufferInfo();MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();ByteBuffer audioByteBuffer = ByteBuffer.allocate(1024*1024*10);FileOutputStream mFileStream = new FileOutputStream(newAudioAAc);while (true) {int readSampleSize = audioExtractor.readSampleData(audioByteBuffer, 0);Log.i(TAG, "readSampleSize==" + readSampleSize+",audioByteBuffer.limit=="+audioByteBuffer.limit()+",timeOutUs=="+timeOutUs);if (readSampleSize < 0) {audioExtractor.unselectTrack(audioExtractorTrackIndex);break;}long audioSampleTime=audioExtractor.getSampleTime();//可以做进度回调Log.i(TAG, "audioSampleTime==" +audioSampleTime+",,progress=="+((float)audioSampleTime/(float)sourceDuration));if (audioStartTimeUs !=-1 && audioSampleTime < audioStartTimeUs) {audioExtractor.advance();continue;}if (audioEndTimeUs !=-1 && audioSampleTime > audioEndTimeUs) {break;}int audioSampleFlags=audioExtractor.getSampleFlags();//解码int sourceInputBufferIndex = audioDecoder.dequeueInputBuffer(timeOutUs);if (sourceInputBufferIndex >= 0) {ByteBuffer sourceInputBuffer = audioDecoder.getInputBuffer(sourceInputBufferIndex);sourceInputBuffer.clear();sourceInputBuffer.put(audioByteBuffer);audioDecoder.queueInputBuffer(sourceInputBufferIndex, 0, readSampleSize, audioSampleTime, audioSampleFlags);}int sourceOutputBufferIndex = audioDecoder.dequeueOutputBuffer(sourceAudioBufferInfo, timeOutUs);if (sourceOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// 后续输出格式变化}while (sourceOutputBufferIndex >= 0) {ByteBuffer decoderOutputBuffer = audioDecoder.getOutputBuffer(sourceOutputBufferIndex);//编码int inputBufferIndex = mEncorder.dequeueInputBuffer(timeOutUs);if (inputBufferIndex >= 0) {ByteBuffer inputBuffer = mEncorder.getInputBuffer(inputBufferIndex);inputBuffer.clear();inputBuffer.put(decoderOutputBuffer);mEncorder.queueInputBuffer(inputBufferIndex, 0, decoderOutputBuffer.limit(), audioSampleTime, audioSampleFlags);}int outputBufferIndex = mEncorder.dequeueOutputBuffer(audioBufferInfo, timeOutUs);if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// 后续输出格式变化}while (outputBufferIndex >= 0) {ByteBuffer outputBuffer = mEncorder.getOutputBuffer(outputBufferIndex);int outBufferSize = outputBuffer.limit() + 7;byte[] aacBytes = new byte[outBufferSize];addADTStoPacket(aacBytes, outBufferSize, channelCount);outputBuffer.get(aacBytes, 7, outputBuffer.limit());mFileStream.write(aacBytes);mEncorder.releaseOutputBuffer(outputBufferIndex, false);outputBufferIndex = mEncorder.dequeueOutputBuffer(audioBufferInfo, timeOutUs);}audioDecoder.releaseOutputBuffer(sourceOutputBufferIndex, false);sourceOutputBufferIndex = audioDecoder.dequeueOutputBuffer(sourceAudioBufferInfo, timeOutUs);}audioExtractor.advance();}//释放资源mEncorder.stop();mFileStream.flush();mFileStream.close();long b=System.currentTimeMillis()-a;Log.i(TAG,"编码结束=="+b);} catch (Exception e) {e.printStackTrace();}return newAudioAAc;}
得到aac编码的音频数据后,我们就可以将音视频合入到同一段视频中
@SuppressLint("WrongConstant")public void mix() {MediaExtractor videoExtractor = null;MediaExtractor audioExtractor = null;MediaMuxer mixMediaMuxer = null;try {videoExtractor = new MediaExtractor();videoExtractor.setDataSource(outputVideoFilePath);int videoIndex = -1;MediaFormat videoTrackFormat = null;int trackCount = videoExtractor.getTrackCount();for (int i = 0; i < trackCount; i++) {videoTrackFormat = videoExtractor.getTrackFormat(i);if (Objects.requireNonNull(videoTrackFormat.getString(MediaFormat.KEY_MIME)).startsWith("video/")) {videoIndex = i;}}audioExtractor = new MediaExtractor();audioExtractor.setDataSource(outputAudioFilePath);int audioIndex = -1;MediaFormat audioTrackFormat = null;trackCount = audioExtractor.getTrackCount();for (int i = 0; i < trackCount; i++) {audioTrackFormat = audioExtractor.getTrackFormat(i);if (Objects.requireNonNull(audioTrackFormat.getString(MediaFormat.KEY_MIME)).startsWith("audio/")) {audioIndex = i;}}videoExtractor.selectTrack(videoIndex);audioExtractor.selectTrack(audioIndex);// 初始化视频和音频缓冲区信息MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();// 初始化MediaMuxer,用于合成输出文件mixMediaMuxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);assert videoTrackFormat != null;int videoTrackIndex = mixMediaMuxer.addTrack(videoTrackFormat);assert audioTrackFormat != null;int audioTrackIndex = mixMediaMuxer.addTrack(audioTrackFormat);mixMediaMuxer.start();ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);long audioTime = 0L;long cycleTimes = 0;videoExtractor.unselectTrack(videoIndex);videoExtractor.selectTrack(videoIndex);while (true) {int data = videoExtractor.readSampleData(byteBuffer, 0);if (data < 0) {break;}videoBufferInfo.size = data;videoBufferInfo.presentationTimeUs = videoExtractor.getSampleTime();videoBufferInfo.offset = 0;videoBufferInfo.flags = videoExtractor.getSampleFlags();mixMediaMuxer.writeSampleData(videoTrackIndex, byteBuffer, videoBufferInfo);videoExtractor.advance();}while (true) {int data = audioExtractor.readSampleData(byteBuffer, 0);if (data < 0) {if (audioBufferInfo.presentationTimeUs < videoBufferInfo.presentationTimeUs) {if (cycleTimes == 0) {audioTime = audioBufferInfo.presentationTimeUs;}cycleTimes ++;audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);data = audioExtractor.readSampleData(byteBuffer, 0);} else {break;}}audioBufferInfo.size = data;audioBufferInfo.presentationTimeUs = audioTime * cycleTimes + audioExtractor.getSampleTime();audioBufferInfo.offset = 0;audioBufferInfo.flags = audioExtractor.getSampleFlags();mixMediaMuxer.writeSampleData(audioTrackIndex, byteBuffer, audioBufferInfo);audioExtractor.advance();if (videoBufferInfo.presentationTimeUs <= audioBufferInfo.presentationTimeUs) {break;}}myCallBack.muxerResult(true);} catch (IOException e) {Log.i("dealBug", "mix exception " + e);e.printStackTrace();myCallBack.muxerResult(false);} finally {if (mixMediaMuxer != null) {mixMediaMuxer.stop();mixMediaMuxer.release();}videoExtractor.release();audioExtractor.release();}}
这里遇到的坑,原先在网上找了类似的audioTime和videoTime他们采用的策略是获取头两个关键帧取标准值,作为音视频的时间戳。但这样获取会存在不准的问题,advance之后,每次获取到的data值并不是固定的,所以录出的视频播放速度可能存在要么快或者慢的问题。
记录一下,防止下次忘记!