ffmpeg-实时获取转码进度

概要

网页一般支持H264 H265的视频格式播放,所以需要对客户端上传的视频做转码处理,于是就用到了ffmpeg这个很强大的音视频编解码工具,一些较大的视频,想在页面看到实时转码进度,就要对转码进度实时获取。

整体架构流程

后端处理流程‌

  • 通过ProcessBuilder启动FFmpeg子进程
  • 实时解析FFmpeg的错误输出流获取时间戳进度
  • 使用SSE(Server-Sent Events)技术推送进度到前端

前端监控方案‌

  • 采用EventSource API建立持久连接
  • 可视化进度条展示实时转码状态
  • 支持上传中断后恢复机制

FFmpeg参数优化‌

  • 使用libx264视频编码和aac音频编码保证兼容性
  • 通过错误流(stderr)捕获进度信息而非标准输出
  • 支持自定义转码参数配置

技术名词解释

ffmpeg 简介

ffmpeg 是一个开源的多媒体框架,用于处理音视频流。它包含一系列库和命令行工具,支持编码、解码、转码、流化、过滤和播放等多种功能。核心组件包括 libavcodec(编解码库)、libavformat(格式处理库)和 libavfilter(滤镜库)等。

关键名词解释

编解码器(Codec)

用于压缩(编码)或解压缩(解码)音视频数据的算法。例如:

  • H.264:视频编码标准,广泛用于流媒体和存储。
  • AAC:音频编码标准,常见于MP4文件。
容器格式(Container)

封装音视频流和元数据的文件格式,如 MP4MKVAVI。容器决定如何存储数据,但不涉及编码方式。

复用与解复用(Mux/Demux)
  • 复用(Mux):将音视频流合并到容器中(如将H.264和AAC打包为MP4)。
  • 解复用(Demux):从容器中提取音视频流。
滤镜(Filter)

处理音视频数据的工具链,例如缩放、裁剪、降噪。ffmpeg 通过 -vf(视频滤镜)和 -af(音频滤镜)参数调用:

ffmpeg -i input.mp4 -vf "scale=640:480" output.mp4
流(Stream)

容器中的独立数据通道,如视频流、音频流或字幕流。可通过 -map 选项选择特定流。

硬件加速

利用GPU或专用芯片加速编解码,如:

  • NVENC:NVIDIA的硬件编码器。
  • VAAPI:Intel/AMD的硬件加速接口。
常用参数
  • -i:指定输入文件。
  • -c:设置编解码器(如 -c:v libx264)。
  • -preset:控制编码速度与压缩率(如 fastslow)。
典型应用场景
  • 转码视频:ffmpeg -i input.avi -c:v libx264 output.mp4
  • 提取音频:ffmpeg -i video.mp4 -q:a 0 -map a audio.mp3
  • 截取片段:ffmpeg -ss 00:01:30 -t 10 -i input.mp4 output.mp4

通过灵活组合参数,ffmpeg 可满足绝大多数音视频处理需求。

音视频编解码核心名词解释

容器格式(Container)

容器用于封装音视频流、字幕等数据,常见格式如MP4、MKV、AVI。容器不定义编码方式,仅规定数据组织方式。例如MP4支持H.264视频和AAC音频的混合存储。

编码器(Codec)

编码器实现原始数据到压缩格式的转换,分为硬件编码(如NVIDIA NVENC)和软件编码(如x264)。关键参数包括码率控制(-b:v)、帧率(-r)和GOP结构(-g)。

解码器(Decoder)

解码器执行压缩数据到原始格式的逆向过程,如H.264解码为YUV像素数据。FFmpeg自动匹配解码器,也可手动指定(如-c:v h264_cuvid调用NVIDIA硬解)。

复用(Muxing)

将编码后的音视频流合并到容器中的过程,通过-f指定输出格式。典型命令:ffmpeg -i video.h264 -i audio.aac -c copy output.mp4

解复用(Demuxing)

从容器中分离音视频流的过程,常用-map选项选择流。示例:ffmpeg -i input.mp4 -map 0:v:0 video.h264 -map 0:a:0 audio.aac

转码(Transcoding)

包含解码和重新编码的完整流程,典型场景为改变编码格式或分辨率。基本命令结构:

ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 22 -c:a aac -b:a 128k output.mp4
码率控制模式
  • CBR(固定码率):-b:v 1M -minrate 1M -maxrate 1M -bufsize 2M
  • VBR(可变码率):-qscale:v 3(MPEG-2)或-crf 23(x264/x265)
  • ABR(平均码率):-b:v 1M配合-maxrate-bufsize
关键帧(I帧)

视频压缩中独立编码的帧,通过-g设置GOP长度。例如-g 60表示每60帧一个关键帧。关键帧间隔影响视频随机访问性能和压缩效率。

像素格式

定义原始视频数据的存储方式,通过-pix_fmt指定。常见格式包括:

  • yuv420p(8-bit 4:2:0,最广泛兼容)
  • yuv422p(8-bit 4:2:2)
  • yuv444p(8-bit 4:4:4,无色彩抽样)
  • yuv420p10le(10-bit 4:2:0,HDR内容)
时间基(Timebase)

表示时间戳增量的分数单位,影响帧精准定位。典型值如1/1000(毫秒级精度)或1/90000(TS流常用)。可通过-video_track_timescale修改MP4的时间基。

硬件加速

利用GPU加速编解码流程:

  • NVENC:-c:v h264_nvenc
  • QSV:-c:v h264_qsv
  • VAAPI:-hwaccel vaapi -hwaccel_output_format vaapi
滤镜系统(Filtergraph)

通过-vf/-af应用音视频处理滤镜,如缩放和降噪:

ffmpeg -i input.mp4 -vf "scale=1280:720,tonemap=hable" -c:a copy output.mp4
场景切换检测

通过select滤镜识别关键帧位置:

ffmpeg -i input.mp4 -vf "select='gt(scene,0.4)',showinfo" -f null -

技术细节

后端实现(SpringBoot)

pom.xml


<dependency>
    <groupId>net.bramp.ffmpeg</groupId>
    <artifactId>ffmpeg</artifactId>
    <version>0.7.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

FFmpegService.java


@Service
public class FFmpegService {
    @Value("${ffmpeg.path}")
    private String ffmpegPath;

    public Process executeCommand(List<String> command) throws IOException {
        ProcessBuilder builder = new ProcessBuilder(command);
        return builder.start();
    }

    public double parseProgress(String line) {
        // 解析FFmpeg输出的时间进度
        Pattern pattern = Pattern.compile("time=(\\d+):(\\d+):(\\d+)\\.\\d+");
        Matcher matcher = pattern.matcher(line);
        if(matcher.find()) {
            int hours = Integer.parseInt(matcher.group(1));
            int minutes = Integer.parseInt(matcher.group(2));
            int seconds = Integer.parseInt(matcher.group(3));
            return hours * 3600 + minutes * 60 + seconds;
        }
        return 0;
    }
}

VideoController.java


@RestController
@RequestMapping("/api/video")
public class VideoController {
    @Autowired
    private FFmpegService ffmpegService;

    @PostMapping("/transcode")
    public SseEmitter transcode(@RequestParam String inputPath) {
        SseEmitter emitter = new SseEmitter();
        new Thread(() -> {
            try {
                List<String> command = Arrays.asList(
                    ffmpegService.getFfmpegPath(),
                    "-i", inputPath,
                    "-c:v", "libx264",
                    "-c:a", "aac",
                    "output.mp4"
                );
                Process process = ffmpegService.executeCommand(command);
                
                try(BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getErrorStream()))) {
                    String line;
                    while((line = reader.readLine()) != null) {
                        double progress = ffmpegService.parseProgress(line);
                        emitter.send(SseEmitter.event()
                            .data(progress)
                            .name("progress"));
                    }
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        }).start();
        return emitter;
    }
}

前端实现(Vue+ElementUI)

src/views/transcode.vue


<template>
  <div>
    <el-upload action="/api/video/transcode" :on-success="handleSuccess">
      <el-button type="primary">上传视频</el-button>
    </el-upload>
    <el-progress 
      :percentage="progress" 
      :format="format" 
      status="success"
      v-if="progress > 0"/>
  </div>
</template>

<script>
export default {
  data() {
    return { progress: 0, eventSource: null }
  },
  methods: {
    handleSuccess(response, file) {
      this.eventSource = new EventSource(`/api/video/transcode?inputPath=${file.url}`);
      this.eventSource.addEventListener('progress', (e) => {
        this.progress = parseFloat(e.data);
      });
    },
    format(percentage) {
      return `转码进度: ${percentage}%`;
    }
  }
}
</script>

ffmpeg转码进度打印如下
ffmpeg process

参考

FFmpeg Official Website

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞瑞绮绮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值