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优化还不太了解,囧…….
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。