.m3u8格式HLS直播流不间断拉取切割分段转换成MP4格式

1 篇文章 0 订阅
1 篇文章 0 订阅

在原文链接代码里进行了调整:非常感谢原文作者
原文链接:利用FFmpeg将HLS直播列表.m3u8格式转为mp4保存
原文代码中已经实现[利用FFmpeg将HLS直播列表.m3u8格式转为mp4保存,在此基础上因业务需求做了以下调整:
1、24小时不间断拉取直播流并分段保存。
2、在拉取过程中将过程文件存入临时目录中。
3、在拉取过程中突然拉几个视频就停了,问题处理。
只贴一下修改的类代码:
HlsToMp4TestController

package cn.piesat.web.controller.app;



import cn.piesat.app.domain.VideoParamsVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * description:
 *
 * @author jwb
 * @date 2022/1/12
 */
@RestController
@Slf4j
@RequestMapping("/app/convert")
public class HlsToMp4TestController {
    static final String DEST_VIDEO_TYPE = ".mp4";
    static final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMddHHmmssSSS");
    //查看json文件
    @Value("${sys-config.video-path}")
    private String videoPath;



    @PostMapping("/convert_to_mp4")
    public void convertToMp4(@RequestBody VideoParamsVO dataVO) throws InterruptedException {
        String sourceVideoUrl = dataVO.getSourceVideoUrl();
        Assert.notNull(sourceVideoUrl, "视频源不能为空");

        // 将m3u8格式视频转为mp4本地文件(用于转换格式的中间文件)
        String destFileName = HlsToMp4Processor.process(sourceVideoUrl,videoPath);
        if (StringUtils.isEmpty(destFileName)) {
            log.error("操作失败");
        }

        // 推送流
        if(StringUtils.isNotEmpty(dataVO.getDestVideoPath())) {
           // Thread.sleep(20000);
            new VideoPusher().from(destFileName).to(dataVO.getDestVideoPath()  + "ceshi_" + SDF.format(new Date()) + DEST_VIDEO_TYPE).go();
            // 删除中间文件
            new File(destFileName).delete();
        }
       //递归调用,重新拉取
       convertToMp4(dataVO);
    }
}

HlsToMp4Processor

package cn.piesat.web.controller.app;

import com.github.pagehelper.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.Loader;
import org.springframework.util.StringUtils;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * description:
 * 根据hls直播地址,将hls的m3u8播放列表格转为mp4格式
 * 如果原视频不是m3u8格式则不用进行中间转换。
 *
 * javacv的采集器FFmpegFrameGrabber并不支持直接读取hls的m3u8格式文件,
 * 所以没法直接用采集、录制的方式进行m3u8到mp4的转换。
 * 这里的实现过程是直接操作ffmpeg,
 *
 * todo:使用ffmepg用于格式转换速度较慢
 *
 * @author liuxingwu
 * @date 2022/1/9
 */
@Slf4j
public class HlsToMp4Processor{
    static final String DEST_VIDEO_TYPE = ".mp4";
    static final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMddHHmmssSSS");
    static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
    /**
     * 方法入口
     *
     * @param sourceVideoPath   视频源路径
     * @return
     */
    public static String process(String sourceVideoPath,String tempUrl) {
        log.info("开始进行格式转换");
        if (!checkContentType(sourceVideoPath)) {
            log.info("请输入.m3u8格式的文件");
            return "";
        }
        // 获取文件名
        String destVideoPath = tempUrl
                + "ceshi_" + SDF.format(new Date()) + DEST_VIDEO_TYPE;
        // 执行转换逻辑
        return processToMp4(sourceVideoPath, destVideoPath) ? destVideoPath : "";
    }

    private static String getFileName(String sourceVideoPath) {
        return sourceVideoPath.substring(sourceVideoPath.contains("/") ?
                        sourceVideoPath.lastIndexOf("/") + 1 : sourceVideoPath.lastIndexOf("\\") + 1,
                sourceVideoPath.lastIndexOf("."));
    }

    /**
     * 执行转换逻辑
     * @author saodisheng_liuxingwu
     * @modifyDate 2022/1/9
     */
    private static boolean processToMp4(String sourceVideoPath, String destVideoPath) {
        long startTime = System.currentTimeMillis();

        List<String> command = new ArrayList<String>();
        //获取JavaCV中的ffmpeg本地库的调用路径
        String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
        command.add(ffmpeg);
        // 设置支持的网络协议
        command.add("-protocol_whitelist");
        command.add("concat,file,http,https,tcp,tls,crypto");
        command.add("-i");
        command.add(sourceVideoPath);
        //拉取60秒
        command.add("-t");
        command.add("60");
        //跳过40秒,解决拉取几个视频突然停止问题
        command.add("-ss");
        command.add("40");
        command.add(destVideoPath);
        System.out.print("命令:"+command);
        try {
            Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();
            fixedThreadPool.execute(new ReadStreamInfo(videoProcess.getErrorStream()));
            fixedThreadPool.execute(new ReadStreamInfo(videoProcess.getInputStream()));
            videoProcess.waitFor();
            //这里会生成一个临时的中间文件,转换完成会删除掉
            log.info("中间转换已完成,生成文件:" + destVideoPath);
          //  processToMp4(sourceVideoPath,destVideoPath);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            long endTime = System.currentTimeMillis();
            log.info("用时:" + (int)((endTime - startTime) / 1000) + "秒");
        }
    }

    /**
     * 检验是否为m3u8文件
     * @author saodisheng_liuxingwu
     * @modifyDate 2022/1/9
     */
    private static boolean checkContentType(String filePath) {
        if (StringUtil.isEmpty(filePath)) {
            return false;
        }
        String type = filePath.substring(filePath.lastIndexOf(".") + 1, filePath.length()).toLowerCase();
        return "m3u8".equals(type);
    }
}

创作不易,大家帮忙关注一下公众号,感谢。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值