这里主要实现一下多视频合成,主要困难是手机前置摄像头和后置摄像头录制的视频合成问题,我这里主要实现了功能,但是效率不优,暂时记录一下,如果有更好的方式再更新。

1.新建SelectRecordActivity类,并且打开AndroidManifest.xml修改为启动类(之前的启动类是MainActivity,现在只是作为一个单独的功能类),引用activity_select_record.xml布局文件,两个选择按钮。

点击单段视频录制实现第二按钮的功能,点击多段视频录制合成跳转到MultiRecordActivity。

2.新建MultiRecordActivity,直接引用MainActivity的布局activity_main.xml。因为布局都一样,主要区别在于修改切换摄像头的按钮逻辑以及停止录制的逻辑。

需要用到变量

   /*** 相机预览*/private SurfaceView mSurfaceView;/*** 开始录制按钮*/private ImageView mStartVideo;/*** 正在录制按钮,再次点击,停止录制*/private ImageView mStartVideoIng;/*** 录制时间*/private TextView mTime;/*** 录制进度条*/private ProgressBar mProgress;/*** 等待视频合成完成提示*/private ProgressBar mWait;/*** 录制主要工具类*/private MediaHelper mMediaHelper;/*** 录制进度值*/private int mProgressNumber=0;/*** 视频段文件编号*/private int mVideoNumber=1;private FileUtils mFileUtils;/*** 临时记录每段视频的参数内容*/private List<Mp4TsVideo> mTsVideo = new ArrayList<>();/*** mp4转ts流后的地址,主要合成的文件*/private List<String> mTsPath = new ArrayList<>();/*** 是否已经取消下一步,比如关闭了页面,就不再做线程处理,结束任务*/private boolean isCancel;/*** 权限相关*/private PermissionHelper mPermissionHelper;

初始化录制工具类以及文件类

mMediaHelper = new MediaHelper(this);mMediaHelper.setTargetDir(new File(mFileUtils.getMediaVideoPath()));//视频段从编号1开始mMediaHelper.setTargetName(mVideoNumber + ".mp4");mPermissionHelper = new PermissionHelper(this);//录制之前删除所有的多余文件mFileUtils = new FileUtils(this);mFileUtils.deleteFile(mFileUtils.getMediaVideoPath(),null);mFileUtils.deleteFile(mFileUtils.getStorageDirectory(),null);

其中用来记录视频段的Mp4TsVideo类

   /*** 记录下每段视频*/private class Mp4TsVideo{/*** 视频段的地址*/private String mp4Path;/*** ts地址*/private String tsPath;/*** 是否需要翻转*/private boolean flip;public String getMp4Path() {return mp4Path;}public void setMp4Path(String mp4Path) {this.mp4Path = mp4Path;}public String getTsPath() {return tsPath;}public void setTsPath(String tsPath) {this.tsPath = tsPath;}public boolean isFlip() {return flip;}public void setFlip(boolean flip) {this.flip = flip;}}

3.修改点击镜头切换的逻辑,在MainActivity中这个逻辑是直接停止录制,等待点击重新录制。本文这里,是切换摄像头成功后先保存当前录制的视频,然后再继续录制。

        case R.id.inversion:if(mMediaHelper.isRecording()){mMediaHelper.stopRecordSave();addMp4Video();mVideoNumber++;mMediaHelper.setTargetName(mVideoNumber+".mp4");mMediaHelper.autoChangeCamera();mMediaHelper.record();}else{mMediaHelper.autoChangeCamera();}break;

其中addMp4Video()方法就是记录保存当前录制的视频段

/*** 记录这个视频片段并且开始处理。*/private void addMp4Video(){Mp4TsVideo mp4TsVideo = new Mp4TsVideo();mp4TsVideo.setMp4Path(mMediaHelper.getTargetFilePath());mp4TsVideo.setTsPath(mFileUtils.getMediaVideoPath()+"/"+mVideoNumber+".ts");mp4TsVideo.setFlip(mMediaHelper.getPosition()== Camera.CameraInfo.CAMERA_FACING_FRONT);mTsVideo.add(mp4TsVideo);mp4ToTs();}

注意:之前就说过涉及到前置摄像头视频,所以需要翻转的功能来进行处理,翻转是需要重新编码,所以无法直接使用copy指令,所以转换ts的过程比较耗时,特别多段,为了保证体验的效率,所以拿到一段视频段就开始通过AsyncTask转换处理。

 /*** 如果发现是多个视频就异步开始合成,节省等待时间。* 通过递归的模式来处理视频合成。*/private void mp4ToTs(){if(isCancel){return;}if(mTsVideo.size()==0){if(mTsPath.size()>0 && !mMediaHelper.isRecording()){showProgressLoading();concatVideo(mTsPath);}return;}final Mp4TsVideo mp4TsVideo = mTsVideo.get(0);Mp4TsVideo mp4TsVideoIng = (Mp4TsVideo) mStartVideo.getTag();if(mp4TsVideo == mp4TsVideoIng){return;}mStartVideo.setTag(mp4TsVideo);FFmpegRun.execute(FFmpegCommands.mp4ToTs(mp4TsVideo.getMp4Path(), mp4TsVideo.getTsPath(),mp4TsVideo.isFlip()), new FFmpegRun.FFmpegRunListener() {@Overridepublic void onStart() {}@Overridepublic void onEnd(int result) {if(mTsVideo.size() == 0 || isCancel){return;}mTsPath.add(mp4TsVideo.getTsPath());mTsVideo.remove(mp4TsVideo);mp4ToTs();}});}

打开FFmpegCommands类新增mp4转ts的命令

 /*** mp4转ts* @param videoUrl* @param outPath* @param flip* @return*/public static String[] mp4ToTs(String videoUrl,String outPath,boolean flip){Log.w("SLog","videoUrl:" + videoUrl + "\noutPath:" + outPath);ArrayList<String> _commands = new ArrayList<>();_commands.add("ffmpeg");_commands.add("-i");_commands.add(videoUrl);if(flip){_commands.add("-vf");_commands.add("hflip");}_commands.add("-b");_commands.add(String.valueOf(2 * 1024 * 1024));_commands.add("-s");_commands.add("720x1280");_commands.add("-acodec");_commands.add("copy");
//        _commands.add("-vcodec");
//        _commands.add("copy");_commands.add(outPath);String[] commands = new String[_commands.size()];for (int i = 0; i < _commands.size(); i++) {commands[i] = _commands.get(i);}return commands;}

注意:如果是前置录制的视频,需要镜像翻转,否则合成的视频有一段是倒过来,这样的视频完全不能到达要求 ,主要判断逻辑

      if(flip){_commands.add("-vf");//hflip左右翻转,vflip上下翻转_commands.add("hflip");}

完整的视频是按顺序拼接的,我通过递归的方式,一段一段的提取mTsVideo中的视频段,直到视频全部由mp4转成ts流为止。

4.录制视频段的行为和处理视频段的行为是互不干扰的,直到点击停止录制按钮,如果满足时间要求(我这里设置最低录制8秒),就只需要等待所有视频段转换完成。
点击停止按钮:

     case R.id.start_video_ing:if(mProgressNumber == 0){stopView(false);break;}Log.e("SLog","mProgressNumber:"+mProgressNumber);if (mProgressNumber < 8){//时间太短不保存Toast.makeText(this,"请至少录制到红线位置",Toast.LENGTH_LONG).show();mMediaHelper.stopRecordUnSave();stopView(false);break;}//停止录制mMediaHelper.stopRecordSave();stopView(true);break;

stopView方法:

/*** 停止录制* @param isSave*/private void stopView(boolean isSave){int timer = mProgressNumber;mProgressNumber = 0;mProgress.setProgress(0);handler.removeMessages(0);mTime.setText("00:00");mTime.setTag(timer);if(isSave) {String videoPath = mFileUtils.getMediaVideoPath();final File file = new File(videoPath);if(!file.exists()){Toast.makeText(this,"文件已损坏或者被删除,请重试!",Toast.LENGTH_SHORT).show();return;}File[] files = file.listFiles();if(files.length==1){startMediaVideo(mMediaHelper.getTargetFilePath());}else{showProgressLoading();addMp4Video();}}else{mFileUtils.deleteFile(mFileUtils.getStorageDirectory(),null);mFileUtils.deleteFile(mFileUtils.getMediaVideoPath(),null);mVideoNumber=1;isCancel = true;}mStartVideoIng.setVisibility(View.GONE);mStartVideo.setVisibility(View.VISIBLE);}

判断文件夹内如果只有一段视频,不需要做任何转换处理,直接进入下一步,这里和单段视频录制原理一样,如果是多段视频需要把最后一段视频也添加到待处理的集合中,等待递归处理完成。

处理完视频段后,得到所有视频段的ts文件,进入合成的方法concatVideo()

/*** ts合成视频* @param filePaths*/private void concatVideo(List<String> filePaths){StringBuilder ts = new StringBuilder();for (String s:filePaths) {ts.append(s).append("|");}String tsVideo = ts.substring(0,ts.length()-1);final String videoPath = mFileUtils.getStorageDirectory()+"/video_ts.mp4";FFmpegRun.execute(FFmpegCommands.concatTsVideo(tsVideo, videoPath), new FFmpegRun.FFmpegRunListener() {@Overridepublic void onStart() {Log.e("SLog","concatTsVideo start...");}@Overridepublic void onEnd(int result) {Log.e("SLog","concatTsVideo end...");dismissProgress();startMediaVideo(videoPath);}});}

打开FFmpegCommands类新增ts合成mp4的命令

/*** ts拼接视频*/public static String[] concatTsVideo(String _filePath, String _outPath) {//-f concat -i list.txt -c copy concat.mp4Log.w("SLog","_filePath:" + _filePath + "\n_outPath:" + _outPath);ArrayList<String> _commands = new ArrayList<>();_commands.add("ffmpeg");_commands.add("-i");_commands.add("concat:"+_filePath);_commands.add("-b");_commands.add(String.valueOf(2 * 1024 * 1024));_commands.add("-s");_commands.add("720x1280");_commands.add("-acodec");_commands.add("copy");_commands.add("-vcodec");_commands.add("copy");_commands.add(_outPath);String[] commands = new String[_commands.size()];for (int i = 0; i < _commands.size(); i++) {commands[i] = _commands.get(i);}return commands;}

因为之前mp4转ts的时候参数处理都一致,所以这里的ts流合成可以直接用copy指令直接复制音频和视频源,几乎秒合成。
合并完成后进入制作页面:

 /*** 进入下一步制作页面* @param path*/private void startMediaVideo(String path){int timer = (int) mTime.getTag();Log.d("SLog","video path:"+path);Intent intent = new Intent(this,MakeVideoActivity.class);intent.putExtra("path",path);intent.putExtra("time",timer);startActivity(intent);}

视频合成的功能是达到了,但是效率并不是最佳,特别在硬件差的手机上更是不敢恭维,我实现的途中尝试了很多办法,包括监听Camera的源数据处理,效果都不太好,所以如果哪位大神有更好的思路和方式。

最后我在提供一下其他我认为效率最佳的合成命令,也是官网查阅的。

/*** txt文件拼接视频*/public static String[] concatPathVideo(String _filePath, String _outPath) {//-f concat -i list.txt -c copy concat.mp4if (SLog.debug) SLog.w("_filePath:" + _filePath + "\n_outPath:" + _outPath);ArrayList<String> _commands = new ArrayList<>();_commands.add("ffmpeg");_commands.add("-f");_commands.add("concat");_commands.add("-safe");_commands.add("0");_commands.add("-i");_commands.add(_filePath);_commands.add("-c");_commands.add("copy");_commands.add(_outPath);String[] commands = new String[_commands.size()];for (int i = 0; i < _commands.size(); i++) {commands[i] = _commands.get(i);}return commands;}

这里需要传入一个文件路径,这个文件的内容就是你合成视频的地址,多个视频换行区分,效率极高,但是有限制,比如帧率等参数一致才行(比如我都是用后置摄像头录制的视频段),否则合成的视频有问题或者无法播放。

simple.txt
file 'input1.mp4'
file 'input2.mp4'
file 'input3.mp4'

原创作者:Galaxy北爱
原文链接:https://www.jianshu.com/p/6c51b11550be
校验:逆流的鱼yuiop

FFmpeg实现多段小视频合成相关推荐

  1. Java实现多段小视频合成一个视频

    Java实现多段小视频合成一个视频 import java.io.*; import java.util.*; import java.util.stream.Collectors;public cl ...

  2. ffmeg将多段视频合成一个视频

    ffmeg将多段视频合成一个视频 文章目录: 一.方法一:利用文件列表 二.方法二:不利用文件列表 三. 方法三:拼接不同编码格式的文件 四.注意事项 要处理多段视频太费劲啦,如果直接把多段小视频合成 ...

  3. android端采用FFmpeg进行音视频合成与分离

    上一篇文章谈到音频剪切.混音.拼接与转码,也详细介绍cMake配置与涉及FFmpeg文件的导入: android端采用FFmpeg进行音频混合与拼接剪切 .现在接着探讨音视频的合成与分离. 1.音频提 ...

  4. Mac High Sierra + pyCharm + py3.5 + Moviepy 工厂式视频合成测试

    近日接触手机端小视频合成业务,发现现有的主流视频合成APP中,能实现"单张图片 + 指定语音"类型的APP屈指可数,ios自带APP"Clips"仅能合成正方形 ...

  5. 微信朋友圈如何发已存的小视频

    我们知道,微信发朋友圈的时候,只能即录即发,不能够发已经存好的视频,或者转发别人的视频.下面以转发别人的小视频为例,说说如何发送已经存好的小视频.有时候,看到别人发的直升机,钞票啊等等小视频,感觉很拉 ...

  6. 微信朋友圈小视频显示服务器超时,微信小视频转发到朋友圈却提示发送失败怎么办?...

    相信很多朋友都有这样的困扰:微信小视频发送到朋友圈后,却提示发布失败,这是为什么呢?微信小视频发布失败有什么解决方法?那么,今天宿迁波仔就和大家分享一下正确发送小视频到朋友圈的方法. 自从微信更新6. ...

  7. 【DIY】FFmpeg Joiner – 多段视频「无损合并」小工具 [Windows]

    http://subscribe.mail.10086.cn/subscribe/readAll.do?columnId=280&itemId=6082779 FFmpeg Joiner – ...

  8. python ffmpeg 视频转图片 视频转音频 播放音频 多张图片+音频转视频 多个视频合成一个视频 改变视频播放速度

    文章目录 视频转图片 视频转音频 播放音频 图片+音频 转 视频 多个视频合成一个视频 改变视频播放速度 视频转图片 #!/usr/bin/env python # -*- encoding: utf ...

  9. 如何下载多段ts视频 m3u8 ffmpeg

    如何下载多段ts视频 m3u8 ffmpeg ffmpeg使用教程 下载 Windows下载方法 step1. 官网下载地址 点此进入下载地址 step2. 选择 Windows EXE files ...

  10. 没有内涵段子可以刷了,利用Python爬取段友之家贴吧图片和小视频(含源码)

    由于最新的视频整顿风波,内涵段子APP被迫关闭,广大段友无家可归,但是最近发现了一个"段友"的app,版本更新也挺快,正在号召广大段友回家,如下图,有兴趣的可以下载看看(ps:我不 ...

最新文章

  1. Codeforces Round #Pi (Div. 2)(A,B,C,D)
  2. 在android开发中使用multdex的方法-IT蓝豹为你整理
  3. Oracle RMAN 表空间恢复
  4. 自己做的一个登录页面,纯代码!
  5. Linux系统中read的用法,Linux中read命令的用法
  6. 连接linux工具有哪些_推荐 10 个不错的网络监视工具,值得收藏
  7. 小米路由器4 bootloader_无线路由器刷机中,Breed详细使用教程,一些注意事项
  8. 【CSAPP笔记】11. 存储器层次结构
  9. Hibernate会话工厂
  10. Poj 1324 Holedox Moving 状压判重+BFS
  11. linux、docker容器缺少tailf命令,解决方案。
  12. android mtklog,Mtklog结构及分析
  13. C语言高效编程的四大秘技之以空间换时间
  14. 20192132055 李欣桐第二次作业
  15. 洋码头API接口:item_search - 根据关键词取商品列表
  16. echarts is not defined解决方案
  17. 虚拟直播降低线上直播成本,虚拟直播如何助力企业抓住新风口?
  18. ps之通道、计算、应用图像及混合模式讲解
  19. 织梦采集,织梦采集工具,织梦cms免费采集
  20. Latex 给一段文字加边框

热门文章

  1. 论文笔记_S2D.46_2013-3DV_基于点融合的动态场景实时三维重建
  2. 深度学习笔记_卷积神经网络基本概念
  3. 视觉SLAM十四讲_3-李群和李代数
  4. 编译原理文法等价变换
  5. 招聘|华为2012部门招算法工程师
  6. XGBoost和GBDT的区别与联系
  7. Luogu5280 [ZJOI2019] 线段树 【线段树】
  8. 作业二在校大学生零食消费调查问卷
  9. 上下文路径request.getContextPath();与${pageContext.request.contextPath}
  10. Oracle中的COALESCE,NVL,NVL2,NULLIF函数