4

本文导读

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

了解视频的基本原理。

了解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,不禁一句我x,为啥我下载的大片才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,除此之外的其它频率人耳无法察觉,都可视为冗余信号。此外,根据人耳听觉的生理和心理声学现象,当一个强音信号与一个弱音信号同时存在时,弱音信号将被强音信号所掩蔽而听不见,这样弱音信号就可以视为冗余信号而不用传送。

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

https://baike.baidu.com/item/...

https://www.kamilet.cn/how-au...


下面进入本文的第二个主角

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 在浏览器中的碰撞


<p style="text-align: center">—— 完 ——</p>


本文参考文章

即时通讯网-史上最通俗视频编码技术入门

简书-音视频基础知识

滤镜实现各种图片效果 | Video-Filters | avfilter | 变色

阮一峰:FFmpeg 视频处理入门教程


ling
1.1k 声望314 粉丝