Android中音视频合成的几种方案详析
前言
最近工作中遇到了音视频处理的需求,Android下音视频合成,在当前调研方案中主要有三大类方法:MediaMux硬解码,mp4parser,FFmepg。三种方法均可实现,但是也有不同的局限和问题,先将实现和问题记录于此,便于之后的总结学习。下面话不多说了,来一起看看详细的介绍吧。
方法一(Fail)
利用MediaMux实现音视频的合成。
效果:可以实现音视频的合并,利用Android原生的VideoView和SurfaceView播放正常,大部分的播放器也播放正常,但是,但是,在上传Youtube就会出现问题:音频不连续,分析主要是上传Youtube时会被再次的压缩,可能在压缩的过程中出现音频的帧率出现问题。
分析:在MediaCodec.BufferInfo的处理中,时间戳presentationTimeUs出现问题,导致Youtube的压缩造成音频的紊乱。
publicstaticvoidmuxVideoAndAudio(StringvideoPath,StringaudioPath,StringmuxPath){ try{ MediaExtractorvideoExtractor=newMediaExtractor(); videoExtractor.setDataSource(videoPath); MediaFormatvideoFormat=null; intvideoTrackIndex=-1; intvideoTrackCount=videoExtractor.getTrackCount(); for(inti=0;i方法二(Success)
publicstaticvoidmuxVideoAudio(StringvideoFilePath,StringaudioFilePath,StringoutputFile){ try{ MediaExtractorvideoExtractor=newMediaExtractor(); videoExtractor.setDataSource(videoFilePath); MediaExtractoraudioExtractor=newMediaExtractor(); audioExtractor.setDataSource(audioFilePath); MediaMuxermuxer=newMediaMuxer(outputFile,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); videoExtractor.selectTrack(0); MediaFormatvideoFormat=videoExtractor.getTrackFormat(0); intvideoTrack=muxer.addTrack(videoFormat); audioExtractor.selectTrack(0); MediaFormataudioFormat=audioExtractor.getTrackFormat(0); intaudioTrack=muxer.addTrack(audioFormat); LogUtil.d(TAG,"VideoFormat"+videoFormat.toString()); LogUtil.d(TAG,"AudioFormat"+audioFormat.toString()); booleansawEOS=false; intframeCount=0; intoffset=100; intsampleSize=256*1024; ByteBuffervideoBuf=ByteBuffer.allocate(sampleSize); ByteBufferaudioBuf=ByteBuffer.allocate(sampleSize); MediaCodec.BufferInfovideoBufferInfo=newMediaCodec.BufferInfo(); MediaCodec.BufferInfoaudioBufferInfo=newMediaCodec.BufferInfo(); videoExtractor.seekTo(0,MediaExtractor.SEEK_TO_CLOSEST_SYNC); audioExtractor.seekTo(0,MediaExtractor.SEEK_TO_CLOSEST_SYNC); muxer.start(); while(!sawEOS){ videoBufferInfo.offset=offset; videoBufferInfo.size=videoExtractor.readSampleData(videoBuf,offset); if(videoBufferInfo.size<0||audioBufferInfo.size<0){ sawEOS=true; videoBufferInfo.size=0; }else{ videoBufferInfo.presentationTimeUs=videoExtractor.getSampleTime(); //noinspectionWrongConstant videoBufferInfo.flags=videoExtractor.getSampleFlags(); muxer.writeSampleData(videoTrack,videoBuf,videoBufferInfo); videoExtractor.advance(); frameCount++; } } booleansawEOS2=false; intframeCount2=0; while(!sawEOS2){ frameCount2++; audioBufferInfo.offset=offset; audioBufferInfo.size=audioExtractor.readSampleData(audioBuf,offset); if(videoBufferInfo.size<0||audioBufferInfo.size<0){ sawEOS2=true; audioBufferInfo.size=0; }else{ audioBufferInfo.presentationTimeUs=audioExtractor.getSampleTime(); //noinspectionWrongConstant audioBufferInfo.flags=audioExtractor.getSampleFlags(); muxer.writeSampleData(audioTrack,audioBuf,audioBufferInfo); audioExtractor.advance(); } } muxer.stop(); muxer.release(); LogUtil.d(TAG,"Output:"+outputFile); }catch(IOExceptione){ LogUtil.d(TAG,"MixerError1"+e.getMessage()); }catch(Exceptione){ LogUtil.d(TAG,"MixerError2"+e.getMessage()); } }方法三
利用mp4parser实现
mp4parser是一个视频处理的开源工具箱,由于mp4parser里的方法都依靠工具箱里的一些内容,所以需要将这些内容打包成jar包,放到自己的工程里,才能对mp4parser的方法进行调用。
compile“com.googlecode.mp4parser:isoparser:1.1.21”问题:上传Youtube压缩后,视频数据丢失严重,大部分就只剩下一秒钟的时长,相当于把视频变成图片了,囧
publicbooleanmux(StringvideoFile,StringaudioFile,finalStringoutputFile){ if(isStopMux){ returnfalse; } Movievideo; try{ video=MovieCreator.build(videoFile); }catch(RuntimeExceptione){ e.printStackTrace(); returnfalse; }catch(IOExceptione){ e.printStackTrace(); returnfalse; } Movieaudio; try{ audio=MovieCreator.build(audioFile); }catch(IOExceptione){ e.printStackTrace(); returnfalse; }catch(NullPointerExceptione){ e.printStackTrace(); returnfalse; } TrackaudioTrack=audio.getTracks().get(0); video.addTrack(audioTrack); Containerout=newDefaultMp4Builder().build(video); FileOutputStreamfos; try{ fos=newFileOutputStream(outputFile); }catch(FileNotFoundExceptione){ e.printStackTrace(); returnfalse; } BufferedWritableFileByteChannelbyteBufferByteChannel=new BufferedWritableFileByteChannel(fos); try{ out.writeContainer(byteBufferByteChannel); byteBufferByteChannel.close(); fos.close(); if(isStopMux){ returnfalse; } runOnUiThread(newRunnable(){ @Override publicvoidrun(){ mCustomeProgressDialog.setProgress(100); goShareActivity(outputFile); //FileUtils.insertMediaDB(AddAudiosActivity.this,outputFile);// } }); }catch(IOExceptione){ e.printStackTrace(); if(mCustomeProgressDialog.isShowing()){ mCustomeProgressDialog.dismiss(); } ToastUtil.showShort(getString(R.string.process_failed)); returnfalse; } returntrue; } privatestaticclassBufferedWritableFileByteChannelimplementsWritableByteChannel{ privatestaticfinalintBUFFER_CAPACITY=2000000; privatebooleanisOpen=true; privatefinalOutputStreamoutputStream; privatefinalByteBufferbyteBuffer; privatefinalbyte[]rawBuffer=newbyte[BUFFER_CAPACITY]; privateBufferedWritableFileByteChannel(OutputStreamoutputStream){ this.outputStream=outputStream; this.byteBuffer=ByteBuffer.wrap(rawBuffer); } @Override publicintwrite(ByteBufferinputBuffer)throwsIOException{ intinputBytes=inputBuffer.remaining(); if(inputBytes>byteBuffer.remaining()){ dumpToFile(); byteBuffer.clear(); if(inputBytes>byteBuffer.remaining()){ thrownewBufferOverflowException(); } } byteBuffer.put(inputBuffer); returninputBytes; } @Override publicbooleanisOpen(){ returnisOpen; } @Override publicvoidclose()throwsIOException{ dumpToFile(); isOpen=false; } privatevoiddumpToFile(){ try{ outputStream.write(rawBuffer,0,byteBuffer.position()); }catch(IOExceptione){ thrownewRuntimeException(e); } } }方法四
利用FFmpeg大法
FFmpeg由于其丰富的codec插件,详细的文档说明,并且与其调试复杂量大的编解码代码(是的,用MediaCodec实现起来十分啰嗦和繁琐)还是不如调试一行ffmpeg命令来的简单。
MergeVideo/Audioandretainbothaudios
可以实现,兼容性强,但由于是软解码,合并速度很慢,忍受不了,而相应的FFmpeg优化还不太了解,囧…….
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。