初探视频原理和FFmpeg

前端 @ 奇虎360

本文作者郭文涛,奇舞团前端开发工程师。

本文导读

阅读本文你将获得以下知识:

了解视频的基本原理。

了解 FFmpeg 是什么,和一些常用的用法。

用 FFmpeg 搭建简单的视频直播推流。

FFmpeg 在 NodeJS 中的一些用法。

背景

短视频大行其道的年代,作为程序员势必需要了解:视频编辑背后的原理和技术。本文简略的描述了视频的组成原理和常用的视频编辑工具,以及在 NodeJS 中的用法。

想要了解视频原理,首先应该从图像原理开始说起。

图像基础

1. 像素

图像画面由一个数字序列表示的图像中的一个最小单位色块,被称之像素(pixel/px)。

注意:像素只有位图才会有,是用来记录位图图像的。

我们所说的图像大小为1920*1080,指的就是长宽各有 1920 和 1080 的像素点,那么一张1920*1080的图片总共有的像素点为:1920*1080 = 2073600个像素点。

图像的大小如何计算?

图像的大小:像素数量 * 像素大小 = 图片大小,而 像素大小像素深度有关系。RGB表示的真彩色能表示256×256×256=16,777,216,就是我们常见的1600万色,是人眼可见的全部色彩,超出没有意义。RGB的像素深度有1bit、4bit、8bit、16bit、24bit、32bit,如在ps中下图在新建一张画布选择8bit(指 rgb 每种颜色占 8bit),那这样1 px = 3 * 8bit = 24bit,俗称24 位图。根据以上公式就能算出如下图图像的大小:500 * 378 * 24 / 8 = 567000Byte = 567000Byte / 1024 = 553.7109375 Kb,和 ps 显示的图像大小一样。

但往往真实的图片大小远比以上计算的结果小很多, 这是因为导出的图片都经过压缩的,关于图片压缩技术可自行搜索学习。

视频基础

1. 视频和图像的关系?

视频就是图片一帧一帧连起来的产物,连起来的越快看着越流畅,用 帧率(就是每秒播放图片的数量 FPS)来衡量视频的流畅度。那么根据图片大小的算法就能算出视频的大小。

视频的大小 = 时长(秒) * 帧率(FPS)* 图片大小

那么1920×1280分辨率, 30FPS,时长 1 秒的视频的大小就是:1920 * 1280 * 24 / 8 * 30 / 1024 / 1024 = 210.9375 M,那么 1 小时的影片需要:210.9 * 60 * 60 / 1024 = 741.4453125 G,不禁产生疑问,为啥我下载的大片才 1G 多?莫慌,视频要是这么简单,那我们就太天真了,所以就有了下文 「视频编码」

2. 视频是怎么来的?

几个概念

  • 帧(Frame):就是一张静止的画面, 是视频的最小单位。

  • 帧速率(FPS):每秒播放图片的数量。

  • 码率(Bit Rate):视频文件在单位时间内使用的数据流量,决定视频的质量和大小,单位是 kb/s 或者 Mb/s。一般来说同样分辨率下,视频文件的码流越大,压缩比就越小,画面质量就越高。码流越大,说明单位时间内取样率越大,数据流,精度就越高,处理出来的文件就越接近原始文件,图像质量越好,画质越清晰,要求播放设备的解码能力也越高。

    码率的常见三种模式:
    - CBR
      - 全程码率恒定
      - 文件大小可预测
      - 编码压力小,直播常用
    - VBR
      - 码率可变
      - 简单场景码率低,复杂场景码率高
    - CRF
      - 固定质量模式
      - CRF值越低,视频看起来质量越高
    复制代码

视频构成

视频和音频就像是饭和菜,封装格式就相当于碗。

注意: 下文所有视频均代表包含音频的视频

1. 视频封装格式

常见封装格式有 MP4、AVI、FLV、mov、RMVB、MKV、WMV、3GP、ASF 等。

2. 编码格式

视频编码是对采用视频压缩算法将一种视频格式转换成另一种视频格式的描述,音频编码同理。

常见的视频编码格式有:AC-1MPEG2/H.262VP8MPEG4VP9H.261H.263H.264H.265 等。

常见的音频编码格式有:WMAMP3AC-3AACAPEFLACWAV 等。

视频压缩原理

主要是将视频像素数据(RGB,YUV 等)压缩成为视频码流,从而降低视频的数据量,也就是处理像素。

YUV:RGB一样是一种颜色编码格式,相比RGB更利于压缩。其中"Y"表示明亮度(Lumina nce 或 Luma),也就是灰阶值;而"U"和"V"表示的则是色度(Chrominance 或 Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

视频压缩分为下面两种类型

1. 帧内压缩

也叫空间压缩,类似于图像压缩,属于有损压缩算法,达不到很高的压缩比。

2. 帧间压缩

主要是通过记录关键帧,通过压缩关键帧之间连续帧的冗余信息(连续帧内相同的像素区域)的过程。

为了记录关键帧,将视频的画面帧分为三类:

  • I 帧:帧内编码帧(intra picture),能展示最完整的画面, 可压缩的空间小,编码过程属于帧内编码。
  • P 帧:前向预测编码帧(predictive-frame),需要参考前面的 I 帧或者 P 帧来找出不同部分进行编码,压缩比比较高。
  • B 帧 双向预测,也就是 B 帧记录的是本帧与前后帧的差别。也就是说要解码 B 帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B 帧压缩率高,但是对解码性能要求较高。

GOP(Group of Pictures)值

编码器将多张图像进行编码后生产成一段一段的 GOP ,每一组 IPB 帧的序列包含多少帧,也就是一个 I 帧结束后需要经过多少帧才能出现下一个 I 帧。所以同码率下 GOP 值越大,B 帧和 P 帧越多,视频质量越高。

在压缩或者解压缩视频的过程用到编解码器(Codec)。总的过程可以:

视频的编码的过程:

下图来源于即时通讯网

视频解码的过程:

音频压缩原理

音频压缩是在保证信号在听觉方面不产生失真的前提下,对音频数据信号进行尽可能大的压缩, 去除冗余信息。冗余信号包含人耳听觉范围外的音频信号以及被掩蔽掉的音频信号等。例如,人耳所能察觉的声音信号的频率范围为 20Hz ~ 20KHz,除此之外的其它频率人耳无法察觉,都可视为冗余信号。此外,根据人耳听觉的生理和心理声学现象,当一个强音信号与一个弱音信号同时存在时,弱音信号将被强音信号所掩蔽而听不见,这样弱音信号就可以视为冗余信号而不用传送。

音频压缩不是今天的主角,想深入学习可参考如下链接:

baike.baidu.com/item/%E9%9F…

www.kamilet.cn/how-audio-c…

FFmpeg

1. FFmpeg 什么?

FFmpeg is a collection of libraries and tools to process multimedia content such as audio, video, subtitles and related metadata.

简单说就是一个跨平台的视频处理的程序。

2. FFmpeg 的原理

整个过程基本可以说成:解复用 => 解码 => 编码 => 复用器。

 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                      _________
                                     |         |
                                     | decoded |
                                     | frames  |
                                     |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|
复制代码

3.FFmpeg 安装

FFmpeg 分为 3 个版本:Static、 Shared、 Dev

Mac 安装:

brew install ffmpeg
复制代码

其他安装请参考官网

4. FFmpeg 用法

它能分别对视频的的各个组成进行编码,它对音视频的编码格式支持也比较全面。例如:对视频容器的转换、音视频的压缩、视频截取、截图、滤镜、音频提取等等,非常强大。

命令行语法:

ffmpeg [全局参数] [输入文件参数] -i [输入文件] [输出文件参数] [输出文件]

复制代码

视频信息:

// 获取视频信息
ffmpeg -i input.mp4

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input2.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2mp41
    encoder         : Lavf58.29.100
    description     : Packed by Bilibili XCoder v2.0.2
  Duration: 00:08:24.45, start: 0.000000, bitrate: 2180 kb/s   // 时长,码率
    Stream #0:0(und): Video: hevc (Main) (hev1 / 0x31766568), yuv420p(tv), 1920x1080 [SAR 1:1 DAR 16:9], 2046 kb/s, 25 fps, 25 tbr, 16k tbn, 25 tbc (default)  // 第一个流是视频流,编码格式是hevc(封装格式为hev1),每一帧表示为yuv420p,分辨率1920*1080,码率2046kb/s, fps为25。
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default) // 第二个流是音频流,编码格式是aac(封装格式为mp4a)采样率是44100 Hz,声道是立体声,码率92Kbit/s。
    Metadata:
      handler_name    : SoundHandler
复制代码

码率的转换:

ffmpeg -i input.mp4 -b:v 64k -bufsize 64k output.mp4
复制代码

帧率转换:

ffmpeg -i input.mp4 -r 5 output.mp4
复制代码

分辨率转换:

ffmpeg -i input.mp4 -vf scale=480:-1 output.mp4 // 1080p 转为 480p
复制代码

视频倍速:

ffmpeg -i test1 "setpts=PTS/5" test4.mp4 // 视频5倍速转换
fmpeg -i input.mp4 -filter:a "atempo=2.0" 4s.mp4 // 音频2倍速播放
ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" -vn 4s.mp4 // 音视频同时2倍速
复制代码

提取音视频:

ffmpeg -i input.mp4 -an output.mp4 //提取视频
ffmpeg -i input.mp4 -vn output.mp3 //提取音频
复制代码

视频比例转换:

ffmpeg -i input.mp4 -aspect 21:9 output.mp4
复制代码

视频容器转换:

ffmpeg -i input.mp4 output.avi
复制代码

视频截图:

ffmpeg -ss 00:00:05 -i input.mp4 -vframes 1 -q:v 5 -f image2 pic-%03d.jpeg
// -ss 00:00:05 从第五秒开始  -vframes 1 只截取1帧  -q:v 5 图片质量1-5
复制代码

视频截取:

ffmpeg -ss 00:00:02 -i input.mp4 -t 6.5 -c copy cut.mp4
ffmpeg -ss 00:00:02 -i input.mp4 -to 00:00:10 -c copy cut.mp4
复制代码

连续图片或视频生成 gif 图:

ffmpeg -i output.mp4 -to 10 -r 30 -vf scale=100:-1 gg.gif // 截取视频某个部分生成gif  100:-1 指定宽度,高度保持原始比例

ffmpeg -r 5 -i pic-%03d.jpeg 11.gif   // 多图生成gif

// 图片还可生成视频
ffmpeg -r 20 -i pic-%03d.jpeg gif.mp4

ffmpeg -f concat -i "concat:part1.mp4|part2.mp4|3.mp4|part4.mp4" -c copy output.mp4 // 多个视频拼接成一个
复制代码

图片或视频加滤镜:

// 模糊滤镜
ffmpeg -y -i pic-012.jpeg -vf boxblur=7 blur.jpeg
// 变色
ffmpeg -i pic-012.jpeg -vf colorbalance=rm=1 colorbalance1.jpg // 调整rgb某个维度的权重实现变色。
ffmpeg -i pic-012.jpeg -vf colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3 colorchannelmixer1.jpg // 对rgba四个通道进行重新计算,并分别给定权重比例。
ffmpeg -i pic-012.jpeg -vf hue=h=30:s=1 hue1.jpg // 改变色调,相当在调色板上调色
ffmpeg -i pic-012.jpeg -vf lutyuv="y=negval:u=negval:v=negval" lutyuv1.jpg // lutyuv用于yuv颜色空间
ffmpeg -i pic-012.jpeg -vf negate=0 negate1.jpg // 反转
ffmpeg -i pic-012.jpeg -vf swapuv swapuv1.jpg  // UV 互换
ffmpeg -i pic-012.jpeg -vf crop=w=200:h=300:x=500:y=800 crop1.jpg // 裁剪

复制代码

添加水印:

ffmpeg -i input.mp4 -i pic-012.jpeg -filter_complex "[1:v] scale=176:144 [logo];[0:v][logo]overlay=x=0:y=0" out.mp4 //给视频添加图片水印

ffmpeg -i input.mp4 -vf "drawtext=fontsize=100:fontcolor=white:alpha=0.3:text='%{localtime\:%Y\-%m\-%d %H-%M-%S}':y=h-line_h-100:x=(w-text_w)/2" output22.mp4// 添加文字水印

ffmpeg -i input.mp4 -i pic-012.jpeg -filter_complex "[1:v] scale=176:144 [logo];[0:v][logo]overlay=x=0:y=0" out.mp4

ffmpeg -i input.mp4 -vf drawtext="fontsize=100:text='我是水印':fontcolor=green:enable=lt(mod(t\,3)\,1)" interval-sy.mp4
// t 时间,s
// mod(t\,2) 计算t%2
// lt(mod(t\,2)\,1) 如果mod(t\,2)<1,返回1,否则返回0
// enable=lt(mod(t\,2)\,1) 每隔1s显示一次水印,enable=lt(mod(t\,3)\,1) 每隔3s.
复制代码

添加字幕:

// 第一步 用you-get下载B站视频
// 第二步 用 danmaku2ass.py 转换弹幕 https://github.com/m13253/danmaku2ass
// 第三步 可以用ffmpeg转换弹幕
ffpmeg -i input.ass input.srt

// 第四步 给视频添加字幕或弹幕 字幕可添加多个
ffmpeg -i input.mp4 -vf subtitles=input.ass output.mp4
复制代码

为音频添加封面:

ffmpeg -loop 1 -i cover.jpg -i input.mp3 -c:v libx264 -c:a aac -b:a 192k -shortest output.mp4

// -loop 1表示一直循环, -shortest 音频结束视频输出就结束
复制代码

视频画中画:

ffmpeg -re -i input.mp4 -vf "movie=output.mp4,scale = 480*320[test]; [in][test] overlay [out]" -vcodec libx264 videoInvideo.mp4
复制代码

多宫格:

ffmpeg -y -i input.mp4 -i input.mp4 \
-i input.mp4 -i input.mp4 \
-filter_complex "nullsrc=size=640x480[base]; \
[0:v]scale=320x240[topleft]; \
[1:v]scale=320x240[topright]; \
[2:v]scale=320x240[bottomleft]; \
[3:v]scale=320x240[bottomright]; \
[base][topleft]overlay=shortest=1[tmp1]; \
[tmp1][topright]overlay=shortest=1:x=320[tmp2]; \
[tmp2][bottomleft]overlay=shortest=1:y=240[tmp3]; \
[tmp3][bottomright]overlay=shortest=1:x=320:y=240" \
-vcodec libx264 9_video_filtered.flv
// nullsrc创建画布
复制代码

视频压缩:

ffmpeg -i input.mp3 -ab 128 output.mp3 // 压缩音频

ffmpeg -i input.mp4 -vf scale=1280:-1 -c:v libx264 -preset veryslow -crf 24 output.mp4 // 压缩视频
复制代码

视频直播推流:

// 录制视频保存在本地
ffmpeg -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -f h264 -r 30 ~/Downloads/test.h264

// 推送已下载在文件夹的视频
ffmpeg -re -i ~/Downloads/xxx.mp4  -vcodec libx264 -acodec aac -strict -2 -f flv rtmp://localhost:1935/live

// 录制桌面
ffmpeg -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -acodec libfaac -f flv rtmp://localhost:1935/rtmplive/room

// 录制桌面和麦克风
ffmpeg -f avfoundation -i "1:0" -vcodec libx264 -preset ultrafast -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:1935/live/room

// 录制桌面和麦克风,并打开摄像头拍摄
ffmpeg -f avfoundation -framerate 30 -i "1:0" \-f avfoundation -framerate 30 -video_size 640x480 -i "0" \-c:v libx264 -preset ultrafast \-filter_complex 'overlay=main_w-overlay_w-10:main_h-overlay_h-10' -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:2016/rtmplive/room

复制代码

直播 DEMO:

  1. 安装支持rtmp的 docker 镜像:docker pull tiangolo/nginx-rtmp

  2. 启动tiangolo/nginx-rtmp容器:docker run -d -p 1935:1935 --name nginx-rtmp tiangolo/nginx-rtmp 查看nginx配置: docker exec -it nginx-rtmp /bin/bash

    推流地址:rtmp://10.17.8.189:1935/live

  3. Ffmpeg 推流:ffmpeg -f avfoundation -i "1:0" -vcodec libx264 -preset ultrafast -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:1935/live/room

  4. 用支持支持rtmp的播放器(IINA)或者ffplay打开: ffplay rtmp://10.17.8.189:1935/live

一个简单的直播 demo 就跑起来了。


5. FFmpeg 在 Node 中的用法

Fluent-ffmpeg

Fluent-ffmpeg 是将复杂的 ffmpeg 命令抽象成 NodeJS 的模块,前提是系统已安装 FFmpeg

一些简单的用法

// 视频信息
ffmpeg.ffprobe(input, function (err, metadata) {
  console.dir(metadata);
});

// 提取音频
ffmpeg(input)
  .audioCodec("libmp3lame")
  .on("error", function (err) {
    console.log("发生错误: " + err.message);
  })
  .on("end", function () {
    console.log("提取音频完成 🍻🍻!");
  })
  .save(resOut);

// 提取视频
ffmpeg(input)
  .noAudio()
  .on("error", function (err) {
    console.log("发生错误: " + err.message);
  })
  .on("end", function () {
    console.log("提取视频完成 🍻🍻!");
  })
  .save(resOut);
复制代码

总结

回顾一下,广义上的视频是由:音频视频两部分组成,它们分别有对应的各自的编码规范视频容器是将不同编码格式的音、视频组合在一起的一种封装格式。视频编码格式主要是对视频的大小进行压缩,分为帧内压缩帧间压缩,帧间压缩主要是通过记录关键帧形式来进行压缩。

FFmpeg 是处理音视频编码的一种程序,主要原理:demuxer => decoder => encoder => muxer

Fluent-ffmpeg 是将复杂的 ffmpeg 命令抽象成 nodeJs 的模块,前提是系统已安装 FFmpeg,这对于前端工程师来说,可以用它处理众多音视频操作。

挖坑

下一篇预告:FFmpegwasm 在浏览器中的碰撞

本文参考文章

  1. 即时通讯网-史上最通俗视频编码技术入门:www.52im.net/thread-2840…
  2. 简书-音视频基础知识:www.jianshu.com/p/614b3e6e6…
  3. 滤镜实现各种图片效果 | Video-Filters | avfilter | 变色:www.geek-share.com/detail/2763…
  4. 阮一峰:FFmpeg 视频处理入门教程:www.ruanyifeng.com/blog/2020/0…
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改