Java转成m3u8,hls格式

时间:2024-01-26 22:52:09

Java转成m3u8,hls格式

需求分析

大致思路

  1. 循环文件夹下面所有文件
  2. 判断当前文件是否是视频文件,如果是视频文件先转为ts文件
    • 因为听别人说先转成ts之后再切片会快很多
  3. 转成ts文件,并为这些文件单独生成一个目录,如果目录不存在则新建一个目录
    • 执行转换命令—转成ts文件
  4. 执行完成后,再将ts文件转成m3u8文件

详细流程

  1. 判断当前文件夹是否是一个目录
  2. 如果是目录就遍历下面的文件包括所以的文件。
  3. 循环这些子文件,如果当前的文件是文件并且是视频文件(写一个判断函数,因为格式不同)
  4. 循环完成后将这些视频转成ts,并为这些文件生成单独目录,转成ts文件命名为output.ts
    • 需要为这些文件单独写一个执行命令函数(封装)
  5. 转成ts后再将这些视频转成m3u8

方法实现

判断是否为文件函数

在类中定义正则表达式,以方便修改。

函数中使用文件名后缀方式判断当前文件是否是符合要求的视频文件。

// 判断是否是视频文件正则
private static String isVideoFileRegex = ".*\\.(mp4|avi|mkv)$";

/**
 * 判断是否符合想要的格式
 * mp4|avi|mkv
 * 通过文件后缀名哦安短
 *
 * @param fileName 文件名称
 * @return 布尔值
 */
private static boolean isVideoFile(String fileName) {
    return fileName.matches(isVideoFileRegex);
}

执行命令函数

因为在使用过程中需要使用两次命令行的操作,使用Java调起命令行,这是可以封装成单独的函数,使用时只需要将命令行传入即可。

命令行解释

创建了一个 ProcessBuilder 对象,并使用给定的命令字符串初始化它。ProcessBuilder 类用于创建操作系统进程,并提供一种从 Java 程序中启动外部命令的方式。

command.split(" ") 将输入的命令字符串分割成一个字符串数组,每个数组元素都是一个命令或参数。然后,这个数组被用来初始化 ProcessBuilder 对象,以便在后续步骤中启动一个新的进程来执行这些命令。

processBuilder.redirectErrorStream(true) 设置了当启动的进程产生错误流时,将错误流合并到标准输出流中。这意味着在执行命令时,任何由该命令生成的错误输出将会与标准输出一起被捕获,并可以通过 Java 程序进行处理。

ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
processBuilder.redirectErrorStream(true);
  1. processBuilder.start() 启动了之前创建的 ProcessBuilder 对象,执行之前设置的命令。
  2. BufferedReader 对象将其连接到外部进程的标准输出流,程序可以读取外部进程的输出。这里使用了 InputStreamReader 将字节流转换为字符流,然后通过 BufferedReader 逐行读取外部进程的输出。
  3. 在一个 while 循环中,程序持续读取外部进程的输出,直到输出结束。每读取一行输出,该行内容被打印到控制台上。
  4. 之后,通过调用 process.waitFor(),程序会等待外部进程执行完毕。这会导致当前的线程暂停,直到外部进程执行完成。
try {
    Process process = processBuilder.start();
    // 将ffmpeg执行内容输出在控制台中
    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
    // 等待转换完成
    process.waitFor();
} catch (IOException | InterruptedException e) {
    throw new RuntimeException(e);
}
完整代码
// 输入文件夹
private static String inputDirectory = "C:\\Users\\13199\\Downloads";
// 输出m3u8文件
private static String outputM3u8FileName = "index.m3u8";
// 判断是否是视频文件正则
private static String isVideoFileRegex = ".*\\.(mp4|avi|mkv)$";
// 选择切片时间
private static Integer HlsTime = 1;

/**
 * 执行将文件转成m3u8 hls格式
 *
 * @param command Ffmpeg 命令
 */
private static void executeFfmpegCommand(String command) {
    ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
    processBuilder.redirectErrorStream(true);
    try {
        Process process = processBuilder.start();
        // 将ffmpeg执行内容输出在控制台中
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
        // 等待转换完成
        process.waitFor();
    } catch (IOException | InterruptedException e) {
        throw new RuntimeException(e);
    }
}

转成ts

  1. 根据输入文件路径 inputFilePath 确定了输出目录 outputDirectory。使用正则表达式 \\.[^.]+$ 来匹配文件路径中的文件扩展名,并将其替换为空字符串,从而得到输出目录的路径。
  2. 创建了一个 File 对象来代表输出目录,并检查该目录是否存在。如果输出目录不存在,用 mkdirs() 方法来创建输出目录。
  3. 构建了一个输出文件的路径 outputFilePath,这里假设输出文件的名称为 “output.ts”,并将其放在之前确定的输出目录中。
  4. FFmpeg 命令字符串 command,该命令使用输入文件路径来进行转换,具体命令包括了输入文件、编解码器选项、关键帧选项等参数。
  5. executeFfmpegCommand 将构建的 FFmpeg 命令字符串作为参数传递给该方法,以执行 FFmpeg 命令。
  6. 最后,代码调用了一个名为 convertToM3U8 的方法,该方法可能用于将生成的视频文件转换为 M3U8 格式
// 输入文件夹
private static String inputDirectory = "C:\\Users\\13199\\Downloads";
// 输出m3u8文件
private static String outputM3u8FileName = "index.m3u8";
// 判断是否是视频文件正则
private static String isVideoFileRegex = ".*\\.(mp4|avi|mkv)$";
// 选择切片时间
private static Integer HlsTime = 1;

/**
 * 如果文件名 后缀符合将文件先转成ts类型文件
 * 有实验表名,先转成ts会快一点
 *
 * @param inputFilePath 当前文件的路径
 */
private static void convertToTs(String inputFilePath) {
    String outputDirectory = inputFilePath.replaceFirst("\\.[^.]+$", "");
    File directory = new File(outputDirectory);
    if (!directory.exists()) {
        directory.mkdirs();
    }

    String outputFilePath = outputDirectory + File.separator + "output.ts";
    String command = "ffmpeg -i " + inputFilePath + " -c copy -bsf:v h264_mp4toannexb -force_key_frames expr:gte(t,n_forced*" + HlsTime + ") -f mpegts " + outputFilePath;
    executeFfmpegCommand(command);
    convertToM3U8(outputDirectory);
}

转成m3u8

输出文件的路径 outputFilePath,这个路径是由输入目录 inputDirectory 和一个名为 outputM3u8FileName

之后将这段代码放到之前封装好的执行命令函数中executeFfmpegCommand

// 输入文件夹
private static String inputDirectory = "C:\\Users\\13199\\Downloads";
// 输出m3u8文件
private static String outputM3u8FileName = "index.m3u8";
// 判断是否是视频文件正则
private static String isVideoFileRegex = ".*\\.(mp4|avi|mkv)$";
// 选择切片时间
private static Integer HlsTime = 1;

/**
 * 将文件转成hls类型,并且m3u8命名为index
 * 切片的ts以output.ts转换
 *
 * @param inputDirectory 输入的文件夹
 */
private static void convertToM3U8(String inputDirectory) {
    // 使用FFmpeg将ts文件转换成m3u8文件
    String outputFilePath = inputDirectory + File.separator + outputM3u8FileName;
    String command = "ffmpeg -i " + inputDirectory + File.separator + "output.ts" + " -c:v libx264 -c:a aac -force_key_frames expr:gte(t,n_forced*" + HlsTime + ") -hls_time " + HlsTime + " -hls_list_size 0 -f hls " + outputFilePath;
    executeFfmpegCommand(command);
}
自定义ts输出名

需要使用-hls_segment_filename参数。这个参数可以帮助你自定义切片文件的命名规则。

如果要将输入的ts文件以1,2,3这种方式命名可以使用这个方法inputDirectory + File.separator + "%d.ts"

String segmentFilename = inputDirectory + File.separator + "%d.ts"; // 定义切片文件名规则
String command = "ffmpeg -i " + inputDirectory + File.separator + "output.ts" + " -c:v libx264 -c:a aac -force_key_frames expr:gte(t,n_forced*1) -hls_time 1 -hls_list_size 0 -f hls -hls_segment_filename " + segmentFilename + " " + outputFilePath; // 定义切片文件名规则
executeFfmpegCommand(command);

主函数

  1. 创建了一个 File 对象来代表输入目录 inputDirectory
  2. 检查输入目录是否存在,并且确保它是一个目录而不是一个文件。
  3. 如果输入目录存在且是一个目录,获取了该目录下的所有文件列表,并遍历这些文件。
  4. 在遍历文件列表时,对每个文件进行检查,确保它是一个文件并且文件扩展名表明它是一个视频文件(使用了 isVideoFile 方法进行检查)。
  5. 对于每个找到的视频文件,代码创建了一个新的线程,并在该线程中调用了 convertToTs 方法,传递视频文件的绝对路径作为参数,以启动视频文件的转换过程。
// 输入文件夹
private static String inputDirectory = "C:\\Users\\13199\\Downloads";
// 输出m3u8文件
private static String outputM3u8FileName = "index.m3u8";
// 判断是否是视频文件正则
private static String isVideoFileRegex = ".*\\.(mp4|avi|mkv)$";
// 选择切片时间
private static Integer HlsTime = 1;

/**
 * 定义目录所在位置
 * 循环遍历这个目录文件,找到子目录的视频文件
 * 判断当前的文件是否是视频文件
 * 如果是文件夹则继续遍历
 *
 * @param args main program arguments
 */
public static void main(String[] args) {
    File directory = new File(inputDirectory);
    if (directory.isDirectory()) {
        File[] files = directory.listFiles();
        assert files != null;
        for (File file : files) {
            if (file.isFile() && isVideoFile(file.getName())) {
                String inputFilePath = file.getAbsolutePath();
                new Thread(() -> convertToTs(inputFilePath)).start();
            }
        }
    }
}

常规变量

// 输入文件夹
private static String inputDirectory = "C:\\Users\\13199\\Downloads";
// 输出m3u8文件
private static String outputM3u8FileName = "index.m3u8";
// 判断是否是视频文件正则
private static String isVideoFileRegex = ".*\\.(mp4|avi|mkv)$";
// 选择切片时间
private static Integer HlsTime = 1;

完整代码

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

@Data
public class NewTest {
    // 输入文件夹
    private static String inputDirectory = "C:\\Users\\13199\\Downloads";
    // 输出m3u8文件
    private static String outputM3u8FileName = "index.m3u8";
    // 判断是否是视频文件正则
    private static String isVideoFileRegex = ".*\\.(mp4|avi|mkv)$";
    // 选择切片时间
    private static Integer HlsTime = 1;

    /**
     * 定义目录所在位置
     * 循环遍历这个目录文件,找到子目录的视频文件
     * 判断当前的文件是否是视频文件
     * 如果是文件夹则继续遍历
     *
     * @param args main program arguments
     */
    public static void main(String[] args) {
        File directory = new File(inputDirectory);
        if (directory.isDirectory()) {
            File[] files = directory.listFiles();
            assert files != null;
            for (File file : files) {
                if (file.isFile() && isVideoFile(file.getName())) {
                    String inputFilePath = file.getAbsolutePath();
                    new Thread(() -> convertToTs(inputFilePath)).start();
                }
            }
        }
    }

    /**
     * 如果文件名 后缀符合将文件先转成ts类型文件
     * 有实验表名,先转成ts会快一点
     *
     * @param inputFilePath 当前文件的路径
     */
    private static void convertToTs(String inputFilePath) {
        String outputDirectory = inputFilePath.replaceFirst("\\.[^.]+$", "");
        File directory = new File(outputDirectory);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        String outputFilePath = outputDirectory + File.separator + "output.ts";
        String command = "ffmpeg -i " + inputFilePath + " -c copy -bsf:v h264_mp4toannexb -force_key_frames expr:gte(t,n_forced*" + HlsTime + ") -f mpegts " + outputFilePath;
        executeFfmpegCommand(command);
        convertToM3U8(outputDirectory);
    }

    /**
     * 将文件转成hls类型,并且m3u8命名为index
     * 切片的ts以output.ts转换
     *
     * @param inputDirectory 输入的文件夹
     */
    private static void convertToM3U8(String inputDirectory) {
        // 使用FFmpeg将ts文件转换成m3u8文件
        String outputFilePath = inputDirectory + File.separator + outputM3u8FileName;
        String command = "ffmpeg -i " + inputDirectory + File.separator + "output.ts" + " -c:v libx264 -c:a aac -force_key_frames expr:gte(t,n_forced*" + HlsTime + ") -hls_time " + HlsTime + " -hls_list_size 0 -f hls " + outputFilePath;
        executeFfmpegCommand(command);
    }

    /**
     * 执行将文件转成m3u8 hls格式
     *
     * @param command Ffmpeg 命令
     */
    private static void executeFfmpegCommand(String command) {
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();
            // 将ffmpeg执行内容输出在控制台中
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            // 等待转换完成
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 判断是否符合想要的格式
     * mp4|avi|mkv
     * 通过文件后缀名哦安短
     *
     * @param fileName 文件名称
     * @return 布尔值
     */
    private static boolean isVideoFile(String fileName) {
        return fileName.matches(isVideoFileRegex);
    }
}