Android实现视频剪切、视频拼接以及音视频合并

音视频 专栏收录该内容
3 篇文章 0 订阅
              因公司项目有需求,要实现视频剪切,视频拼接以及音视频合并的功能,自己通过在网上查找大量的资料终于把功能实现了,把实现的公共类提取出来,以便以后复习巩固。

使用map4parser作为视频处理包,android studio引入 compile 'com.googlecode.mp4parser:isoparser:1.1.21'//视频处理

工具类

一、视频处理工具类  

/**
 * mp4处理公共类
 * Created by lxy on 17-4-21.
 */

public class Mp4ParseUtil {


    /**
     * Mp4文件集合进行追加合并(按照顺序一个一个拼接起来)
     *
     * @param mp4PathList [输入]Mp4文件路径的集合(支持m4a)(不支持wav)
     * @param outPutPath  [输出]结果文件全部名称包含后缀(比如.mp4)
     * @throws IOException 格式不支持等情况抛出异常
     */
    public static void appendMp4List(List<String> mp4PathList, String outPutPath){


        try {

            List<Movie> mp4MovieList = new ArrayList<>();// Movie对象集合[输入]
            for (String mp4Path : mp4PathList) {// 将每个文件路径都构建成一个Movie对象
                mp4MovieList.add(MovieCreator.build(mp4Path));
            }

            List<Track> audioTracks = new LinkedList<>();// 音频通道集合
            List<Track> videoTracks = new LinkedList<>();// 视频通道集合

            for (Movie mp4Movie : mp4MovieList) {// Movie对象集合进行循环
                for (Track inMovieTrack : mp4Movie.getTracks()) {
                    if ("soun".equals(inMovieTrack.getHandler())) {// Movie对象中取出音频通道
                        audioTracks.add(inMovieTrack);
                    }
                    if ("vide".equals(inMovieTrack.getHandler())) {// Movie对象中取出视频通道
                        videoTracks.add(inMovieTrack);
                    }
                }
            }
            Movie resultMovie = new Movie();// 结果Movie对象[输出]
            if (!audioTracks.isEmpty()) {// 将所有音频通道追加合并
                resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
            }

            if (!videoTracks.isEmpty()) {// 将所有视频通道追加合并
                resultMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
            }

            Container outContainer = new DefaultMp4Builder().build(resultMovie);// 将结果Movie对象封装进容器
            FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel();
            outContainer.writeContainer(fileChannel);// 将容器内容写入磁盘
            fileChannel.close();

        }catch(Exception e){
            e.printStackTrace();
        }

    }

    /**
     * AAC文件集合进行追加合并(按照顺序一个一个拼接起来)
     *
     * @param aacPathList [输入]AAC文件路径的集合(不支持wav)
     * @param outPutPath  [输出]结果文件全部名称包含后缀(比如.aac)
     * @throws IOException 格式不支持等情况抛出异常
     */
    public static void appendAacList(List<String> aacPathList, String outPutPath){

        try{

            List<Track> audioTracks = new LinkedList<>();// 音频通道集合
            for (int i = 0; i < aacPathList.size(); i++) {// 将每个文件路径都构建成一个AACTrackImpl对象
                audioTracks.add(new AACTrackImpl(new FileDataSourceImpl(aacPathList.get(i))));
            }

            Movie resultMovie = new Movie();// 结果Movie对象[输出]
            if (!audioTracks.isEmpty()) {// 将所有音频通道追加合并
                resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
            }

            Container outContainer = new DefaultMp4Builder().build(resultMovie);// 将结果Movie对象封装进容器
            FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel();
            outContainer.writeContainer(fileChannel);// 将容器内容写入磁盘
            fileChannel.close();

        }catch (Exception e){
            e.printStackTrace();
        }

    }


    private static List<Movie> moviesList = new ArrayList<>();
    private static List<Track> videoTracks = new ArrayList<>();
    private static List<Track> audioTracks = new ArrayList<>();
    //将两个mp4视频进行拼接
    public static void appendMp4(List<String> mMp4List,String outputpath){


        try {
            for (int i=0;i<mMp4List.size();i++) {
                Movie movie=MovieCreator.build(mMp4List.get(i));
                moviesList.add(movie);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        for (Movie m : moviesList) {
            for (Track t : m.getTracks()) {
                if (t.getHandler().equals("soun")) {
                    audioTracks.add(t);
                }
                if (t.getHandler().equals("vide")) {
                    videoTracks.add(t);
                }
            }
        }

        Movie result = new Movie();

        try {
            if (audioTracks.size() > 0) {
                result.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
            }
            if (videoTracks.size() > 0) {
                result.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        Container out = new DefaultMp4Builder().build(result);

        try {
            FileChannel fc = new FileOutputStream(new File(outputpath)).getChannel();
            out.writeContainer(fc);
            fc.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        moviesList.clear();
    }


    /**
     * AAC MP4 进行混合[替换了视频的音轨]
     *
     * @param aacPath .aac
     * @param mp4Path .mp4
     * @param outPath .mp4
     */
    public static boolean muxAacMp4(String aacPath, String mp4Path, String outPath) {
        boolean flag=false;
        try {
            AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(aacPath));
            Movie videoMovie = MovieCreator.build(mp4Path);
            Track videoTracks = null;// 获取视频的单纯视频部分
            for (Track videoMovieTrack : videoMovie.getTracks()) {
                if ("vide".equals(videoMovieTrack.getHandler())) {
                    videoTracks = videoMovieTrack;
                }
            }

            Movie resultMovie = new Movie();
            resultMovie.addTrack(videoTracks);// 视频部分
            resultMovie.addTrack(aacTrack);// 音频部分

            Container out = new DefaultMp4Builder().build(resultMovie);
            FileOutputStream fos = new FileOutputStream(new File(outPath));
            out.writeContainer(fos.getChannel());
            fos.close();
            flag=true;
            Log.e("update_tag","merge finish");
        } catch (Exception e) {
            e.printStackTrace();
            flag=false;
        }
        return flag;
    }



    /**
     * M4A MP4 进行混合[替换了视频的音轨]
     *
     * @param m4aPath .m4a[同样可以使用.mp4]
     * @param mp4Path .mp4
     * @param outPath .mp4
     */
    public static void muxM4AMp4(String m4aPath, String mp4Path, String outPath) throws IOException {
        Movie audioMovie = MovieCreator.build(m4aPath);
        Track audioTracks = null;// 获取视频的单纯音频部分
        for (Track audioMovieTrack : audioMovie.getTracks()) {
            if ("soun".equals(audioMovieTrack.getHandler())) {
                audioTracks = audioMovieTrack;
            }
        }

        Movie videoMovie = MovieCreator.build(mp4Path);
        Track videoTracks = null;// 获取视频的单纯视频部分
        for (Track videoMovieTrack : videoMovie.getTracks()) {
            if ("vide".equals(videoMovieTrack.getHandler())) {
                videoTracks = videoMovieTrack;
            }
        }

        Movie resultMovie = new Movie();
        resultMovie.addTrack(videoTracks);// 视频部分
        resultMovie.addTrack(audioTracks);// 音频部分

        Container out = new DefaultMp4Builder().build(resultMovie);
        FileOutputStream fos = new FileOutputStream(new File(outPath));
        out.writeContainer(fos.getChannel());
        fos.close();
    }


    /**
     * 分离mp4视频的音频部分,只保留视频部分
     *
     * @param mp4Path .mp4
     * @param outPath .mp4
     */
    public static void splitMp4(String mp4Path, String outPath){

        try{
            Movie videoMovie = MovieCreator.build(mp4Path);
            Track videoTracks = null;// 获取视频的单纯视频部分
            for (Track videoMovieTrack : videoMovie.getTracks()) {
                if ("vide".equals(videoMovieTrack.getHandler())) {
                    videoTracks = videoMovieTrack;
                }
            }

            Movie resultMovie = new Movie();
            resultMovie.addTrack(videoTracks);// 视频部分

            Container out = new DefaultMp4Builder().build(resultMovie);
            FileOutputStream fos = new FileOutputStream(new File(outPath));
            out.writeContainer(fos.getChannel());
            fos.close();
        }catch (Exception e){
            e.printStackTrace();
        }

    }


    /**
     * 分离mp4的视频部分,只保留音频部分
     *
     * @param mp4Path .mp4
     * @param outPath .aac
     */
    public static void splitAac(String mp4Path, String outPath){

        try{
            Movie videoMovie = MovieCreator.build(mp4Path);
            Track videoTracks = null;// 获取音频的单纯视频部分
            for (Track videoMovieTrack : videoMovie.getTracks()) {
                if ("soun".equals(videoMovieTrack.getHandler())) {
                    videoTracks = videoMovieTrack;
                }
            }

            Movie resultMovie = new Movie();
            resultMovie.addTrack(videoTracks);// 音频部分

            Container out = new DefaultMp4Builder().build(resultMovie);
            FileOutputStream fos = new FileOutputStream(new File(outPath));
            out.writeContainer(fos.getChannel());
            fos.close();
        }catch (Exception e){
            e.printStackTrace();
        }

    }


    /**
     * 分离mp4视频的音频部分,只保留视频部分
     *
     * @param mp4Path .mp4
     * @param mp4OutPath  mp4视频输出路径
     * @param aacOutPath  aac视频输出路径
     */
    public static void splitVideo(String mp4Path, String mp4OutPath,String aacOutPath){

        try{
            Movie videoMovie = MovieCreator.build(mp4Path);
            Track videTracks = null;// 获取视频的单纯视频部分
            Track sounTracks = null;// 获取视频的单纯音频部分

            for (Track videoMovieTrack : videoMovie.getTracks()) {
                if ("vide".equals(videoMovieTrack.getHandler())) {
                    videTracks = videoMovieTrack;
                }
                if ("soun".equals(videoMovieTrack.getHandler())) {
                    sounTracks = videoMovieTrack;
                }
            }

            Movie videMovie = new Movie();
            videMovie.addTrack(videTracks);// 视频部分

            Movie sounMovie = new Movie();
            sounMovie.addTrack(sounTracks);// 音频部分

            // 视频部分
            Container videout = new DefaultMp4Builder().build(videMovie);
            FileOutputStream videfos = new FileOutputStream(new File(mp4OutPath));
            videout.writeContainer(videfos.getChannel());
            videfos.close();

            // 音频部分
            Container sounout = new DefaultMp4Builder().build(sounMovie);
            FileOutputStream sounfos = new FileOutputStream(new File(aacOutPath));
            sounout.writeContainer(sounfos.getChannel());
            sounfos.close();

        }catch (Exception e){
            e.printStackTrace();
        }

    }


    /**
     * Mp4 添加字幕
     *
     * @param mp4Path .mp4 添加字幕之前
     * @param outPath .mp4 添加字幕之后
     */
    public static void addSubtitles(String mp4Path, String outPath) throws IOException {
        Movie videoMovie = MovieCreator.build(mp4Path);

        TextTrackImpl subTitleEng = new TextTrackImpl();// 实例化文本通道对象
        subTitleEng.getTrackMetaData().setLanguage("eng");// 设置元数据(语言)

        subTitleEng.getSubs().add(new TextTrackImpl.Line(0, 1000, "Five"));// 参数时间毫秒值
        subTitleEng.getSubs().add(new TextTrackImpl.Line(1000, 2000, "Four"));
        subTitleEng.getSubs().add(new TextTrackImpl.Line(2000, 3000, "Three"));
        subTitleEng.getSubs().add(new TextTrackImpl.Line(3000, 4000, "Two"));
        subTitleEng.getSubs().add(new TextTrackImpl.Line(4000, 5000, "one"));
        subTitleEng.getSubs().add(new TextTrackImpl.Line(5001, 5002, " "));// 省略去测试
        videoMovie.addTrack(subTitleEng);// 将字幕通道添加进视频Movie对象中

        Container out = new DefaultMp4Builder().build(videoMovie);
        FileOutputStream fos = new FileOutputStream(new File(outPath));
        out.writeContainer(fos.getChannel());
        fos.close();
    }


    /**
     * MP4 切割
     *
     * @param mp4Path    .mp4
     * @param fromSample 起始位置   不是传入的秒数
     * @param toSample   结束位置   不是传入的秒数
     * @param outPath    .mp4
     */
    public static void cropMp4(String mp4Path, long fromSample, long toSample, String outPath){

        try{

            Movie mp4Movie = MovieCreator.build(mp4Path);
            Track videoTracks = null;// 获取视频的单纯视频部分
            for (Track videoMovieTrack : mp4Movie.getTracks()) {
                if ("vide".equals(videoMovieTrack.getHandler())) {
                    videoTracks = videoMovieTrack;
                }
            }
            Track audioTracks = null;// 获取视频的单纯音频部分
            for (Track audioMovieTrack : mp4Movie.getTracks()) {
                if ("soun".equals(audioMovieTrack.getHandler())) {
                    audioTracks = audioMovieTrack;
                }
            }

            Movie resultMovie = new Movie();
            resultMovie.addTrack(new AppendTrack(new CroppedTrack(videoTracks, fromSample, toSample)));// 视频部分
            resultMovie.addTrack(new AppendTrack(new CroppedTrack(audioTracks, fromSample, toSample)));// 音频部分

            Container out = new DefaultMp4Builder().build(resultMovie);
            FileOutputStream fos = new FileOutputStream(new File(outPath));
            out.writeContainer(fos.getChannel());
            fos.close();

        }catch(Exception e){
            e.printStackTrace();
        }

    }




}

二、视频剪切工具类

/**
 * 视频剪切
 * Created by lxy on 17-4-21.
 */

public class VideoClip {

    private static final String TAG = "VideoClip";
    private String filePath;//视频路径
    private String workingPath;//输出路径
    private String outName;//输出文件名
    private double startTime;//剪切起始时间
    private double endTime;//剪切结束时间


    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }


    public void setWorkingPath(String workingPath) {
        this.workingPath = workingPath;
    }

    public void setOutName(String outName) {
        this.outName = outName;
    }

    public void setEndTime(double endTime) {
        this.endTime = endTime / 1000;
    }

    public void setStartTime(double startTime) {
        this.startTime = startTime / 1000;
    }

    public synchronized void clip() {
        try {
            //将要剪辑的视频文件
            Movie movie = MovieCreator.build(filePath);

            List<Track> tracks = movie.getTracks();
            movie.setTracks(new LinkedList<Track>());
            //时间是否修正
            boolean timeCorrected = false;

            //计算并换算剪切时间
            for (Track track : tracks) {
                if (track.getSyncSamples() != null
                        && track.getSyncSamples().length > 0) {
                    if (timeCorrected) {
                        throw new RuntimeException(
                                "The startTime has already been corrected by another track with SyncSample. Not Supported.");
                    }
                    //true,false表示短截取;false,true表示长截取
                    startTime = VideoHelper.correctTimeToSyncSample(track, startTime, false);//修正后的开始时间
                    endTime = VideoHelper.correctTimeToSyncSample(track, endTime, true);     //修正后的结束时间
                    timeCorrected = true;
                }
            }
            //根据换算到的开始时间和结束时间来截取视频
            for (Track track : tracks) {
                long currentSample = 0; //视频截取到的当前的位置的时间
                double currentTime = 0; //视频的时间长度
                double lastTime = -1;    //上次截取到的最后的时间
                long startSample1 = -1;  //截取开始的时间
                long endSample1 = -1;    //截取结束的时间

                //设置开始剪辑的时间和结束剪辑的时间  避免超出视频总长
                for (int i = 0; i < track.getSampleDurations().length; i++) {
                    long delta = track.getSampleDurations()[i];
                    if (currentTime > lastTime && currentTime <= startTime) {
                        startSample1 = currentSample;//编辑开始的时间
                    }
                    if (currentTime > lastTime && currentTime <= endTime) {
                        endSample1 = currentSample;  //编辑结束的时间
                    }
                    lastTime = currentTime;          //上次截取到的时间(避免在视频最后位置了还在增加编辑结束的时间)
                    currentTime += (double) delta
                            / (double) track.getTrackMetaData().getTimescale();//视频的时间长度
                    currentSample++;                 //当前位置+1
                }
                movie.addTrack(new CroppedTrack(track, startSample1, endSample1));// 创建一个新的视频文件
            }

            //合成视频mp4
            Container out = new DefaultMp4Builder().build(movie);
            File storagePath = new File(workingPath);
            storagePath.mkdirs();
            FileOutputStream fos = new FileOutputStream(new File(storagePath, outName));
            FileChannel fco = fos.getChannel();
            out.writeContainer(fco);
            //关闭流
            fco.close();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}


/**
 * Created by lxy on 17-4-17.
 */

public class VideoHelper {

    //换算剪切时间
    public static double correctTimeToSyncSample(Track track, double cutHere,
                                                 boolean next) {
        double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
        long currentSample = 0;
        double currentTime = 0;
        for (int i = 0; i < track.getSampleDurations().length; i++) {
            long delta = track.getSampleDurations()[i];
            if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
                timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(),
                        currentSample + 1)] = currentTime;
            }
            currentTime += (double) delta
                    / (double) track.getTrackMetaData().getTimescale();
            currentSample++;
        }
        double previous = 0;
        for (double timeOfSyncSample : timeOfSyncSamples) {
            if (timeOfSyncSample > cutHere) {
                if (next) {
                    return timeOfSyncSample;
                } else {
                    return previous;
                }
            }
            previous = timeOfSyncSample;
        }
        return timeOfSyncSamples[timeOfSyncSamples.length - 1];
    }

}

三、实现录音功能的类

/**
 * 实现录音功能的类
 */
public class MediaRecorderUtil {


    private MediaRecorder mediarecorder=null;//录音功能公共类
    private static final String recordFilePath = FileUtil.getRecorderDir()+"/recorder.aac";
    private RecorderThread recorderThread=null;
    private static final String UPDATE_TAG="update_tag";


    /**
     * 开启录音功能
     */
    public void recorderStart(){

        //启动midiarecoder录音
        recorderThread=new RecorderThread();
        recorderThread.start();

    }


    /**
     * 停止录音,并保存录音
     */
    public void recorderSave(){

        if(mediarecorder!=null){
            mediarecorder.stop();
            mediarecorder.release();
            mediarecorder=null;

            if(recorderThread!=null){
                recorderThread=null;
            }
            Log.e(UPDATE_TAG,"Thread stop voice and save...");
        }

    }


    //开启录音功能线程
    class RecorderThread extends Thread{

        @Override
        public void run() {
            super.run();
            try {

                //创建保存录音文件
                File file=new File(recordFilePath);

                if (mediarecorder==null) {
                    mediarecorder=new MediaRecorder();//实例化录音文件对象
                }
                mediarecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置获取录音文件来源
                mediarecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);//设置录音文件输出格式
                mediarecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//设置录音文件的编码格式
                mediarecorder.setOutputFile(file.getAbsolutePath());//设置录音文件的输出路径

                mediarecorder.prepare();//录音文件的准备工作
                mediarecorder.start();//录音开始

                Log.e(UPDATE_TAG,"Thread start voice...");

                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

        }
    }

}

我项目主要用到的功能实现方法

一、视频剪切主要实现方法

/**
 * 视频剪切
 * @param startTime 视频剪切的开始时间
 * @param endTime 视频剪切的结束时间
 * @param FilePath 被剪切视频的路径
 * @param WorkingPath 剪切成功保存的视频路径
 * @param fileName 剪切成功保存的文件名
 */
private synchronized void cutMp4(final long startTime, final long endTime, final String FilePath, final String WorkingPath, final String fileName){
    new Thread(new Runnable() {
        @Override
        public void run() {

            try{
                //视频剪切
                VideoClip videoClip= new VideoClip();//实例化VideoClip                videoClip.setFilePath(FilePath);//设置被编辑视频的文件路径  FileUtil.getMediaDir()+"/test/laoma3.mp4"
         videoClip.setWorkingPath(WorkingPath);//设置被编辑的视频输出路径  FileUtil.getMediaDir()
         videoClip.setStartTime(startTime);//设置剪辑开始的时间
                videoClip.setEndTime(endTime);//设置剪辑结束的时间
                videoClip.setOutName(fileName);//设置输出的文件名称
                videoClip.clip();//调用剪辑并保存视频文件方法(建议作为点击保存时的操作并加入等待对话框)

            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }).start();
}

二、开启录音功能

MediaRecorderUtil mrUtil=new MediaRecorderUtil();
mrUtil.recorderStart();

三、音视频合成

//aacPath录音的文件路径、mp4Path原视频路径、outPath合成之后输出的视频文件路径

boolean flag=Mp4ParseUtil.muxAacMp4(aacPath,mp4Path,outPath);

四、视频拼接

//mMp4List视频拼接的路径集合、outPath拼接成功的视频输出路径

Mp4ParseUtil.appendMp4List(mMp4List,outPath);

大致就这样了





我不是个呆若木鸡的小小英

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值