生成m3u8视频
maven依赖
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.4-1.5.6</version>
</dependency>
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameRecorder;
import java.io.*;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* javacv ffmpeg 工具类
*/
public class FfmpegUtil {
//
public static void main(String[] args) throws Exception {
String from = "C:/videos/video/8f1a0304-a874-4619-8598-4c65c0539b6a";
mp4ToM3u8(from, "8f1a0304-a874-4619-8598-4c65c0539b6a.mp4");
}
/**
* 通过Java代码生成m3u8分片视频
* 这是方法1,使用这种方法需要引入前面的pom依赖,使用命令分方式则不需要
* @param sourceDir
* @param fileName
*/
public static void mp4ToM3u8(String sourceDir, String fileName) {
try {
avutil.av_log_set_level(avutil.AV_LOG_INFO);
FFmpegLogCallback.set();
boolean isStart = true;// 该变量建议设置为全局控制变量,用于控制录制结束
//加载文件
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(sourceDir + "/" + fileName + ".mp4");
grabber.start();
File tempFile3 = new File(sourceDir + "/m3u8", "output.m3u8");
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempFile3, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
//格式方式
recorder.setFormat("hls");
//关于hls_wrap的说明,hls_wrap表示重复覆盖之前ts切片,这是一个过时配置,ffmpeg官方推荐使用hls_list_size 和hls_flags delete_segments代替hls_wrap
//设置单个ts切片的时间长度(以秒为单位)。默认值为2秒
/* recorder.setOption("hls_time", "60");
//不根据gop间隔进行切片,强制使用hls_time时间进行切割ts分片
recorder.setOption("hls_flags", "split_by_time");
//设置播放列表条目的最大数量。如果设置为0,则列表文件将包含所有片段,默认值为5
// 当切片的时间不受控制时,切片数量太小,就会有卡顿的现象
recorder.setOption("hls_list_size", "0");
//自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保留hls_list_size个数量的切片
recorder.setOption("hls_flags", "delete_segments");
//ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除
recorder.setOption("hls_delete_threshold", "1");
recorder.setOption("hls_segment_type", "mpegts");
recorder.setOption("hls_segment_filename", sourceDir + File.separator + "m3u8" + "-%5d.ts");
recorder.setVideoOption("tune", "fastdecode");
// 快速
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoOption("threads", "12");
recorder.setVideoOption("vsync", "2");
recorder.setFrameRate(grabber.getFrameRate());// 设置帧率
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);*/
recorder.setOption("start_number", "0");
recorder.setOption("hls_time", "50");
recorder.setOption("hls_list_size", "0");
recorder.setOption("hls_segment_filename", sourceDir + "/m3u8" + File.separator + "m3u8" + "-%5d.ts");
// ffmpeg -i 8f1a0304-a874-4619-8598-4c65c0539b6a.mp4 -codec: copy -start_number 0 -hls_time 50 -hls_list_size 0 -f hls output.m3u8
recorder.start(grabber.getFormatContext());
AVPacket packet;
while ((packet = grabber.grabPacket()) != null) {
try {
recorder.recordPacket(packet);
} catch (FrameRecorder.Exception e) {
}
}
recorder.setTimestamp(grabber.getTimestamp());
recorder.stop();
recorder.release();
grabber.stop();
grabber.release();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void mp4ToM3u8(String source, String to, String fileName) {
try {
avutil.av_log_set_level(avutil.AV_LOG_INFO);
FFmpegLogCallback.set();
boolean isStart = true;// 该变量建议设置为全局控制变量,用于控制录制结束
//加载文件
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(source);
grabber.start();
File tempFile3 = new File(to, fileName + ".m3u8");
System.out.println("--------------------");
System.out.println(tempFile3);
System.out.println("--------------------===========");
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempFile3, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
//格式方式
recorder.setFormat("hls");
//关于hls_wrap的说明,hls_wrap表示重复覆盖之前ts切片,这是一个过时配置,ffmpeg官方推荐使用hls_list_size 和hls_flags delete_segments代替hls_wrap
//设置单个ts切片的时间长度(以秒为单位)。默认值为2秒
recorder.setOption("hls_time", "60");
//不根据gop间隔进行切片,强制使用hls_time时间进行切割ts分片
recorder.setOption("hls_flags", "split_by_time");
//设置播放列表条目的最大数量。如果设置为0,则列表文件将包含所有片段,默认值为5
// 当切片的时间不受控制时,切片数量太小,就会有卡顿的现象
recorder.setOption("hls_list_size", "0");
//自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保留hls_list_size个数量的切片
recorder.setOption("hls_flags", "delete_segments");
//ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除
recorder.setOption("hls_delete_threshold", "1");
/*hls的切片类型:
* 'mpegts':以MPEG-2传输流格式输出ts切片文件,可以与所有HLS版本兼容。
* 'fmp4':以Fragmented MP4(简称:fmp4)格式输出切片文件,类似于MPEG-DASH,fmp4文件可用于HLS version 7和更高版本。
*/
recorder.setOption("hls_segment_type", "mpegts");
//指定ts切片生成名称规则,按数字序号生成切片,例如'file%03d.ts',就会生成file000.ts,file001.ts,file002.ts等切片文件
//recorder.setOption("hls_segment_filename", toFilePath + "-%03d.ts");
recorder.setOption("hls_segment_filename", to + File.separator + fileName + "-%5d.ts");
recorder.setVideoOption("tune", "fastdecode");
// 快速
recorder.setVideoOption("preset", "ultrafast");
// recorder.setVideoOption("crf", "26");
recorder.setVideoOption("threads", "12");
recorder.setVideoOption("vsync", "2");
recorder.setFrameRate(grabber.getFrameRate());// 设置帧率
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.start(grabber.getFormatContext());
AVPacket packet;
while ((packet = grabber.grabPacket()) != null) {
try {
recorder.recordPacket(packet);
} catch (FrameRecorder.Exception e) {
}
}
recorder.setTimestamp(grabber.getTimestamp());
recorder.stop();
recorder.release();
grabber.stop();
grabber.release();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 通过命令的方式生成分片视频
* 使用这种方式不需要引入pom依赖
* @param sourcePath 要分割的视频完整路径(包含文件名)
* 例:C:/video/test/liuLangDiQiu.mp4
* @param targetPath 生成m3u8的视频地址(包含文件名)
* 例:C:/video/test/m3u8/output.m3u8
* 参考文档 https://blog.youkuaiyun.com/hantanxin/article/details/103957871/
*/
public static void mp4ToM3u8ByCommand(String sourcePath, String targetPath) {
List<String> command = new ArrayList<>();
command.add("ffmpeg");
command.add("-i");
command.add(sourcePath);
command.add("-codec:");
command.add("copy");
command.add("-start_number");
command.add("0");
command.add("-hls_time");
command.add("300"); //每个切片视频的时长(秒) 这里设置一个视频切成五分钟一小片,如果视频小于五分钟的话,则相当于不切分,只有一片
command.add("-hls_list_size");
command.add("0");
command.add("-f");
command.add("hls");
command.add(targetPath);
StringBuilder stringBuilder = new StringBuilder();
ProcessBuilder builder = new ProcessBuilder(command);
try {
Process process = builder.start();
final InputStream is1 = process.getInputStream();
new Thread(new Runnable() {
public void run() {
BufferedReader bufferedReader = null;
String line = null;
try {
bufferedReader = new BufferedReader(
new InputStreamReader(is1, "GBK"));
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + "\n");
}
is1.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start(); // 启动单独的线程来清空p.getInputStream()的缓冲区
InputStream is2 = process.getErrorStream();
BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
StringBuilder buf = new StringBuilder(); // 保存输出结果流
String line2 = null;
while ((line2 = br2.readLine()) != null) buf.append(line2); //
System.out.println("----res:----" + stringBuilder + "&" + buf);
System.out.println(stringBuilder + "&" + buf);
System.out.println("ok");
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.toString());
}
}
}
nginx配置
server {
listen 8701;
server_name localhost; # 替换为您的域名或IP地址
location / {
root C:/zh/hls; # 替换为您的视频文件所在的目录,hls目录下就是 *.m3u8所在的地址,如果不行,前端报跨域,则可以给m3u8文件再套一层文件夹
add_header Cache-Control no-cache;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
}
}