练习使用FFmpeg将视频转码为hls,并添加水印


前提准备

  • 一个高清的视频(这样转分辨率后起码不会糊)。
  • 一个高清的水印图片(理由同上)。
  • FFmpeg,直接官网下一个就可以,这里我使用的就是FFmpeg.exe。

下面将各个部分拆开练习,最后再合在一起。

1
2
3
4
ffmpeg -i 11.mp4
-vf "movie=wm.png [logo];[logo][in]scale2ref=w=oh*mdar:h=ih/10[logo-rescale][video-out];[video-out][logo-rescale]overlay=x=main_w/10-w/2:y=main_h/10-h/2 [out]"
-codec:v libx264 -s 1920x1080 -codec:a mp3
-map 0 -f ssegment -segment_format mpegts -segment_list playlist.m3u8 -segment_time 10 1080P%03d.ts

一些基本命令


1.转格式(容器转换)

1
$ ffmpeg -i 11.mp4 out.avi

直接在输出文件的后缀指定需要转的格式(容器)就行。

这样转码是会使用默认编码器,需要设置转码为copy来达到视频质量无损失。参考设置视频编码。


2.转分辨率

1
$ ffmpeg -i 11.mp4 -s 1280x720 out.mp4

通过-s 1280x720指定分辨率为720P。


3.设置视频编码

1
$ ffmpeg -i 11.mp4 -s 1280x720 -codec:v libx264 -codec:a mp3 out.mp4

这里通过-codec:v libx264指定视频编码格式为x264,通过-codec:a mp3指定音频编码格式为mp3

1
$ ffmpeg -i 4K_BXJG.mp4 -codec:v copy -codec:a copy out.avi

这里的转码格式被指定为copy,实际上FFmpeg就会省去编解码过程,速度非常快。

Stream copy is a mode selected by supplying the copy parameter to the -codec option. It makes ffmpeg omit the decoding and encoding step for the specified stream, so it does only demuxing and muxing. It is useful for changing the container format or modifying container-level metadata.

Since there is no decoding or encoding, it is very fast and there is no quality loss.

上面是官网中的描述,可以看到使用copy可以无损的转化容器格式。


4.转图片分辨率

1
$ ffmpeg -i wm.png -s 100x100 out.png

可以看到转图片其实和转视频是同理的。

5.流选择

一个正常的视频至少有两个流,一个视频流,一个音频流。

通过输入而不输出可以查看媒体文件信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ffmpeg -i 4K_BXJG.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '4K_BXJG.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp42mp41
creation_time : 2017-02-04T16:09:19.000000Z
Duration: 00:00:54.02, start: 0.000000, bitrate: 39655 kb/s
Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 3840x2160 [SAR 1:1 DAR 16:9], 39366 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
Metadata:
creation_time : 2017-02-04T16:09:19.000000Z
handler_name : ?Mainconcept Video Media Handler
encoder : AVC Coding
Stream #0:1(eng): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 317 kb/s (default)
Metadata:
creation_time : 2017-02-04T16:09:19.000000Z
handler_name : #Mainconcept MP4 Sound Media Handler

可以看到这个mp4文件包含两个流,0号流为视频流,1号流为音频流。

同时输入两个媒体文件试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ ffmpeg -i 4K_Z4.mp4 -i 4K_BXJG.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '4K_Z4.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: mp41mp42
creation_time : 2015-01-05T13:14:01.000000Z
Duration: 00:02:22.56, start: 0.000000, bitrate: 44851 kb/s
Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 3840x2160 [SAR 1:1 DAR 16:9], 44717 kb/s, 50 fps, 50 tbr, 30k tbn, 100 tbc (default)
Metadata:
creation_time : 2015-01-05T13:14:01.000000Z
encoder : AVC Coding
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
Metadata:
creation_time : 2015-01-05T13:14:01.000000Z
Input #1, mov,mp4,m4a,3gp,3g2,mj2, from '4K_BXJG.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp42mp41
creation_time : 2017-02-04T16:09:19.000000Z
Duration: 00:00:54.02, start: 0.000000, bitrate: 39655 kb/s
Stream #1:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 3840x2160 [SAR 1:1 DAR 16:9], 39366 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
Metadata:
creation_time : 2017-02-04T16:09:19.000000Z
handler_name : ?Mainconcept Video Media Handler
encoder : AVC Coding
Stream #1:1(eng): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 317 kb/s (default)
Metadata:
creation_time : 2017-02-04T16:09:19.000000Z
handler_name : #Mainconcept MP4 Sound Media Handler

可以发现流编号上面的规律,0:00:1表示第一个文件的两个流,1:01:1则表示第二个文件的两个流。

提取视频流:

1
$ ffmpeg -i 1080_BXJG.mp4 -map 0:0 -codec copy out.mp4

上面的命令将原视频的视频流单独分离出来。

合并音视频流:

1
$ ffmpeg -i 1080_BXJG.mp4 -i 4K_Z4.mp4 -map 0:0 -codec copy -map 1:1 -codec copy out.mp4

提取第一个视频的视频流,第二个视频的音频流,合并为out.mp4。




filter设置


1.过滤器图

先看官方文档说的:

Filtering in FFmpeg is enabled through the libavfilter library.

In libavfilter, a filter can have multiple inputs and multiple outputs. To illustrate the sorts of things that are possible, we consider the following filtergraph.

1
2
3
4
5
[main]
input --> split ---------------------> overlay --> output
| ^
|[tmp] [flip]|
+-----> crop --> vflip -------+

This filtergraph splits the input stream in two streams, then sends one stream through the crop filter and the vflip filter, before merging it back with the other stream by overlaying it on top.

filter即常说的滤镜,Filtering过程可以是一个多输入多输出的过程,多个输入的视频经过处理合并成一个或多个输出。

上面的过程的命令为:

1
$ ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT

它的效果如下:

image

image

它将原视频复制(split),裁剪一半(crop),反转(cflip),覆盖(overlay)到原视频之上。

可以看到主要的处理过程在-vf之后,它就代表filtergraph也就是过滤器图(相似的还有-filter_complex),相当于定义了一个视频处理的过程。

注意到:-vf不能有多个输入,而-filter_complex可以。


2.label(标签)

标签用于对流进行标记,目前我只知道它一些简单的用法。

例如上面的命令中的过滤器图:

"split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2"

  1. 它将原视频进行split,将它复制为两个流,一个命名为[main],一个命名为[tmp]
  2. 它将[tmp]流进行crop操作和vfilp操作,处理之后的流命名为[flip]
  3. 输入[main][flip]流,进行overlay(覆盖)操作,根据这里[main][flip]的先后关系,这里将[flip]覆盖到[main]之上。

例如下面的命令,将第二个输入视频分辨率降低一半,然后覆盖到第一个视频之上:

1
$ ffmpeg -i 1080_BXJG.mp4 -i 1080_BXJG.mp4 -filter_complex "[1:0]scale=w=iw/2:h=ih/2[scaled]; [0][scaled] overlay" tmp1.mp4

其中"[1:0]scale=w=iw/2:h=ih/2[scaled]; [0][scaled] overlay"

  1. [1:0]代表第二个视频的第一个流(视频流),将它的scale变为一半(iw/2ih/2),然后标记为[scaled]
  2. [0]就代表第一个输入的媒体文件,将[scaled]置于它之上然后输出。

3.将视频分辨率降低一半

可以利用scale来完成,其中iwih分别代表输入的宽和高。

1
$ ffmpeg -i 11.mp4 -vf "[in]scale=w=iw/2:h=ih/2[out]" out.mp4

为了展示效果,这里将降低了分辨率的视频覆盖到原视频之上:

1
$ ffmpeg -i 1080_BXJG.mp4 -filter_complex "[0:0]scale=w=iw/2:h=ih/2[scaled]; [0][scaled] overlay" tmp1.mp4

image


4.为视频添加水印

为视频添加水印,也就是将水印覆盖到原视频之上,即overlay滤镜。

简单覆盖:

1
$ ffmpeg -i 1080_BXJG.mp4 -i wm.png -filter_complex overlay out.mp4

简单覆盖时,没有对水印大小进行调整,会造成下面的结果:

image

大小调整-1:

1
$ ffmpeg -i 1080_BXJG.mp4 -i logo.png -filter_complex '[1]scale=w=200:h=-1[scaled]; [0][scaled]overlay' out.mp4

使用scale滤镜来进行分辨率的调整,再将它覆盖到原视频之上:

image

可以看到这里成功的对水印的大小进行了调整,但是又有一点太小了。

那么问题来了,难道要不断的试来找到一个健康的分辨率吗?

大小调整-2:

1
$ ffmpeg -i 1080_BXJG.mp4 -i logo.png -filter_complex '[1][0]scale2ref=w=oh*mdar:h=ih/8[scaled][0-out]; [0-out][scaled]overlay' out.mp4

scale2ref滤镜有两个输入,它对第一个输入进行rescale, 但它与scale滤镜的区别在于,它使用第二个输入作为参考,来进行第一个输入的rescale。

这里的[1][0]scale2ref=w=oh*mdar:h=ih/8[scaled][0-out]中,ih指的就是参考输入[0]的宽度, 为了保证水印的横纵比,这里使用oh*mdar来定义水印的宽度。

image

水印位置调整-1:

可以看到上面水印都是贴着视频的左上角的,这里来对它的位置进行调整:

1
$ ffmpeg -i 1080_BXJG.mp4 -i logo.png -filter_complex '[1][0]scale2ref=w=oh*mdar:h=ih/8[scaled][0-out]; [0-out][scaled]overlay=x=300:y=300' out.mp4

可以通过指定overlay的坐标来确定水印覆盖的位置(默认x=0,y=0,即左上角),这里将坐标指定在300,300,效果如下:

image

水印位置调整-2:

1
$ ffmpeg -i 1080_BXJG.mp4 -i logo.png -filter_complex '[1][0]scale2ref=w=oh*mdar:h=ih/8[scaled][0-out]; [0-out][scaled]overlay=x=main_w/10-w/2:y=main_h/10-h/2' out.mp4

这里main_wmain_h指的就是视频输入([0-out])的宽和高,wh则是水印输入([scaled])的宽和高, 这里将水印的中心点指定在视频输入的1/10宽和1/10高的位置。

image


转hls

hls即包括一个m3u(8)的索引文件,TS媒体分片文件和key加密串文件。

在ffmpeg中可以使用hls或者segment来实现。


使用segment muxer

1
$ ffmpeg -i 1080_BXJG.mp4 -f segment -segment_format mpegts -segment_list playlist.m3u8 -segment_list_size 0 -segment_time 5 out%03d.ts

上面的命令将输入视频切割为5秒一片的ts文件。其中,-segment_list playlist.m3u8设置文件list输出到playlist.m3u8, -segment_list_size 0设置playlist.m3u8里面将包含所有切分出来的分片, -segment_time 5设置分片大小为5秒。

得到如下结果:

image


使用hls muxer

1
$ ffmpeg -i 1080_BXJG.mp4 -map 0 -f hls -hls_segment_type mpegts -hls_list_size 6 -hls_time 5 -hls_segment_filename 'out%03d.ts' playlist.m3u8

参数基本一样,就不多解释了,效果如下:

image


转码,水印,hls

将上面的命令进行总结,可以融合得到下面这条命令:

1
2
3
4
5
$ ffmpeg -i 1080_BXJG.mp4 -i logo.png \
-filter_complex '[1][0]scale2ref=w=oh*mdar:h=ih/8[scaled][0-out]; \
[0-out][scaled]overlay=x=main_w/10-w/2:y=main_h/10-h/2[over];[over]scale=w=1920:h=-1[out]' \
-map [out] -c:v h264 -c:a mp3 \
-f hls -hls_segment_type mpegts -hls_list_size 0 -hls_time 5 -hls_segment_filename 'out%03d.ts' playlist.m3u8

这条命令将一个视频文件添加水印,并切割为5秒的ts小文件,同时将分辨率调整为1080P。


基于nginx的m3u8的点播、直播的实现

首先看我这里nginx的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ cat /etc/nginx/nginx.conf
...
server {
listen 11111 default_server;
listen [::]:11111 default_server;
server_name long;
location / {
root html;
index index.html index.htm;
}
location /hls {
types {
application/vnd.apple.mpegusr m3u8;
video/mp2t ts;
}
root /mycephfs;
add_header Cache-Control no-cache;
}
}
...

我这里将nginx的服务起在11111端口,文件目录为/mycephfs/hls,浏览器打开192.168.90.233:11111

image

点播:

将对应的hls文件放入nginx的文件目录下:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ll /mycephfs/hls/videos/BXJG-hls/
-rw-rw-r-- 1 cluster cluster 3938600 Aug 13 12:47 out000.ts
-rw-rw-r-- 1 cluster cluster 1366196 Aug 13 12:47 out001.ts
-rw-rw-r-- 1 cluster cluster 926088 Aug 13 12:47 out002.ts
-rw-rw-r-- 1 cluster cluster 3286992 Aug 13 12:47 out003.ts
-rw-rw-r-- 1 cluster cluster 7581852 Aug 13 12:47 out004.ts
-rw-rw-r-- 1 cluster cluster 1721516 Aug 13 12:47 out005.ts
-rw-rw-r-- 1 cluster cluster 3373848 Aug 13 12:48 out006.ts
-rw-rw-r-- 1 cluster cluster 3193556 Aug 13 12:48 out007.ts
-rw-rw-r-- 1 cluster cluster 5041220 Aug 13 12:48 out008.ts
-rw-rw-r-- 1 cluster cluster 3229088 Aug 13 12:48 out009.ts
-rw-rw-r-- 1 cluster cluster 370 Aug 13 12:48 playlist.m3u8

直接使用potplayer打开链接http://192.168.90.233:11111/hls/videos/BXJG-hls/playlist.m3u8

image

点播效果完成。

直播:

这里直播和点播的十分相似,它通过不断的修改.m3u8文件来达到直播的目的。

同样的,使用一个本地的视频文件当作输入流来进行直播,需要加入-re来模拟直播流的输入速度,-stream_loop来不断重复输入。 另外,这个过程会不断产生新的ts文件,需要将使用过的小文件删除,添加-hls_list_size 5.m3u8文件中的数量控制为5个, 添加-hls_flags delete_segments开启自动删除过时的ts小文件。

1
2
3
4
$ ffmpeg -re -stream_loop -1 -i 1080_MS.mp4 -i logo.png \
-filter_complex '[1][0]scale2ref=w=oh*mdar:h=ih/8[scaled][0-out];[0-out][scaled]overlay=x=main_w/10-w/2:y=main_h/10-h/2[over]' \
-map [over] -c:v h264 -map 0 -c:a copy \
-f hls -hls_segment_type mpegts -hls_flags delete_segments -hls_list_size 5 -hls_time 10 -hls_segment_filename 'out%05d.ts' live.m3u8

产生ts小文件的效果如下:

1
2
3
4
5
6
7
8
9
10
11
$ ls /mycephfs/hls/videos/MS/
total 49306
-rw-rw-r-- 1 cluster cluster 227 Aug 14 16:41 live.m3u8
-rw-rw-r-- 1 cluster cluster 6638092 Aug 14 16:40 out00017.ts
-rw-rw-r-- 1 cluster cluster 6406100 Aug 14 16:40 out00018.ts
-rw-rw-r-- 1 cluster cluster 7560044 Aug 14 16:40 out00019.ts
-rw-rw-r-- 1 cluster cluster 7581664 Aug 14 16:41 out00020.ts
-rw-rw-r-- 1 cluster cluster 9148832 Aug 14 16:41 out00021.ts
-rw-rw-r-- 1 cluster cluster 8171984 Aug 14 16:41 out00022.ts
-rw-rw-r-- 1 cluster cluster 4980736 Aug 14 16:41 out00023.ts

可以看到ffmpeg同一时刻只会保留6个ts文件。

同样的,使用potplayer打开连接http://192.168.90.233:11111/hls/videos/MS/live.m3u8,就可以看到直播, 和点播十分相似,不同之处在于,这里会不断的播放下去:

image


参考

FFmpeg Documentation

使用FFMPEG生成HLS

---------------------------------END---------------------------------