使用ffmpeg对视频指定时间点加入指定音频

前言

在工作之中,遇到这样的一个场景,在录制教学视频(只有系统声音)时,由于其他原因无法录制音频,后期需要进行视频音频的合成,当时的想法是调用各大厂的语音合成接口进行文字转音频的合成。音频有了,接下来就是把音频以及在视频中的时间点发给视频剪辑的同事,一个一个音频进行加入,最后发现这种重复的工作效率很低,就直接使用ffmpeg进行音视频处理,要求:视频按照指定时间点插入音频,添加片头片尾,添加背景音乐

一、调用接口语音合成生成音频文件

首先通过excel将所有的要合成的文本以及其对应的时间点读取,我的excel文件类似这样的。

使用ffmpeg对视频指定时间点加入指定音频_第1张图片使用POI将excel中的数据读取

 public HashMap<String,List<String>> readXls(String path) throws Exception {
        InputStream is = new FileInputStream(path);
        // HSSFWorkbook 标识整个excel
        HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is);
        HashMap<String,List<String>> result = new HashMap<String, List<String>>();
        int size = hssfWorkbook.getNumberOfSheets();
        // 循环每一页,并处理当前循环页
            HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(0);
        List<String> rowList = new ArrayList<String>();
        List<String> rowList2 = new ArrayList<String>();
        List<String> rowList3 = new ArrayList<String>();
            // 处理当前页,循环读取每一行
            for (int rowNum = 1; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
                // HSSFRow表示行
                HSSFRow hssfRow = hssfSheet.getRow(rowNum);
                int minColIx = hssfRow.getFirstCellNum();
                int maxColIx = hssfRow.getLastCellNum();

                // 遍历改行,获取处理每个cell元素
                HSSFCell cell = hssfRow.getCell(0);
                if (cell != null) {
                    rowList.add(getStringVal(cell));
                }

                HSSFCell cell2 = hssfRow.getCell(1);
                if (cell2 != null) {
                    rowList2.add(getStringVal(cell2));
                }

                HSSFCell cell3 = hssfRow.getCell(2);
                if (cell3 != null) {
                    rowList3.add(getStringVal(cell3));
                }

            }
        result.put("time",rowList);
        result.put("text",rowList2);
        result.put("name",rowList3);
        return result;
    }

在读取数据后,使用阿里语音合成接口将这些文本,转换为音频文件,并且以时间点命名;

/**
     * 通过阿里巴巴语音合成进行单线程语音合成
     * @param list_time
     * @param list_text
     * @param list_name
     * @throws InterruptedException
     */
    public static void buildWavByAlibaba(List<String> list_time,List<String> list_text,List<String> list_name,String title) throws InterruptedException {
        String appKey = "";		//在阿里巴巴控制台中输入自己的appKey
        String id = "";			//自己的id
        String secret = "";		//自己的秘钥
        String url = ""; // 默认即可,默认值:wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1
        AlibabaTts alibabaTts = new AlibabaTts(appKey, id, secret, url);
        for(int i=0;i<list_time.size();i++){
            alibabaTts.process(list_name.get(i).toString(),list_time.get(i).toString(),list_text.get(i).toString());
            Thread.sleep(1000);
        }
        alibabaTts.process(list_name.get(0).toString(),"title-",title);
        alibabaTts.shutdown();
    }

上面的是单线程生成音频文件,当然也可以多线程,只是阿里语音合成非付费版的最高并发只有2,多线程会出现合成失败的情况;

二、使用ffmpeg对视频以及音频进行处理

首先需要安装ffmpeg,安装过程可自行百度。现在我们的需求就是根据第一步获取的excel中的数据以及合成的音频文件对视频进行处理,同时加上片头片尾;

1、将音频文件按照指定的时间点插入到视频中

在进行插入之前,我先将所有的音频文件进行了音量的增强;

//	传入的是音频文件存放的绝对路径的集合
 public static void addVoiceToWav(List<String> paths){
        for(String path:paths){
            if(path.indexOf("title")>=0){
                continue;
            }
            String cmd = "-i "+path+" -vcodec copy -af \"volume=23dB\" "+path.replaceAll("_","-");
            System.out.println(cmd);
            try {
                Runtime run = Runtime.getRuntime();
//            run.exec("cmd /k  start cmd.exe");
                Process process = run.exec("cmd /c  start D:\\ffmpeg\\bin\\ffmpeg.exe "+cmd.toString());
                int rsource = process.waitFor();
                InputStream stderr=process.getInputStream();
                InputStreamReader isr=new InputStreamReader(stderr,"GBK");
                BufferedReader br=new BufferedReader(isr);
                String line=null;
                System.out.println("-------------正在进行音频增强----------"+path);
                while((line=br.readLine())!=null){
                    System.out.println(line);
                }

                System.out.println("cmd执行结果:"+rsource);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

紧接着是将处理过的音频文件按照指定的时间点加入到视频中;其中具体的插入时间点是以音频的文件名来辨别;

/**
     * 将文件名转换为时间,毫秒为单位
     * @param path
     * @return
     */
    public static int converTime(String path){
        String fileName = path.substring(path.length()-12,path.length()-4);
        System.out.println(fileName);
        if(path.indexOf("title")>=0){
            return 0;
        }else {
            String[] times = fileName.split("-");
            int hour = Integer.parseInt(times[0]);
            int minute = Integer.parseInt(times[1]);
            int second = Integer.parseInt(times[2]);
            return (hour*60*60+minute*60+second)*1000;
        }
    }

接下来就可以进行音频和视频的合并;

public static String videoAddWav(String mp4Path, List<String> wav_list) throws UnsupportedEncodingException {
        addVoiceToWav(wav_list);
        //拼接ffmpeg的执行命令
        StringBuffer cmd = new StringBuffer(" -i "+mp4Path);
        StringBuffer cmd2 = new StringBuffer(" -filter_complex \"");
        StringBuffer cmd3 = new StringBuffer("[0]");
        for(int i=0;i<wav_list.size();i++){
            String wav = wav_list.get(i).replaceAll("_","-");
            cmd.append(" -i "+wav);
            int timeOut =  converTime(wav);
            cmd2.append("["+(i+1)+"]adelay="+timeOut+"|"+timeOut+"[aud"+(i+1)+"];");
            cmd3.append("[aud"+(i+1)+"]");
        }
        cmd3.append("amix="+(wav_list.size()+1)+"\"");
        cmd2.append(cmd3);
        cmd.append(cmd2);
        String newFile = mp4Path.substring(0,mp4Path.lastIndexOf("."))+"(finish).mp4";
        cmd.append(" -c:v copy "+newFile);
        try {
            Runtime run = Runtime.getRuntime();
//            run.exec("cmd /k  start cmd.exe");
            Process process = run.exec("cmd /c  start D:\\ffmpeg\\bin\\ffmpeg.exe "+cmd.toString());
            int rsource = process.waitFor();
            InputStream stderr=process.getInputStream();
            InputStreamReader isr=new InputStreamReader(stderr,"GBK");
            BufferedReader br=new BufferedReader(isr);
            String line=null;
            System.out.println("-------------正在进行主体视频与音频融合----------");
            while((line=br.readLine())!=null){
                System.out.println(line);
            }

            System.out.println("cmd执行结果:"+rsource);
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        for(int i=0;i<wav_list.size();i++){
            String wav = wav_list.get(i).replaceAll("_","-");
            if(new File(wav_list.get(i)).exists()){
                new File(wav_list.get(i)).delete();
            }
            if(new File(wav).exists()){
                new File(wav).delete();
            }
        }
        if(mp4Path.indexOf("temp")>=0){
            new File(mp4Path).delete();
        }
        return mp4Path.substring(0,mp4Path.lastIndexOf("."))+"(finish).mp4";
    }

现在音频已经合成到视频之中,接下来就是合成片头片尾,添加背景音乐。

2、片头片尾合并,添加背景音乐

首先片头片尾文件时制作好的,我们需要将他们转换为ts格式,可以使用ffmpeg命令:

ffmpeg -i start.mp4 -c:v libx264 -vf scale=1920:1080 -r 24 -c:a aac -ar 48000 -b:a 160k -strict experimental -f mpegts start.ts

同时需要根据此视频的对应的标题生成一张图片转换为视频,加入到片头后面(我这里的标题是excel文件的文件名)

//这里的text是标题内容,name是excel中的任务名称
public static String createImgVideo(String text,String name) throws Exception {
        String name_temp = System.currentTimeMillis()+"";
        CreateImgUtil.createImage(text, new Font("宋体", Font.BOLD, 50), new File(
                workSpace+"img\\"+name_temp+".png"), 1920, 1078);
        Runtime run = Runtime.getRuntime();
        String cmd =" -f image2 -loop 1 -i "+workSpace+"img\\"+name_temp+".png"+" -i "+workSpace+"\\backgroundWav\\1.wav   -pix_fmt yuv420p -vcodec libx264 -b:v 600k -r:v 15 -preset medium -crf 30 -s 1920x1078 -vframes 250 -r 15 -t 9 "+workSpace+"img\\"+name_temp+"temp.mp4";
        System.out.println(cmd);
        Process process = run.exec("cmd /c  start D:\\ffmpeg\\bin\\ffmpeg.exe "+cmd.toString());
        int rsource = process.waitFor();
        InputStream stderr=process.getInputStream();
        InputStreamReader isr=new InputStreamReader(stderr,"GBK");
        BufferedReader br=new BufferedReader(isr);
        String line=null;
        System.out.println("-------------正在生成由标题图片转为的视频----------");
        while((line=br.readLine())!=null){
            System.out.println(line);
        }

        System.out.println("cmd执行结果:"+rsource);
        Thread.sleep(1000);
        List<String> filePaths = new ArrayList<String>();
        filePaths.add(wavPath_base+"\\"+name+"\\title-.wav");
        new File(workSpace+"img\\"+name_temp+".png").delete();
        return VideoUtil.convertMp4ToTs(VideoAddWavUtil.videoAddWav(workSpace+"img\\"+name_temp+"temp.mp4",filePaths),workSpace);
    }

通过多次测试,将MP4视频进行合并时,由于视频的分辨率不同,播放时会出现下方有黑色区域,所有将所有的视频文件全部转换为分辨率一样的ts格式,正如上面的**VideoUtil.convertMp4ToTs()**方法,

//mp4Path--待转换的视频文件绝对路径,workSpace--自定义的工作路径
public static String convertMp4ToTs(String mp4Path,String workSpace){
        String time_now = System.currentTimeMillis()+"";
        String cmd = " -i "+mp4Path+" -c:v libx264 -vf scale=1920:1080 -r 24 -c:a aac -ar 48000 -b:a 160k -strict experimental -f mpegts "+workSpace+"ts\\"+time_now+".ts";
        Runtime run = Runtime.getRuntime();
        try {
            Process process = run.exec("cmd /c  start D:\\ffmpeg\\bin\\ffmpeg.exe "+cmd.toString());
            int rsource = process.waitFor();
            InputStream stderr=process.getInputStream();
            InputStreamReader isr=new InputStreamReader(stderr,"GBK");
            BufferedReader br=new BufferedReader(isr);
            String line=null;
            System.out.println("-------------正在进行视频转为TS格式视频流文件----------");
            while((line=br.readLine())!=null){
                //                             String encoded=new String(line.getBytes("gbk"),"GB18030");//GB2312/CP936/GB18030
                //                             System.out.println(encoded);
                System.out.println(line);
            }

            System.out.println("cmd执行结果:"+rsource);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new File(mp4Path).delete();
        return workSpace+"ts\\"+time_now+".ts";
    }

接下来就是讲片头、标题片头、主体视频、片尾进行合并,

public static String mergyTsToMp4(String startPath,String imgMp4Path,String bodyPath,String endPath,String name,String workSpace){
        String body_ts = convertMp4ToTs(bodyPath,workSpace);
        String cmd = " -i \"concat:"+startPath+"|"+imgMp4Path+"|"+body_ts+"|"+endPath+"\" -c copy -bsf:a aac_adtstoasc -movflags +faststart "+workSpace+"finish\\"+name+".mp4";
        System.out.println(cmd);
        Runtime run = Runtime.getRuntime();
        try {
            Process process = run.exec("cmd /c  start D:\\ffmpeg\\bin\\ffmpeg.exe "+cmd.toString());
            int rsource = process.waitFor();
            InputStream stderr=process.getInputStream();
            InputStreamReader isr=new InputStreamReader(stderr,"GBK");
            BufferedReader br=new BufferedReader(isr);
            String line=null;
            System.out.println("-------------正在进行4个视频片段的合并----------");
            while((line=br.readLine())!=null){
                //                             String encoded=new String(line.getBytes("gbk"),"GB18030");//GB2312/CP936/GB18030
                //                             System.out.println(encoded);
                System.out.println(line);
            }

            System.out.println("cmd执行结果:"+rsource);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new File(imgMp4Path).delete();
        new File(body_ts).delete();
        return workSpace+"finish\\"+name+".mp4";
    }

4个视频合成后,需要添加上背景音乐

public static String addBackgroundWavToMp4(String mp4Path,String backgroundWavPath){
        String cmd = " -i "+mp4Path+" -i "+backgroundWavPath+" -filter_complex amix=inputs=2:duration=first:dropout_transition=2  "+mp4Path.substring(0,mp4Path.lastIndexOf("."))+"(havedBackground).mp4";
        System.out.println(cmd);
        Runtime run = Runtime.getRuntime();
        try {
            Process process = run.exec("cmd /c  start D:\\ffmpeg\\bin\\ffmpeg.exe "+cmd.toString());
            int rsource = process.waitFor();
            InputStream stderr=process.getInputStream();
            InputStreamReader isr=new InputStreamReader(stderr,"GBK");
            BufferedReader br=new BufferedReader(isr);
            String line=null;
            System.out.println("-------------正在进行视频的背景音乐添加----------");
            while((line=br.readLine())!=null){
                //                             String encoded=new String(line.getBytes("gbk"),"GB18030");//GB2312/CP936/GB18030
                //                             System.out.println(encoded);
                System.out.println(line);
            }

            System.out.println("cmd执行结果:"+rsource);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new File(mp4Path).delete();
        return mp4Path.substring(0,mp4Path.lastIndexOf("."))+"(havedBackground).mp4";
    }

总结

在视频进行合并时,有音频的视频和无音频的视频会合并失败,所以图片转为视频时我也加入了音频;
在java调用ffmpeg时,命令中的文件名带有中文时也会报错;

你可能感兴趣的:(java,语音合成,java,ffmpeg,视频合成)