在原文链接代码里进行了调整:非常感谢原文作者
原文链接:利用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);
}
}
创作不易,大家帮忙关注一下公众号,感谢。