利用ffmpeg工具将mp4文件切片,之后用m3u8格式播放

ffmpeg可以做很多事,今天只用它来做切片。切片就是将一个大文件切成多个小文件然后再播放,小文件下载速度快,所以播放的时候就会流畅很多。
将文件变成m3u8格式后,写代码的时候src直接写m3u8的文件名即可<source id="videoUrl" src="test.m3u8" type="" />,播放器会关联切片后的所有文件使之成为一个完整的文件。

1、转换类

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Kyle
 * @version 1.0 2019年4月3日 NEW
 * @function 前提需要本机有ffmpeg,并且配置好环境变量,即把E:\ffmpeg-20180430-0807a77-win64-static\
 *           bin加入到path中
 */
public class Mp4ConvertM3u8Util {
    private static String inputPath = "";  // 完整的路径和文件名
    private static String outputPath = ""; // 完整的路径和文件名

    
    public static void main(String args[]) throws IOException {
        // 转换过程需要两步
        convert("D:\\MyEclipseProject\\Test\\input\\test2.mp4", "D:\\MyEclipseProject\\Test\\output\\test.ts");
    }

    /**
     * 转换
     * @param inputPath 原文件存在的完整路径和文件名
     * @param outputPath 目标文件存在的完整路径和文件名
     * @return
     */    
    private static boolean convert(String inputPath, String outputPath) {
        Mp4ConvertM3u8Util.inputPath = inputPath;
        Mp4ConvertM3u8Util.outputPath = outputPath;
        
        boolean status = false;

        status = mp4ConvertTs(); // mp4转成ts格式

        if (status) {
            status = tsConvertM3u8();
            
            if(status){
                
                // 删除.ts文件, 因为我们直接播放m3u8就够了
                File file = new File(outputPath);
                if(file.exists()){
                    file.delete();
                }
            }
        }
        
        return status;
    }


    // 将mp4转成ts格式
    private static boolean mp4ConvertTs() {

        List<String> command = new ArrayList<String>();

        // 先用ffmpeg把file.mp4文件转换为file.ts文件:
        // ffmpeg -y -i <in file> -vcodec copy -acodec copy -vbsf
        // h264_mp4toannexb <output file>(这个为转换命令)

        // 以下命令可以按照自己的需要改动
        command.add("ffmpeg");
        command.add("-y"); // 不用提示直接覆盖
        command.add("-i");
        command.add(inputPath);
        command.add("-vcodec");
        command.add("copy");
        command.add("-acodec");
        command.add("copy");
        command.add("-vbsf");
        command.add("h264_mp4toannexb");
        command.add(outputPath);

        try {

            // redirectErrorStream(true)合并错误和标准输出,这样所有的信息通过getInputStream()来获取
            Process tsProcess = new ProcessBuilder(command).redirectErrorStream(true).start();

            // 目前必须加这句话,如果不加子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁
            new SkipStream(tsProcess.getInputStream()).start();

            // 判断子进程是否活着jdk1.8加的此方法,其实不加这个判断也一样可以执行
            if (tsProcess.isAlive()) {
                tsProcess.waitFor();
            }
            
            // 销毁子进程
            tsProcess.destroy();

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // 将ts转成m3u8
    private static boolean tsConvertM3u8() {
        
        // ffmpeg -i abc.ts -c copy -map 0 -f segment -segment_list playlist.m3u8 -segment_time 5 abc%03d.ts
        
        // 以下命令可以按照自己的需要改动
        List<String> command = new ArrayList<String>();
        command.add("ffmpeg");
        command.add("-i");
        command.add(outputPath);
        command.add("-c"); // -c和-codec 一样,-c是简写
        command.add("copy");
        command.add("-map"); // -map 0 把第一个文件的所有流都复制到输出流中
        command.add("0");
        command.add("-f"); //  -f segment -segment_list 分片列表
        command.add("segment");
        command.add("-segment_list"); 
        
        // 去掉.ts扩展名
        outputPath = outputPath.replace(".ts", "");
        
        // 现在m3u8和切片后的文件名都与.ts的文件名一致
        command.add(outputPath + ".m3u8");
        command.add("-segment_time"); // 5秒一个切片
        command.add("5");
        command.add(outputPath + "%03d.ts"); // %03d代表文件名后的数字test001.ts,改成%01d就是test1.ts,其中test是上面定义的文件名

        try {

            Process videoProcess2 = new ProcessBuilder(command).redirectErrorStream(true).start();

            // 目前必须加这句话,如果不加子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。
            new SkipStream(videoProcess2.getInputStream()).start();

            if (videoProcess2.isAlive()) {
                videoProcess2.waitFor();
            }
            videoProcess2.destroy();
            
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

// 只是为了不死锁,不干其它的事情,只是跳过流
class SkipStream extends Thread {
    InputStream input = null;

    public SkipStream(InputStream input) {
        this.input = input;
    }

    public void run() {
        try {
            String line = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(input));
            while ((line = in.readLine()) != null) {
                 System.out.println("output: " + line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


2、播放页代码片段,以下代码既可以播放m3u8也可以播放mp4,代码中做了判断,如果在手机上播放直接用h5的播放标签就可以不需要下面代码,要是在PC端播放需要引入播放m3u8的videojs-contrib-hls.min.js,我还引入了弹出模态框的bootstrap.min.js
<%@ page language="java" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>

<link href="/css/video/video-js.min.css" rel="stylesheet">
<script src="/js/video/video.min.js"></script>
<script src="/js/video/videojs-contrib-hls.min.js"></script>

<div id="myModal" class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static">
    <div class="modal-dialog" style="z-index:2041">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal"
                    aria-hidden="true">&times;</button>
                <h4 class="modal-title" id="myModalLabel">视频播放</h4>
            </div>
            <div class="modal-body" id="myvideo">
                <!--  
                <video id="media" class="video-js vjs-default-skin" controls width="570" height="350" >                    
                    <source id="videoUrl" src="" type="" />
                </video>    
                -->
            </div>
        </div>
    </div>
</div>

<script language="javascript">
    var player;

    $(function() {
        $('#myModal').on('hide.bs.modal',
            function() {
                
                // 关闭的时候销毁video
                player.dispose();
            })
    });

    function modal(videoSrc) {
        $('#myModal').modal('show');

        // 每次都创建一个播放器
        var container = jQuery("#myvideo");
        var content = '';
        
        // 让播放按钮居中显示,默认在左上角vjs-big-play-centered
        content += '<video id="media" class="video-js vjs-default-skin vjs-big-play-centered" controls width="570" height="350" >';
        content += '    <source id="videoUrl" src="" type="" />';
        content += '</video>';
        container.append(content);

        var ext = videoSrc.substring(videoSrc.lastIndexOf("."));
        if (ext == ".m3u8") {
            document.getElementById("videoUrl").type = "application/x-mpegURL";
        } else {
            document.getElementById("videoUrl").type = "video/mp4";
        }

        document.getElementById("videoUrl").src = videoSrc;
        document.getElementById("media").style.width = "570px";
        document.getElementById("media").style.height = "350px";

        // 加载视频
        player = videojs("media");
        player.play();
    }
</script>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值