九宫格的 layout 是九个点,第三行要写 h0+h1 才是对的起点。 九格必须等大,一旦不等,画布会歪成 480×202 这种怪尺寸。
四宫格是两行两列,九宫格多一行一列,坐标的加法多一层。 这一节的重点不是九个坐标,而是「为什么必须等大」。
一、九个坐标
九宫格是三行三列,九个点排成一张表:
行与列 第 1 列 第 2 列 第 3 列
第 1 行 0_0 w0_0 w0+w1_0 第 2 行 0_h0 w0_h0 w0+w1_h0 第 3 行 0_h0+h1 w0_h0+h1 w0+w1_h0+h1
横着看是加宽,竖着看是加高。第三行要写 h0+h1:
第一行的高度是 h0、第二行是 h1,两行叠起来才是第三行的起点。
九格等大时 h0 = h1 = 90,第三行起点就是 180。
这个 180 不是猜的,是「前两行高度之和」。
二、480×270 是怎么来的
九路 160×90 拼成 480×270:
格 输入 颜色 格 输入 颜色 格 输入 颜色
1 red 红 2 green 绿 3 blue 蓝 4 white 白 5 red 红 6 green 绿 7 blue 蓝 8 white 白 9 red 红
九格按 红绿蓝白 循环,量出来的中心色一路对上,九格没有一格错位。
480 是 160×3、270 是 90×3,尺寸也是整齐的三倍关系。
三倍宽高三倍高,等于面积九倍。 九路 1080p 拼成九宫格直接就是 5760×3240,普通机器编不动。
三、九格必须等大
w0、w1、h0、h1 只认第 0 路与第 1 路,这是硬限制:
九格尺寸 输出尺寸
全是 160×90 480×270 三列依次 160×90、80×45、40×22 480×202
第二行那个 480×202 就歪了:宽不是三列相加,高也对不上。
w0+w1 只加到第二路的宽,第三路到底多宽它根本不知道。
所以规矩很硬:进 xstack 之前,九路必须先 scale 成同一尺寸。
想让九路保持各自比例,就先 scale 到同一个框里再 pad 补边,
这样九格等大、内容也不变形。
四、实际用途与开销
九宫格最常见的三个用途:
用途 为什么是九格
短视频同屏 九宫格是最常见的竖屏拼法 监控九路 3×3 正好铺满一块屏 多机位预览 一眼看全九个角度
开销这件事得说清楚:九路一起解码、一起拼,解码与内存的开销大致是单路的九倍。 另外 xstack 默认按最短的那路收尾,某一路短了整段就跟着短。
真要长期跑,先降分辨率:九格每格 320×180 就够看,不必按原片尺寸拼。 拼完 960×540,编码压力比 5760×3240 差了三十多倍。
五、动手 Demo:九格颜色、尺寸不一致的后果
下面这段代码把九路纯色拼成九宫格,量每行三格的中心色; 再把三列的尺寸改成不一样,看输出会变成多大。
import av
import numpy as np
L9 = (u"0_0|w0_0|w0+w1_0|0_h0|w0_h0|w0+w1_h0"
u"|0_h0+h1|w0_h0+h1|w0+w1_h0+h1")
COL = [u"red", u"green", u"blue", u"white"]
def nine(sizes=None, fill=u"black"):
g = av.filter.Graph()
sizes = sizes or [u"160x90"] * 9
srcs = [g.add(u"color", u"c=%s:s=%s:r=25" % (COL[i % 4], sizes[i]))
for i in range(9)]
xs = g.add(u"xstack",
u"inputs=9:layout=%s:fill=%s" % (L9, fill))
sk = g.add(u"buffersink")
for i, p in enumerate(srcs):
p.link_to(xs, 0, i)
xs.link_to(sk)
g.configure()
o = sk.pull()
return o.to_ndarray(format=u"rgb24"), o.width, o.height
a, w, h = nine()
print(u"--- 九格 160x90 -> %dx%d" % (w, h))
for j in range(3):
cells = []
for i in range(3):
k = j * 3 + i
blk = a[j * 90 + 40:j * 90 + 50, i * 160 + 75:i * 160 + 85]
cells.append(tuple(int(v) for v in blk.reshape(-1, 3).mean(axis=0)))
print(u" 第 %d 行 %s" % (j + 1, cells))
print(u"--- 九格尺寸不一致")
mix = [u"160x90"] * 3 + [u"80x45"] * 3 + [u"40x22"] * 3
_, w2, h2 = nine(mix)
print(u" 三列依次 160x90 / 80x45 / 40x22 -> %dx%d" % (w2, h2))
实跑输出:
--------------------------------------------------------------------
7.10.4 使用 FFmpeg 实现【九宫格】
--------------------------------------------------------------------
**① 九个坐标**
**九宫格的 layout 就是四个变量拼出来的九个点:**
行\列 第 1 列 第 2 列 第 3 列
----------------------------------------------------
第 1 行 0_0 w0_0 w0+w1_0
第 2 行 0_h0 w0_h0 w0+w1_h0
第 3 行 0_h0+h1 w0_h0+h1 w0+w1_h0+h1
**第三行要写 `h0+h1`,因为第一行的高度是 `h0`、第二行是 `h1`,**
**两行叠起来才是第三行的起点。九格等大时就是 `90+90=180`。**
**② 九路 160×90 拼成 480×270**
格 输入 颜色 格 输入 颜色 格 输入 颜色
-----------------------------------------------------------------------------
1 red 红 2 green 绿 3 blue 蓝
4 white 白 5 red 红 6 green 绿
7 blue 蓝 8 white 白 9 red 红
**九格按 红绿蓝白红绿蓝白红 循环,量出来的中心色一路对上。**
**480 是 `160×3`、270 是 `90×3`,尺寸也是三倍关系。**
**③ 九格尺寸必须一致**
**`w0`、`w1`、`h0`、`h1` 只认第 0 路与第 1 路。**
**九格等大时它们相等,怎么用都对;一旦不等,坐标就全歪。**
九格尺寸 输出尺寸
----------------------------------------------
全是 160×90 480×270
三列依次 160×90、80×45、40×22 480×202
**第二行那个 480×202 就歪了:宽不是三列相加,高也对不上。**
**`w0+w1` 只加到第二路的宽,第三路的宽它根本不知道。**
**所以规矩很硬:先 `scale` 把九路统一成同一尺寸,再进 xstack。**
**④ 九宫格的实际用途**
用途 为什么是九格
-------------------------------------------
短视频同屏 九宫格是最常见的竖屏拼法
监控九路 3×3 正好铺满一块屏
多机位预览 一眼看全九个角度
**九路一起解、一起拼,解码与内存的开销大致是单路的九倍。**
**真要长期跑,得先降分辨率:九格每格 320×180 就够了,不必按原片尺寸拼。**
三行输出按 红绿蓝 / 白红绿 / 蓝白红 排下来,跟本节那张表一致。
九格都没错位,说明第三行写 h0+h1 是对的。
最后一行是本节的重点:尺寸一不齐,输出变成 480×202。 480 还是对的(因为第三列起点按 w0+w1 算),但高从 270 掉到 202,画面被压扁了。
这个错误不会抛异常,只会出一张歪图。 所以生产环境里,进 xstack 之前统一 scale 是硬规矩,不能靠肉眼发现。
30 秒实验
- 改格数:把
inputs=9改成inputs=4配四宫格 layout,看能不能复用同一张图。 - 试等大:把
mix换成九路全80x45,看输出是不是 240×135。 - 量混色:把格中心改成格边界取样,看混色有多宽。
常见误区
- 九格尺寸不统一。画布会歪成 480×202 这种怪尺寸,还不报错。
- 第三行写
0_h1。那是第二行的起点,第三行会跟第二行重叠。 - 九路按原片尺寸拼。输出面积是九倍,编码直接卡死。
自测三问(点开对答案)
1. 九宫格第三行的 y 起点怎么写? —— `h0+h1`,前两行高度之和。 2. 九格 160×90 拼完多大? —— 480×270。 3. 九格尺寸不统一会怎样? —— 画布歪掉(实测 480×202),而且不报错。- ☐ (本节完,下一节 7.10.5 实现的视频【四宫格】)