核心组件:
ffmpeg:主要的命令行工具,用于转码、转换格式等
ffprobe:用于分析多媒体文件信息的工具
ffplay:简单的媒体播放器
主要功能:
✅ 格式转换(转码)
✅ 视频裁剪、合并
✅ 调整分辨率、比特率
✅ 提取音频/视频
✅ 截图/生成缩略图
✅ 添加水印、字幕
✅ 流媒体处理
一、安装FFmpeg
Windows:
- 访问 https://ffmpeg.org/download.html
- 下载Windows版本,解压到指定目录
- 将bin目录添加到系统PATH环境变量
Linux (Ubuntu/Debian):
sudo apt update
sudo apt install ffmpeg
macOS:
brew install ffmpeg
二、项目配置
在application.properties或application.yml中配置FFmpeg路径:
# application.properties
ffmpeg.path=/usr/bin/ffmpeg # Linux/Mac
# 或
ffmpeg.path=C:\\ffmpeg\\bin\\ffmpeg.exe # Windows
三、依赖注入与异步处理
@Service
@Slf4j
public class VideoTranscodingService {
@Value("${ffmpeg.path}")
private String ffmpegPath; // 注入FFmpeg路径
@Async // 异步执行,避免阻塞请求
public void transcodeVideo(Long materialId, String inputPath) {
// 转码逻辑
}
}
核心转码方法
- ProcessBuilder
- Process
private void transcodeToResolution(String inputPath, String outputPath, String resolution) throws Exception {
List<String> command = new ArrayList<>();
command.add(ffmpegPath);
command.add("-i");
command.add(inputPath); // 输入文件
command.add("-s");
command.add(resolution); // 目标分辨率
command.add("-c:v");
command.add("libx264"); // 视频编码器
command.add("-crf");
command.add("23"); // 视频质量
command.add("-c:a");
command.add("aac"); // 音频编码器
command.add("-b:a");
command.add("128k"); // 音频比特率
command.add(outputPath); // 输出文件
ProcessBuilder builder = new ProcessBuilder(command);
Process process = builder.start();
int exitCode = process.waitFor(); // 等待转码完成
if (exitCode != 0) {
throw new RuntimeException("FFmpeg转码失败,退出码: " + exitCode);
}
}
四、控制器中上传文件视频
@RestController
@RequestMapping("/api/video")
public class VideoController {
@Autowired
private VideoTranscodingService transcodingService;
@PostMapping("/upload")
public ResponseEntity<String> uploadVideo(@RequestParam("file") MultipartFile file,
@RequestParam Long materialId) {
try {
// 1. 保存上传的文件
String uploadDir = "uploads/";
String originalFilename = file.getOriginalFilename();
String filePath = uploadDir + UUID.randomUUID() + "_" + originalFilename;
File dest = new File(filePath);
file.transferTo(dest);
// 2. 异步启动转码
transcodingService.transcodeVideo(materialId, filePath);
return ResponseEntity.ok("视频上传成功,转码中...");
} catch (Exception e) {
return ResponseEntity.status(500).body("上传失败: " + e.getMessage());
}
}
}
@GetMapping("/status/{materialId}")
public ResponseEntity<Map<String, Object>> getTranscodingStatus(@PathVariable Long materialId) {
CourseMaterial material = materialRepository.findById(materialId).orElse(null);
if (material == null) {
return ResponseEntity.notFound().build();
}
Map<String, Object> response = new HashMap<>();
response.put("status", material.getTranscodingStatus());
response.put("filePath", material.getFilePath());
response.put("duration", material.getDuration());
return ResponseEntity.ok(response);
}