美文网首页Android问题集Android项目二楼后座android
android 视频裁切,拼接和合成,添加滤镜,修改视频播放速度

android 视频裁切,拼接和合成,添加滤镜,修改视频播放速度

作者: 唐小鹏 | 来源:发表于2018-10-16 11:34 被阅读155次

    前言:闲来无事,想自己做一个视频编辑器,能够满足自己本身日常需要,而不是依赖于其他商业的app,部分功能用的是七牛提供的短视频sdk,对比了阿里,腾讯的短视频sdk,感觉七牛提供的sdk功能强大一些,但是后面真正用起来的时候,发现很多明显的bug,现在只用七牛能用的功能咯

    整个app主要有的功能就是:视频裁切,视频合成,视频中添加文字,语音,图片,涂鸦等功能,使用起来也是超级的方便,直接秒杀其他收费类app。

    因为本项目开源,代码公开,所以只讲一讲项目中的难点,以及在开发的时候需要注意的地方

    本项目主要的难点在于:

    1,需要想要像本人那样,在同一个页面添加多个视频,裁切后拼接预览,PLShortVideoEditor只能实例化一次,七牛裁切使用的是GLSurfaceView。而他在Acivitiy中只能存在一个,并且需要渲染器,所以后面决定了使用fragment,在fragment里面渲染,后面再把PLShortVideoEditor传入到Acivitiy

        /**
         * @dec fragment中的实例化七牛视频类PLShortVideoEditor
         * @author fanqie
         * @date 2018/8/28 16:28
         */
        private void initShortVideoEditor(String mMp4path) {
            MyLog.i(TAG, "editing file: " + mMp4path);
            setting.setSourceFilepath(mMp4path);
            // 视频源文件路径
            setting.setDestFilepath(Config.EDITED_FILE_PATH);
            // 编辑保存后,是否保留源文件
            setting.setKeepOriginFile(true);
            //编辑后保存的目标文件路径
            //SquareGLSurfaceView srlQiqiuVideo = new SquareGLSurfaceView(context);
            //mSrlQiqiuVideoInlude.removeAllViews();
            //mSrlQiqiuVideoInlude.addView(srlQiqiuVideo);
    
            mShortVideoEditor = new PLShortVideoEditor(mGlsvVideoCommon, setting);
            ((BekidMainActivity)getActivity()).getShortVideoEditor(mShortVideoEditor);
        }
    

    2.切换fragment的时候,需要 mShortVideoEditor.stopPlayback(); 停止视频播放,不然切换会有声音继续

    3.不要想到使用多个播放器去实现1的问题,要求连续播放不同视频,不能使用多个播放器,影响性能

    4.因为视频有裁切,合成是在最后执行,如果需要判断获取播放的时间点,以及裁切起始结束都应该使用0.1秒为单位,防止偏移,为什么不以秒为单位,后面会说

        /**
         * 获取视频播放的时间
         */
        @SuppressLint("HandlerLeak")
        public Handler getCurrentHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1:
                        //需要监听播放的时间点,播放下一段视频  ,这个需要按照秒来计算获取,毫秒的话,可能精确不到  ?????
                        //采用四舍五入
                        int getCurrentTime = MathSwitch(mShortVideoEditor.getCurrentPosition());
                        int getEndTime = MathSwitch(mDataVideoList.get(getnowVideo).getEndTime());
    
                        MyLog.i("tangpeng", "getCurrentTime=" + getCurrentTime);
                        MyLog.i("tangpeng", "getEndTime=" + getEndTime);
                        if (getnowVideo < mDataVideoList.size() - 1) {
                            if (getCurrentTime + 1 >= getEndTime) {
                                //如果播放到截取的时间,播放下一个视频,算播放完成
                                getnowVideo++;
                                Log.i(TAG, "播放完成后,继续播放下一段视频=" + getnowVideo);
                                getVideoMsIng = mDataVideoList.get(getnowVideo).getStartTime();
    
                                MyLog.i(TAG, "后面切换的时候再补上?????????????????????????????");
    //                            reIdlePlay();
                                reIdleReStartPlay();
    
                            } else {
                                //循环发送消息, 携带进度
                                msg = Message.obtain();
                                getCurrentHandler.removeMessages(1);
                                msg.what = 1;
                                getCurrentHandler.sendMessageDelayed(msg, delayMillisCurrent);
                            }
                        } else {
                            if (getCurrentTime + 1 >= getEndTime) {
                                MyLog.i(TAG, "播放到最后一段视频,回到第一段视频暂停");
                                //需要切换视频,通过传递位置,设置播放状态
                                reIdleReStartPlay();
                            } else {
                                //循环发送消息, 携带进度
                                msg = Message.obtain();
                                getCurrentHandler.removeMessages(1);
                                msg.what = 1;
                                getCurrentHandler.sendMessageDelayed(msg, delayMillisCurrent);
                            }
                        }
    
                        //实时播放音乐
                        if (mDataMusicList.size() > 0) {
                            for (int i = 0; i < mDataMusicList.size(); i++) {
                                //这里暂时是算一个视频
                                int mVideoStartTime = MathSwitch(mDataVideoList.get(0).getStartTime());
                                int voiceStartTime = MathSwitch(mDataMusicList.get(i).getStartInsertTime()) + mVideoStartTime;
                                int voiceEnd = MathSwitch(mDataMusicList.get(i).getEndTime()+mVideoStartTime);
                                MyLog.i(TAG, "voiceStartTime=" + voiceStartTime);
                                MyLog.i(TAG, "voiceEnd=" + voiceEnd);
                                MyLog.i(TAG, "getCurrentTime=" + getCurrentTime);
                                //播放声音
                                if (getCurrentTime == voiceStartTime) {
                                    mUPlayerMusic.start(mDataMusicList.get(i).getMusicUrl(), (int) mDataMusicList.get(i).getStartTime());
                                } else if (getCurrentTime == voiceStartTime) {//这里需要注意,结束时间是开始播放的时间+裁切后的结束时间
                                    mUPlayerMusic.stop();
                                }
                            }
                        }
                        //实时播放声音
                        if (mDataListVoice.size() > 0) {
                            for (int i = 0; i < mDataListVoice.size(); i++) {
                                //这里暂时是算一个视频
                                int mVideoStartTime = MathSwitch(mDataVideoList.get(0).getStartTime());
                                int voiceStartTime = MathSwitch(mDataListVoice.get(i).getStartInsertTime()) + mVideoStartTime;
                                int voiceEnd = MathSwitch(mDataListVoice.get(i).getEndTime()+mVideoStartTime);
                                //播放声音
                                if (getCurrentTime == voiceStartTime) {
                                    mUPlayerVoice.start(mDataListVoice.get(i).getMusicUrl(), (int) mDataListVoice.get(i).getStartTime());
                                } else if (getCurrentTime == voiceStartTime + voiceEnd) {//这里需要注意,结束时间是开始播放的时间+裁切后的结束时间
                                    mUPlayerVoice.stop();
                                }
                            }
                        }
    
                        break;
                    default:
                        break;
                }
            }
        };
    

    5.需要注意的地方,activty里面添加fragment,他们的生命周期是分开的,并行,并不会说执行了fragment之后再继续往下执行。需要在切换的fragment添加

     @Override
        public void onStop() {
            super.onStop();
            mShortVideoEditor.pausePlayback();
            MyLog.i(TAG, "onStop");
        }
    

    6.如果有多个状态需要判断,使用枚举比int整型,更加直观,比如播放的状态就有很多种

       private VideoPlayStatus mVideoPlayStatus = VideoPlayStatus.Idle;
        //需要切换视频的状态
        private enum VideoPlayStatus {
            Idle,//默认
            playPlay,//播放
            pausePlay,//暂停播放
            stopPlay,//停止播放
            resumePlay,//从0开始播放
            reStartPlay,//播放完了之后,回到第一段暂停
        }
    

    7.视频有多个,每一个需要需要单独判断再相加,不能相加后再判断,比如说总时间 (int)4.5+5.2+5.8的结果和(int)4.8+(int)5.2+(int)5.8的结果是不一样的哦

    8.mShortVideoEditor.startPlayback();执行了之后再去执行其他添加编辑方法,七牛那边要求,比如添加了文字,贴图,标注等,需要是要预览需要先执行startPlayback();

    9.在添加多段的视频中,可以拖动视频来排序,每一段视频可以裁切,如果删除一段视频,删除recyclerView一个item,再添加一个新的item,会出现旧的item的缓存,记得 mMenuRecyclerView.removeViewAt(position);这里把裁切视频的动画关键代码贴出来,供参考

            //拖动左边
            holder.mHandlerLeft.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    int action = event.getAction();
                    float viewX = v.getX();//相对于父类的x的坐标
                    float movedX = event.getX();//getX()即表示的点击的位置相对于本身的坐标 ,getX()会突然变大,导致偏移??????????????
    //                float finalX = viewX + movedX;
                    holder.rlGetVideoHandler.getLocationInWindow(rlGetVideoHandlerPosition);
                    float finalX = event.getRawX()-rlGetVideoHandlerPosition[0];
                    //滑动控件的位置-视频区域的位置,就是滑动控件位于视频区域的偏移量
    
                    updateHandlerLeftPosition(holder.tvFrgmentCutTime, mDurationMs, holder.handlerLeftAlpha, holder.handlerRightAlpha, holder.mFrameListView, holder.mHandlerLeft, holder.mHandlerRight, finalX,mRlVideoHandlerLeft,mSlicesTotalLength);
    
                    if(action==MotionEvent.ACTION_DOWN){
                        MyLog.i(TAG,"ACTION_DOWN");
                        holder.rlFrgmentCutFuncNormal.setVisibility(View.GONE);
                        holder.rlFrgmentCutFuncSelect.setVisibility(View.VISIBLE);
                    }
                    if (action == MotionEvent.ACTION_UP) {
                        MyLog.i(TAG,"ACTION_UP");
                        holder.rlFrgmentCutFuncNormal.setVisibility(View.VISIBLE);
                        calculateRange(holder.handlerLeftAlpha, holder.handlerRightAlpha, holder.mHandlerLeft, holder.mHandlerRight, holder.mFrameListView, mDurationMs, position);
                    }
                    return true;
                }
            });
    
        public void updateHandlerLeftPosition(TextView tvFrgmentCutTime, long mDurationMs, View mHandlerLeftAlpha, View mHandlerRightAlpha, LinearLayout mFrameListView, View mHandlerLeft, View mHandlerRight, float movedPosition, RelativeLayout mRlVideoHandlerLeft, int mSlicesTotalLength) {
            RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mHandlerLeft.getLayoutParams();
            lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);//因为需要有阴影效果,所以靠右对齐,这个时候的起始点其实是图标的右边
    
            if ((movedPosition) > mHandlerRight.getX()) {//这个时候的起始点其实是图标的右边
                lp.rightMargin = (int) (mRlVideoHandlerLeft.getWidth() - mHandlerRight.getX());
            } else if (movedPosition < mHandlerLeft.getWidth()) {
                lp.rightMargin = mRlVideoHandlerLeft.getWidth() - (mHandlerLeft.getWidth());
            } else {
                lp.rightMargin = (int) (mRlVideoHandlerLeft.getWidth() - movedPosition);
            }
            mHandlerLeft.setLayoutParams(lp);
    
            //使用滑动的阴影
            float beginPercent = 1.0f * ((mHandlerLeft.getX() + mHandlerLeft.getWidth() / 2) - mFrameListView.getX()) / mSlicesTotalLength;
            MyLog.i(TAG, "beginPercent=" + beginPercent);
    
            //获取裁切的时间
            Long mSelectedBeginMs = (long) (beginPercent * mDurationMs);
            tvFrgmentCutTime.setText(Tools.getTimeZone(mSelectedBeginMs) + "");
        }
    
        /**
         * 获取到裁切范围
         *
         * @param mHandlerLeft
         * @param mHandlerRight
         * @param mFrameListView
         * @param mDurationMs
         */
        private void calculateRange(View mHandlerLeftAlpha, View mHandlerRightAlpha, View mHandlerLeft, View mHandlerRight, LinearLayout mFrameListView, long mDurationMs, int position) {
    
            float beginPercent = 1.0f * ((mHandlerLeft.getX() + mHandlerLeft.getWidth() / 2) - mFrameListView.getX()) / mSlicesTotalLength;
            float endPercent = 1.0f * ((mHandlerRight.getX() + mHandlerRight.getWidth() / 2) - mFrameListView.getX()) / mSlicesTotalLength;
            beginPercent = QiniuTool.clamp(beginPercent);
            endPercent = QiniuTool.clamp(endPercent);
    
    
            Long mSelectedBeginMs = (long) (beginPercent * mDurationMs);
            Long mSelectedEndMs = (long) (endPercent * mDurationMs);
            Log.i(TAG, "begin percent: " + beginPercent + " end percent: " + endPercent);
            Log.i(TAG, "mDurationMs: " + mDurationMs);
            Log.i(TAG, "new range: " + mSelectedBeginMs + "-" + mSelectedEndMs);
    
    
            //重新保存视频数据。裁切作品和计算时间
            videobean mvideobean = new videobean();
            mvideobean.setStartTime(mSelectedBeginMs);
            mvideobean.setEndTime(mSelectedEndMs);
            mvideobean.setVideoUrl(mDataVideoList.get(position).getVideoUrl());
            mvideobean.setVideoSize((mSelectedEndMs - mSelectedBeginMs));
            mvideobean.setGetAllTime(mDurationMs);
            mDataVideoList.set(position, mvideobean);
            // 当前的视频参数需要修改
    
    
            mDurationMsAll = 0;
            //总的进度条时间需要修改
            for (int i = 0; i < mDataVideoList.size(); i++) {
                mDurationMsAll = mDurationMsAll + mDataVideoList.get(i).getVideoSize();
            }
            MyLog.i(TAG, "mDurationMsAll=" + mDurationMsAll);//
    
            ((BekidMainActivity) mContext).updateSeekBar(position);
            ((BekidMainActivity) mContext).reIdleReStartPlay();
        }
    

    10.关于在播放的时候,获取视频播放的时间,是以ms为单位还是以s单位,以s为单位虽然好判断,但是进度条会出现一卡一卡的效果,体验不好,以ms 单位进度条会流畅很多,但是毫秒时间太短,判断逻辑处理的时间可能都不够,会错失时间点,所以最后衡量了一下,使用了0.1秒这个相对于中间值

    11.视频合成的时候,需求要求是能够在指定的点插入音频,而且可以插入多段音频(需要用到ffmpeg混音,解决思路就是:1.先裁切出每一段音频,2.再把合成后的视频的音频截取出来,3.再把1和2的音频混合成一个新的音频文件,4.分离出来的无音频的视频插入3的音频文件)android音频编辑之音频合成

    12.不能用第三方的播放器,进度条需要自己定义,因为可以拖动滚动条实时预览,获取进度条值的关键代码

            //设置进度条拖拽的监听
            mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                @Override
                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                    int getProgress = (int) (progress * delayMillisCurrent);
                    //判断是否是由用户拖拽发生的变化
                    if (fromUser) {
                        mPausePlayback.setImageResource(R.drawable.qa1);
                        mIvPlaybackPlay.setImageResource(R.drawable.q1);
    
                        Log.i(TAG, "这里是进度条是按照s的,但是视频是按照ms的所以需要转换=" + getProgress);
                        //需要判断是拖动到了第几段视频了
                        for (int i = 0; i < mDataVideoList.size(); i++) {
                            if (getProgress < mDataVideoList.get(i).getVideoSize()) {
                                getnowVideo = i;//一旦小于,就代表第几段,退出
                                if (getnowVideo == 0) {
                                    getVideoMsIng = getProgress + mDataVideoList.get(i).getStartTime();//播放起始时间,需要加上裁切的时间
                                } else {
                                    //选择从第几段的,多少秒开始播放视频
                                    getVideoMsIng = getProgress + mDataVideoList.get(i).getStartTime() - mDataVideoList.get(getnowVideo - 1).getVideoSize();//
                                }
                                MyLog.i(TAG, "reIdlePlay=拖动后需要关闭声音");
    
                                mShortVideoEditor.seekTo((int) getVideoMsIng);
    
                                if (mVideoPlayStatus == VideoPlayStatus.playPlay) {
                                    pausePlayback();
                                }
    
                                //暂时如果拖动的话,就先暂停视频
    //                            onStopVoice();
    //                            reIdlePlay();
    //                            if (getProgress != 0) {
    //                                handler.removeMessages(1);
    //                                Message message = Message.obtain();
    //                                message.what = 1;
    //                                message.arg1 = getProgress;
    //                                message.arg2 = (int) mDurationMsAll;
    //                                handler.sendMessageDelayed(message, delayMillis);
    //                            }
                                return;
                            }
                        }
                    }
                }
    
                @Override
                public void onStartTrackingTouch(SeekBar seekBar) {
    
                }
    
                @Override
                public void onStopTrackingTouch(SeekBar seekBar) {
    
                }
            });
    

    13视频的裁切,拼接和合成,给视频添加滤镜,修改视频播放速度,添加声音(只能添加一段),添加文字,贴图,标注,最后合成视频,这一块功能都是用的七牛提供的sdk,这里说明一下,七牛短视频这个产品,在七牛所有的产品中,不是属于核心产品,团队也比较小,而需求也是有客户定的,如果客户反馈一个功能,他们觉得有必要,就会在下个版本中添加,而且在使用的过程中sdk功能还是很单一,不太能满足需求,

    这里用到了好几个开源项目:

    好了最后把开源的项目地址贡献出来,如果喜欢请记得在github中star哦,

    github链接地址

    相关文章

      网友评论

        本文标题:android 视频裁切,拼接和合成,添加滤镜,修改视频播放速度

        本文链接:https://www.haomeiwen.com/subject/kjgczftx.html