好久没写博客了,最近的事情的比较多。公司正在向产品这块转型,要做音视频的编辑开发,之前的接触这块的东西并不多,所以开发起来有很多的困难,从踩自定义相机的坑开始,视频的录制,编辑(主要包括合成和裁剪);音频的录制,裁剪;图片的一些基本处理,包括裁剪,旋转,添加文字,水印等等。哇,真的很麻烦!更令人闹心的是,之前和我合作的,主要开发视频这块的功能的同事,顶不住压力,拉稀了,不干了!那。。。视频这块的开发只能又落到我的头上了!

废话就扯到这里,进入正题。

视频的合成裁剪,一般都用的FFmpeg,这块的c代码我没研究过,但是呢,为了开发进度能够顺利的进行,我是不可能自己进行编译一遍FFmpeg的,于是就从万能的gitHub上找了一份编译好的来用(其实我心里的是鄙视我自己的!)。下面开始撸代码!

1.视频的采集功能!

怎么说呢,视频的采集看似简单,其实比较麻烦!从自定义相机开始出发,当然,我没自己从头开始写,用的是之前那个孩子的代码!,改了几天,发现各种各样的问题,采集视频的播放方向,不同相机的不同摄像头的分辨率设置,以及采集完成后,编辑后的播放方向问题等等。加上定时采集,回删操作,拍摄过程中的标记添加等等!总之呢,大坑到没有,小坑不断!之后的合成更是慢的一批,产品经理用了一下,否了!那。。很尴尬!视频的功能做不出来,那就没用!但是国内功能稍微好点的视频编辑都收费,领导在github上看到某云的sdk编辑是免费的,但是他们的云是收费的,感觉能搞,于是让我先。。。。。加上!(欲哭无泪)先加上。。。。结果周五过来一谈,嗯,必须用他们的云,其他的免费!我忍着不笑!可怜我刚刚加上采集。采集确实好用,并且不收费!
下面代码:

布局文件代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" android:id="@+id/rl_root"><android.opengl.GLSurfaceView
            android:id="@+id/camera_preview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_alignParentBottom="true"android:layout_alignParentTop="true"/><com.tian.videomergedemo.view.CameraHintView
            android:id="@+id/camera_hint"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_alignParentBottom="true"android:layout_alignParentTop="true" /><RelativeLayout
        android:id="@+id/bottom_mask"android:layout_width="fill_parent"android:layout_height="120.0dip"android:layout_alignParentBottom="true"android:layout_gravity="bottom" ><ImageView
            android:id="@+id/iv_record"android:layout_width="75dp"android:layout_height="75dp"android:layout_centerHorizontal="true"android:layout_marginTop="30dp"android:src="@drawable/record_state" /><ImageView
            android:id="@+id/iv_point_maker1"android:layout_width="40dp"android:layout_height="40dp"android:layout_centerVertical="true"android:layout_marginRight="30dp"android:layout_toLeftOf="@id/iv_record"android:src="@drawable/record_maker_d"/><ImageView
            android:id="@+id/iv_stop"android:layout_width="40dp"android:layout_height="40dp"android:layout_centerVertical="true"android:layout_marginLeft="30dp"android:layout_toRightOf="@id/iv_record"android:src="@drawable/record_stop_ok" /></RelativeLayout><RelativeLayout android:id="@+id/rl_progress_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentTop="true"><com.tian.videomergedemo.view.RecordProgressView
       android:id="@+id/record_progress"android:layout_width="match_parent"android:layout_height="13dp"/>
</RelativeLayout><RelativeLayout
        android:id="@+id/rl"android:layout_width="match_parent"android:layout_height="50dp"android:layout_below="@id/rl_progress_bar" ><Chronometer
        android:id="@+id/tv_record_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:drawableLeft="@drawable/red_dots"android:drawablePadding="5dp"android:format="%s"android:gravity="center"android:textColor="@color/red"android:textSize="19sp"/><TextView
            android:id="@+id/tv_record_time1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:drawableLeft="@drawable/red_dots"android:drawablePadding="5dp"android:text="00:00"android:visibility="gone"android:textColor="@android:color/white"android:textSize="19sp" /></RelativeLayout><LinearLayout
        android:id="@+id/ll_top"android:layout_width="match_parent"android:layout_height="50dp"android:background="#607394"android:gravity="center_vertical"android:orientation="horizontal" ><ImageView
            android:id="@+id/iv_back"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:padding="15dp"android:src="@drawable/record_cha" /><ImageView
            android:id="@+id/iv_flash"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:padding="15dp"android:src="@drawable/record_falsh_state" /><ImageView
            android:id="@+id/iv_camera_switch"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:padding="15dp"android:src="@drawable/icn_change_view" /><ImageView
            android:id="@+id/iv_clock"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:padding="15dp"android:src="@drawable/clock" /><TextView
            android:id="@+id/tv_resolution"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:text="480P"android:textColor="@android:color/white"android:textSize="16sp" /></LinearLayout><RelativeLayout
        android:id="@+id/rl_progress"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="gone" ><ProgressBar
            android:id="@+id/progressBar1"style="?android:attr/progressBarStyleLarge"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true" /></RelativeLayout></RelativeLayout>

效果图:

开始录制参数设置(也可以设置其他的参数,其他的需求请自行查阅API):

    /*** 初始化默认录制参数*/private void initData() {mShortVideoConfig = new ShortVideoConfig();//帧率   mShortVideoConfig.fps = 20;//视频的码率mShortVideoConfig.videoBitrate = 1000;//音频的码率mShortVideoConfig.audioBitrate = 64;//默认的视频录制的分辨率(480)mShortVideoConfig.resolution = StreamerConstants.VIDEO_RESOLUTION_480P;//H264编码mShortVideoConfig.encodeType = AVConst.CODEC_ID_AVC;//功率(默认平衡模式)mShortVideoConfig.encodeProfile = VideoEncodeFormat.ENCODE_PROFILE_BALANCE;//默认软编模式mShortVideoConfig.encodeMethod = StreamerConstants.ENCODE_METHOD_SOFTWARE;}

初始化相机操作:

/*** 初始化相机,开始拍摄工作*/private void initCameraData() {mKSYRecordKit.setPreviewFps(mShortVideoConfig.fps);mKSYRecordKit.setTargetFps(mShortVideoConfig.fps);mKSYRecordKit.setVideoKBitrate(mShortVideoConfig.videoBitrate);mKSYRecordKit.setAudioKBitrate(mShortVideoConfig.audioBitrate);mKSYRecordKit.setPreviewResolution(mShortVideoConfig.resolution);mKSYRecordKit.setTargetResolution(mShortVideoConfig.resolution);mKSYRecordKit.setVideoCodecId(mShortVideoConfig.encodeType);mKSYRecordKit.setEncodeMethod(mShortVideoConfig.encodeMethod);mKSYRecordKit.setVideoEncodeProfile(mShortVideoConfig.encodeProfile);mKSYRecordKit.setRotateDegrees(0);mKSYRecordKit.setDisplayPreview(mCameraPreviewView);mKSYRecordKit.setEnableRepeatLastFrame(false);mKSYRecordKit.setCameraFacing(CameraCapture.FACING_FRONT);mKSYRecordKit.setFrontCameraMirror(false);mKSYRecordKit.setOnInfoListener(mOnInfoListener);mKSYRecordKit.setOnErrorListener(mOnErrorListener);mKSYRecordKit.setOnLogEventListener(mOnLogEventListener);CameraTouchHelper cameraTouchHelper = new CameraTouchHelper();cameraTouchHelper.setCameraCapture(mKSYRecordKit.getCameraCapture());mCameraPreviewView.setOnTouchListener(cameraTouchHelper);cameraTouchHelper.setCameraHintView(mCameraHintView);mKSYRecordKit.startCameraPreview();}

开始拍摄:

private void startRecord() {mRecordUrl = getRecordFileFolder() + "/" + System.currentTimeMillis() + ".mp4";videosToMerge.add(mRecordUrl);//每次开始录制时记录mKSYRecordKit.setVoiceVolume(50);mKSYRecordKit.startRecord(mRecordUrl);mIsFileRecording = true;mRecordControler.getDrawable().setLevel(2);}

暂停拍摄(因为是断点拍摄,finished参数判断是不是最后的录制完成标记):

/*** * @param finished*/private void stopRecord(boolean finished) {//录制完成进入编辑//若录制文件大于1则需要触发文件合成if (finished) {if (mKSYRecordKit.getRecordedFilesCount() > 1) {String fileFolder = getRecordFileFolder();//合成文件路径final String outFile = fileFolder + "/" + "merger_" + System.currentTimeMillis() + ".mp4";mKSYRecordKit.stopRecord(outFile, new KSYRecordKit.MegerFilesFinishedListener() {@Overridepublic void onFinished() {runOnUiThread(new Runnable() {@Overridepublic void run() {//TODOToast.makeText(RecordActivity.this, "短视频录制结束!", Toast.LENGTH_SHORT).show();}});}});} else {mKSYRecordKit.stopRecord();}} else {//普通录制停止mKSYRecordKit.stopRecord();}//更新进度显示mRecordProgressCtl.stopRecording();mRecordControler.getDrawable().setLevel(1);updateDeleteView();mIsFileRecording = false;stopChronometer();}

合成的异步任务栈

private class MergeVideos extends AsyncTask<String, Integer, String> {//The working path where the video files are locatedprivate String workingPath; //The file names to mergeprivate ArrayList<String> videosToMerge;//Dialog to show to the userprivate ProgressDialog progressDialog;private MergeVideos(String workingPath, ArrayList<String> videosToMerge) {this.workingPath = workingPath;this.videosToMerge = videosToMerge;}@Overrideprotected void onPreExecute() {if(progressDialog==null){progressDialog = ProgressDialog.show(RecordActivity.this,"合并中...", "请稍等...", true);}else{progressDialog.show();}};@Overrideprotected String doInBackground(String... params) {File storagePath = new File(workingPath);             storagePath.mkdirs();  File myMovie = new File(storagePath, String.format("output-%s.mp4", newName)); finalPath=myMovie.getAbsolutePath();VideoStitchingRequest videoStitchingRequest = new VideoStitchingRequest.Builder().inputVideoFilePath(videosToMerge).outputPath(finalPath).build();FfmpegManager manager = FfmpegManager.getInstance();manager.stitchVideos(RecordActivity.this, videoStitchingRequest,new CompletionListener() {@Overridepublic void onProcessCompleted(String message,List<String> merger) {mMessage=message;}});return mMessage;}@Overrideprotected void onPostExecute(String value) {super.onPostExecute(value);progressDialog.dismiss();progressDialog.cancel();progressDialog=null;if(value!=null){Toast.makeText(RecordActivity.this, "啊哦,录制失败了!请重新尝试...", Toast.LENGTH_SHORT).show();}else{saveFlagPointer(mRecordProgressCtl.getFlagPointers());Intent intent = new Intent(RecordActivity.this,EditVedioActivity.class);intent.putExtra("vedio_path",finalPath);//把最终的路径传过去startActivity(intent);finish();}}}

录制完成后会跳转到编辑界面,合成的操作本来就在子线程中,这里其实是多余启动的一个子线程合成,代码后期还会不断的优化的,勿喷!

package com.tian.videomergedemo.manager;import java.io.File;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;import android.annotation.SuppressLint;
import android.content.Context;import com.tian.videomergedemo.R;
import com.tian.videomergedemo.inter.CompletionListener;
import com.tian.videomergedemo.task.StitchingTask;
import com.tian.videomergedemo.task.TrimTask;
import com.tian.videomergedemo.utils.Utils;/*** Created by TCX on 21/01/17.* * 合并和切割的操做(如果编码的话会很耗时,所以用线程池进行管理控制)* * */
public class FfmpegManager {private static FfmpegManager manager;private String mFfmpegInstallPath;private static int NUMBER_OF_CORES =Runtime.getRuntime().availableProcessors();// 线程队列private final BlockingQueue<Runnable> mDecodeWorkQueue = new LinkedBlockingQueue<Runnable>();private static final int KEEP_ALIVE_TIME = 1;// 线程池设置private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;// 线程池管理ThreadPoolExecutor mDecodeThreadPool = new ThreadPoolExecutor(NUMBER_OF_CORES,       // 初始化线程NUMBER_OF_CORES,       // 最大线程数KEEP_ALIVE_TIME,KEEP_ALIVE_TIME_UNIT,mDecodeWorkQueue);private FfmpegManager() {}public synchronized static FfmpegManager getInstance() {if (manager == null) {manager = new FfmpegManager();}return manager;}/*** 合并操作* @param context* @param videoStitchingRequest* @param completionListener*/public void stitchVideos(Context context, VideoStitchingRequest videoStitchingRequest, CompletionListener completionListener) {installFfmpeg(context);StitchingTask stitchingTask = new StitchingTask(context, mFfmpegInstallPath, videoStitchingRequest, completionListener);mDecodeThreadPool.execute(stitchingTask);}//切割操作public void trimVideo(Context context,File srcFile,File destFile,List<long[]> mNewSeeks,CompletionListener completionListener){installFfmpeg(context);TrimTask trimTask=new TrimTask(context, mFfmpegInstallPath, srcFile, destFile,mNewSeeks , completionListener);mDecodeThreadPool.execute(trimTask);}/** 插入FFmpeg的路径(这里我保存在资源文件下的raw文件夹下)*/@SuppressLint("NewApi") private void installFfmpeg(Context context) {String arch = System.getProperty("os.arch");//获取CPU的架构类型String arc = arch.substring(0, 3).toUpperCase();String rarc = "";int rawFileId;if (arc.equals("ARM")) {//arm架构rawFileId = R.raw.ffmpeg;} else if (arc.equals("MIP")) {rawFileId = R.raw.ffmpeg;} else if (arc.equals("X86")) {//x86架构rawFileId = R.raw.ffmpeg_x86;} else {rawFileId = R.raw.ffmpeg;}File ffmpegFile = new File(context.getCacheDir(), "ffmpeg");mFfmpegInstallPath = ffmpegFile.toString();Utils.installBinaryFromRaw(context, rawFileId, ffmpegFile);ffmpegFile.setExecutable(true);//对操作者的执行权限}}

合成的线程

package com.tian.videomergedemo.task;import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;import android.content.Context;
import android.os.Environment;
import android.util.Log;import com.tian.videomergedemo.inter.CompletionListener;
import com.tian.videomergedemo.manager.VideoStitchingRequest;/*** Created by TCX on 22/01/16.*/
public class StitchingTask implements Runnable {private Context context;private VideoStitchingRequest videoStitchingRequest;private CompletionListener completionListener;private String mFfmpegInstallPath;public StitchingTask(Context context, String mFfmpegInstallPath, VideoStitchingRequest stitchingRequest, CompletionListener completionListener) {this.context = context;this.mFfmpegInstallPath = mFfmpegInstallPath;this.videoStitchingRequest = stitchingRequest;this.completionListener = completionListener;}@Overridepublic void run() {stitchVideo(context, mFfmpegInstallPath, videoStitchingRequest, completionListener);}private void stitchVideo(Context context, String mFfmpegInstallPath, VideoStitchingRequest videoStitchingRequest, final CompletionListener completionListener) {//合成的路径String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "ffmpeg_videos";File dir = new File(path);if (!dir.exists()) {dir.mkdirs();}File inputfile = new File(path, "input.txt");try {inputfile.createNewFile();FileOutputStream out = new FileOutputStream(inputfile);for (String string : videoStitchingRequest.getInputVideoFilePaths()) {out.write(("file " + "'" + string + "'").getBytes());out.write("\n".getBytes());}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}
//        execFFmpegBinary("-i " + src.getAbsolutePath() + " -ss "+ startMs/1000 + " -to " + endMs/1000 + " -strict -2 -async 1 "+ dest.getAbsolutePath());//合成的FFmpeg命令行String[] sampleFFmpegcommand = {mFfmpegInstallPath, "-f", "concat", "-i", inputfile.getAbsolutePath(), "-c", "copy", videoStitchingRequest.getOutputPath()};try {Process ffmpegProcess = new ProcessBuilder(sampleFFmpegcommand).redirectErrorStream(true).start();String line;BufferedReader reader = new BufferedReader(new InputStreamReader(ffmpegProcess.getInputStream()));Log.d("***", "*******Starting FFMPEG");while ((line = reader.readLine()) != null) {Log.d("***", "***" + line + "***");}Log.d(null, "****ending FFMPEG****");ffmpegProcess.waitFor();} catch (Exception e) {e.printStackTrace();}inputfile.delete();//合成成功的接口回调completionListener.onProcessCompleted("Video Stitiching Comleted",null);}
}

都有注释,不用我多扯皮了吧!

OK,至此,合成和裁剪的java层已出,篇幅太长,下一篇,接着来(并附传送门)!

加油!

Github地址(大家下载的时候顺便给个star也是对作者劳动成果的肯定,谢谢):
https://github.com/T-chuangxin/VideoMergeDemo

android视频的编辑(录制,裁剪,合成)(1)相关推荐

  1. 【多媒体开发】Android视频全屏录制遇到的一些问题

    最近写了个demo,有个功能是全屏录制视频,录视频的基本功能不算难,demo通过SurfaceView + MediaRecorder的方法, 这里不上代码了,只总结下一些问题: 1.Paramete ...

  2. android 视频裁剪view拖动,android – 视频使用特定坐标裁剪或缩放?

    您可以使用SurfaceView执行此操作.看看我的文章 Surface View – Video Cropping. 用于裁剪SurfaceView的代码. private void updateT ...

  3. Android视频编辑器(一)通过OpenGL预览、录制视频以及断点续录等

    前言 如今的视频类app可谓是如日中天,火的不行.比如美拍.快手.VUE.火山小视频.抖音小视频等等.而这类视频的最基础和核心的功能就是视频录制和视频编辑功能.包括了手机视频录制.美白.加滤镜.加水印 ...

  4. Android视频编辑器(二)预览、录制视频加上水印和美白磨皮效果

    前言      这是视频编辑器系列的第二篇文章,在上篇文章中,我们讲解了利用OpenGl和SurfaceView进行视频预览,MediaCodec和MeidaMuxer进行视频录制和断点续录.而这篇主 ...

  5. 利用FFmpeg玩转Android视频录制与压缩(二)

    请尊重原创,转载请注明出处:http://blog.csdn.net/mabeijianxi/article/details/72983362 预热 时光荏苒,光阴如梭,离上一次吹牛逼已经过去了两三个 ...

  6. 用FFmpeg玩转Android视频录制与压缩

    [置顶] 利用FFmpeg玩转Android视频录制与压缩(二) 标签: Android视频采集Android视频编码Android FFmpegAndroid 视频压缩视频编码 2017-06-10 ...

  7. android 视频美颜,Android短视频中如何实现720P磨皮美颜录制?

    视频中磨皮.美颜功能已成为刚需,那么如何在Android短视频中实现720P磨皮美颜录制?本篇文章中,网易云信资深开发工程师将向大家介绍具体的操作方法. 相关阅读推荐 在Android上要实现一个录制 ...

  8. Android视频编辑SDK免费版,Android视频编辑SDK

    android视频编辑sdk是一款视频编辑软件,用户可以看到视频配音配乐.添加字幕.添加滤镜.视频转场等各种功能,并可以快速的植入到软件中进行编辑,编辑的过程中支持用户进行智能的硬件解码,选择视频时不 ...

  9. 利用FFmpeg玩转Android视频录制与压缩(三)

    请尊重原创,转载请注明出处http://blog.csdn.net/mabeijianxi/article/details/73011313 前言 上一回说到啊,这千秋月没是佳人离别,时逢枯枝落旧城, ...

最新文章

  1. HTTP.sys 远程执行代码验证工具
  2. java 连接池_初探数据库连接池
  3. 两个列表合并去重_花生AI论文去重V1.4更新合并查同义词功能
  4. android关于控件中setTag(key,Object)的设置的相关问题
  5. php mysql 无法查询中文名字_PHP连接MySQL查询中文时显示Notice: Trying to get property of non-object...
  6. [蓝桥杯2017初赛]纸牌三角形-枚举permutation+数论
  7. html转pdf后 框会消失,html或其它文件转pdf弹出打开保存框
  8. 加白名单_Android保活从入门到放弃:乖乖引导用户加白名单吧
  9. Docker storage driver(十四)
  10. java 中缀式转后缀式
  11. 【Spring】bean的作用域
  12. 关于mqtt+js前端中mqtt服务器关闭重连服务器后js前端接收不到消息的问题
  13. turtle绘制奥运五环
  14. 推荐算法之Thompson(汤普森)采样
  15. 长期应用阿达木单抗时所产生的抗抗体会影响疗效
  16. 世界杯网页梦幻联动.html
  17. C语言求解三个数的中间值
  18. 解线性方程组的直接方法:LU分解法及其C语言算法实现
  19. 【Python之pymysql库学习】二.游标cursor的相关知识(保姆级图文+实现代码)
  20. 应用JAVA进行密码加密的一种算法

热门文章

  1. 如何编写python函数_编写高质量的Python代码系列(二)之函数
  2. c语言迷宫问题程序流程图,c语言程序设计 迷宫问题
  3. 《海洋测绘》:从地方坐标系到2000国家大地坐标系的转换方法
  4. PX4源码分析__传感器数据“sensor_combined”的来龙去脉
  5. 限制input输入字符数(中文2个字符,英文1个字符)
  6. 物联网全栈开发实战系列文章汇总(共865篇,持续更新-2023.05.02)
  7. linux下下载fnl数据,如何下载fnl
  8. window7系统为什么老是弹出交互式服务检测
  9. 软件测试每日一题—分享功能测试
  10. 8月新亲测完美短视频点赞系统支持抖音+快手+刷宝+微视等所有主流短视频点赞/关注/评论系统源码