package com.musicplus.media; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; 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 com.musicplus.app.MainApplication; import com.musicplus.utils.DLog; /** * 视频混合音频 * @author Darcy */ public abstract class VideoMuxer { private static final String TAG = "VideoMuxer"; String mOutputVideo; private VideoMuxer(String outputVideo){ this.mOutputVideo = outputVideo; } public final static VideoMuxer createVideoMuxer(String outputVideo){ return new Mp4Muxer(outputVideo); } /** * mix raw audio into video * @param videoFile * @param rawAudioFile * @param includeAudioInVideo */ public abstract void mixRawAudio(File videoFile,File rawAudioFile,boolean includeAudioInVideo); /** * use android sdk MediaMuxer * @author Darcy * @version API >= 18 */ private static class Mp4Muxer extends VideoMuxer{ private final static String AUDIO_MIME = "audio/mp4a-latm"; private final static long audioBytesPerSample = 44100*16/8; private long rawAudioSize; public Mp4Muxer(String outputVideo) { super(outputVideo); } @Override public void mixRawAudio(File videoFile, File rawAudioFile,boolean includeAudioInVideo) { final String videoFilePath = videoFile.getAbsolutePath(); MediaMuxer videoMuxer = null; try { final String outputVideo = mOutputVideo; videoMuxer = new MediaMuxer(outputVideo,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); MediaFormat videoFormat = null; MediaExtractor videoExtractor = new MediaExtractor(); videoExtractor.setDataSource(videoFilePath); for (int i = 0; i < videoExtractor.getTrackCount(); i++) { MediaFormat format = videoExtractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("video/")) { videoExtractor.selectTrack(i); videoFormat = format; break; } } int videoTrackIndex = videoMuxer.addTrack(videoFormat); int audioTrackIndex = 0; //extract and decode audio FileInputStream fisExtractAudio = null; if(includeAudioInVideo){ AndroidAudioDecoder audioDecoder = new AndroidAudioDecoder(videoFilePath); String extractAudioFilePath = MainApplication.RECORD_AUDIO_PATH + "/" + System.currentTimeMillis(); audioDecoder.decodeToFile(extractAudioFilePath); File extractAudioFile = new File(extractAudioFilePath); fisExtractAudio = new FileInputStream(extractAudioFile); } FileInputStream fisMixAudio = new FileInputStream(rawAudioFile); boolean readExtractAudioEOS = includeAudioInVideo ? false : true; boolean readMixAudioEOS = false; byte[] extractAudioBuffer = new byte[4096]; byte[] mixAudioBuffer = new byte[4096]; int extractAudioReadCount = 0; int mixAudioReadCount = 0; final MultiAudioMixer audioMixer = MultiAudioMixer.createAudioMixer(); final byte[][] twoAudioBytes = new byte[2][]; final MediaCodec audioEncoder = createACCAudioDecoder(); audioEncoder.start(); ByteBuffer[] audioInputBuffers = audioEncoder.getInputBuffers(); ByteBuffer[] audioOutputBuffers = audioEncoder.getOutputBuffers(); boolean sawInputEOS = false; boolean sawOutputEOS = false; long audioTimeUs = 0 ; BufferInfo outBufferInfo = new BufferInfo(); int inputBufIndex, outputBufIndex; while(!sawOutputEOS){ if (!sawInputEOS) { inputBufIndex = audioEncoder.dequeueInputBuffer(10000); if (inputBufIndex >= 0) { ByteBuffer inputBuffer = audioInputBuffers[inputBufIndex]; inputBuffer.clear(); int bufferSize = inputBuffer.remaining(); if(bufferSize != extractAudioBuffer.length){ extractAudioBuffer = new byte[bufferSize]; mixAudioBuffer = new byte[bufferSize]; } if(!readExtractAudioEOS){ extractAudioReadCount = fisExtractAudio.read(extractAudioBuffer); if(extractAudioReadCount == -1){ readExtractAudioEOS = true; } } if(!readMixAudioEOS){ mixAudioReadCount = fisMixAudio.read(mixAudioBuffer); if(mixAudioReadCount == -1){ readMixAudioEOS = true; } } if(readExtractAudioEOS && readMixAudioEOS){ audioEncoder.queueInputBuffer(inputBufIndex,0 , 0 , 0 ,MediaCodec.BUFFER_FLAG_END_OF_STREAM); sawInputEOS = true; }else{ byte[] mixAudioBytes; if(!readExtractAudioEOS && !readMixAudioEOS){ if(extractAudioReadCount == mixAudioReadCount){ twoAudioBytes[0] = extractAudioBuffer; twoAudioBytes[1] = mixAudioBuffer; }else if(extractAudioReadCount > mixAudioReadCount){ twoAudioBytes[0] = extractAudioBuffer; Arrays.fill(mixAudioBuffer, mixAudioReadCount -1, bufferSize, (byte)0); }else{ Arrays.fill(extractAudioBuffer, extractAudioReadCount -1, bufferSize, (byte)0); } mixAudioBytes = audioMixer.mixRawAudioBytes(twoAudioBytes); if(mixAudioBytes == null){ DLog.e(TAG, "mix audio : null"); } inputBuffer.put(mixAudioBytes); rawAudioSize += mixAudioBytes.length; audioEncoder.queueInputBuffer(inputBufIndex, 0, mixAudioBytes.length, audioTimeUs, 0); }else if(!readExtractAudioEOS && readMixAudioEOS){ inputBuffer.put(extractAudioBuffer, 0, extractAudioReadCount); rawAudioSize += extractAudioReadCount; audioEncoder.queueInputBuffer(inputBufIndex, 0, extractAudioReadCount, audioTimeUs, 0); }else{ inputBuffer.put(mixAudioBuffer, 0, mixAudioReadCount); rawAudioSize += mixAudioReadCount; audioEncoder.queueInputBuffer(inputBufIndex, 0, mixAudioReadCount, audioTimeUs, 0); } audioTimeUs = (long) (1000000 * (rawAudioSize / 2.0) / audioBytesPerSample); } } } outputBufIndex = audioEncoder.dequeueOutputBuffer(outBufferInfo, 10000); if(outputBufIndex >= 0){ // Simply ignore codec config buffers. if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!= 0) { DLog.i(TAG, "audio encoder: codec config buffer"); audioEncoder.releaseOutputBuffer(outputBufIndex, false); continue; } if(outBufferInfo.size != 0){ ByteBuffer outBuffer = audioOutputBuffers[outputBufIndex]; outBuffer.position(outBufferInfo.offset); outBuffer.limit(outBufferInfo.offset + outBufferInfo.size); DLog.i(TAG, String.format(" writing audio sample : size=%s , presentationTimeUs=%s", outBufferInfo.size, outBufferInfo.presentationTimeUs)); if(lastAudioPresentationTimeUs < outBufferInfo.presentationTimeUs){ videoMuxer.writeSampleData(audioTrackIndex, outBuffer, outBufferInfo); lastAudioPresentationTimeUs = outBufferInfo.presentationTimeUs; }else{ DLog.e(TAG, "error sample! its presentationTimeUs should not lower than before."); } } audioEncoder.releaseOutputBuffer(outputBufIndex, false); if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { sawOutputEOS = true; } }else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { audioOutputBuffers = audioEncoder.getOutputBuffers(); } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat audioFormat = audioEncoder.getOutputFormat(); audioTrackIndex = videoMuxer.addTrack(audioFormat); videoMuxer.start(); //start muxer } } if(fisExtractAudio != null){ fisExtractAudio.close(); } fisMixAudio.close(); audioEncoder.stop(); audioEncoder.release(); //mix video boolean videoMuxDone = false; // 压缩帧大小 < 原始图片大小 int videoWidth = videoFormat.getInteger(MediaFormat.KEY_WIDTH); int videoHeight = videoFormat.getInteger(MediaFormat.KEY_HEIGHT); ByteBuffer videoSampleBuffer = ByteBuffer.allocateDirect(videoWidth * videoHeight); BufferInfo videoBufferInfo = new BufferInfo(); int sampleSize; while (!videoMuxDone) { videoSampleBuffer.clear(); sampleSize = videoExtractor.readSampleData(videoSampleBuffer, 0); if (sampleSize < 0) { videoMuxDone = true; } else { videoBufferInfo.presentationTimeUs = videoExtractor.getSampleTime(); videoBufferInfo.flags = videoExtractor.getSampleFlags(); videoBufferInfo.size = sampleSize; videoSampleBuffer.limit(sampleSize); videoMuxer.writeSampleData(videoTrackIndex, videoSampleBuffer,videoBufferInfo); videoExtractor.advance(); } } videoExtractor.release(); } catch (IOException e) { e.printStackTrace(); }finally{ if(videoMuxer != null){ videoMuxer.stop(); videoMuxer.release(); DLog.i(TAG, "video mix complete."); } } } private MediaCodec createACCAudioDecoder() throws IOException { MediaCodec codec = MediaCodec.createEncoderByType(AUDIO_MIME); MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, AUDIO_MIME); format.setInteger(MediaFormat.KEY_BIT_RATE, 128000); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); return codec; } private long lastAudioPresentationTimeUs = -1; } }