基础篇
1.常用图像格式介绍
常用图像像素格式 RGB 和 YUV。
1.1RGB
RGB分类通常指的是将图像或颜色按照RGB(红、绿、蓝)颜色空间进行分组或分类。RGB图像格式通常包括RGB24(RGB888)、RGB32、RGBA、RGB565等。
RGB24是一种常用的图像颜色格式,表示每个像素使用24位(8位红、8位绿、8位蓝)来存储颜色信息。
RGB32是在RGB24的基础上增加了一个8位的alpha通道,用于表示透明度。每个像素总共占用32位。
RGB565使用16位表示颜色,其中5位用于红色,6位用于绿色,5位用于蓝色,适用于减少内存占用的场景
1.2YUV
YUV 图像是指将亮度参量 Y 和色度参量 U/V 分开表示的像素格式,主要用于优化彩色视频信号的传输。YUV 相比于 RGB 格式最大的好处是可以做到在保持图像质量降低不明显的前提下,减小文件大小。YUV 格式之所以能够做到,是因为进行了采样操作。主流的采样方式有三种:YUV 4:4:4(YUV444),YUV 4:2:2(YUV422),YUV 4:2:0(YUV420)。
若以以黑点表示采样该像素点的 Y 分量,以空心圆圈表示采用该像素点的 UV 分量,则这三种采样方式如下:
YUV 存储格式
YUV 存储可以分为两种:packed(打包)和 planar(平面);
-
packed:Y、U、V 分量穿插着排列,三个分量存在一个 Byte 型数组里;
-
planar:Y、U、V 分量分别存在三个 Byte 型数组中;
常见的像素格式
这四种格式每一种又可以分为 2 类(packed和planar),以 YUYV 为例,一个 6*4 的图像的存储方式如下:
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
Y Y Y Y Y Y
U U U U U U Y U Y V Y U Y V Y U Y V
U U U U U U Y U Y V Y U Y V Y U Y V
V V V V V V Y U Y V Y U Y V Y U Y V
V V V V V V Y U Y V Y U Y V Y U Y V
- Planar - - Packed -
YUV420:I420(YU12)、YV12、NV12、NV21
- YUV420p: I420、YV12
- YUV420sp: NV12、NV21
同样,对于一个6*4的图像,这四种像素格式的存储方式如下:
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
U U U U U U V V V V V V U V U V U V V U V U V U
V V V V V V U U U U U U U V U V U V V U V U V U
- I420 - - YV12 - - NV12 - - NV21 -
- I420、YV12 三个分量均为平面格式,即分别存在三个数组中;
- NV12、NV21 的存储格式为 Y 平面,UV 打包,即 Y 信息存储在一个数组中,UV 信息存储在一个另一个数组中。
2.链表
2.1普通链表
2.1.1链表的难点
链表的难点 ---> 指针。
口诀:
- 变量变量,能变,就是能读能写,必定在内存里
- 指针指针,保存的是地址,32处理器中地址都是32位的,无论是什么类型的指针变量,都是4字节
2.2改进链表
2.3常见双向链表宏
struct list_head
{
struct list_head *next, *prev;
};
next
:指向链表中下一个节点的指针。prev
:指向链表中上一个节点的指针。- 这个结构体用于表示链表的每个节点,允许在链表中双向遍历。
初始化链表头部
#define LIST_HEAD_INIT(name) { &(name), &(name) }
- 这是一个宏,用于初始化链表头部。
- 它将链表头部的
next
和prev
指针都指向自身,表示这是一个空链表。
声明和初始化一个新的链表头部
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
- 这个宏用于声明和初始化一个新的链表头部。
name
是用户指定的链表头的名称。该宏创建一个struct list_head
类型的变量,并使用LIST_HEAD_INIT
进行初始化。
初始化已定义的链表头部
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
- 这个宏用于初始化已定义的链表头部。
ptr
是指向链表头的指针,宏将其next
和prev
指针都设置为指向自身,以表示它是一个空链表。
在链表中增加链表项
static __inline__ void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new; // 将新节点的下一个节点的 prev 指针指向新节点
new->next = next; // 新节点的 next 指向原本的下一个节点
new->prev = prev; // 新节点的 prev 指向前一个节点
prev->next = new; // 前一个节点的 next 指向新节点
}
-
参数:
new
:要添加的新节点。prev
:新节点要插入的位置之前的节点。next
:新节点要插入的位置之后的节点。
-
功能:将新节点
new
插入到链表中,位置在prev
和next
之间。函数首先更新next
节点的prev
指针,使其指向新节点,然后更新新节点的next
和prev
指针,最后更新prev
节点的next
指针,以完成插入操作。
在链表中替换列表项
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next; // 新节点的 next 指向旧节点的下一个节点
new->next->prev = new; // 旧节点下一个节点的 prev 指向新节点
new->prev = old->prev; // 新节点的 prev 指向旧节点的前一个节点
new->prev->next = new; // 旧节点的前一个节点的 next 指向新节点
}
-
参数:
old
:要被替换的旧节点。new
:新的节点,将替换旧节点。
-
功能:替换链表中的旧节点
old
为新节点new
。函数更新新节点的next
和prev
指针,并相应地调整链表中相邻节点的指针,使链表保持连贯。
海思平台
媒体处理平台架构
可以看到主要分为视频输入(VI)、视频处理(VPSS)、视频编码(VENC)、视频解码(VDEC)、视频输出(VO)、视频拼接(AVS)、音频输入(AI)、音频输出(AO)、音频编码(AENC)、音频解码(ADEC)、区 域管理(REGION)等模块,几个主要模块的大致功能为:
VI模块:捕获视频图像,可对其做剪切、去噪等处理,并输出多路不同分辨率的图像数据。
VDEC模块:解码模块对编码后的视频码流进行解码,并将解析后的图像数据送 VPSS 进行图像处理,再送 VO 显示。可对 H.265/H.264/JPEG 格式的视频码流进行解码。
VPSS模块:接收 VI 和解码模块发送过来的图像,可对图像进行图像增强、锐化等处理,并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓拍。
VENC模块:编码模块接收 VI 捕获并经 VPSS 处理后输出的图像数据,可叠加用户通过 Region
模块设置的 OSD 图像,然后按不同协议进行编码并输出相应码流。
VO模块:接收 VPSS 处理后的输出图像,可进行播放控制等处理,最后按用户配置的输出协议输出给外围视频设备。
AVS模块:接收多路VI采集的图像,进行拼接合成全景图像(这里的AVS是指“视频拼接子系统”,我之前一直以为是编解码的AVS标准)。
AI模块:捕获音频数据,然后AENC模块支持按多种音频协议对其进行编码,最后输出音频码流。
ADEC模块:用户从网络或外围存储设备获取的音频码流可直接送给ADEC模块,ADEC支持解码多种不同的音频格式码流,解码后数据送给AO模块即可播放声音。
视频缓存池
系统绑定
MPP提供系统绑定接口(HI_MPI_SYS_Bind),即通过数据接收者绑定数据源来建立两者之间的关联关系(只允许数据接收者绑定数据源)。绑定后,数据源生成的数据将自动发送给接收者。目前MPP支持的绑定关系如下:
数据源 | 数据接受者 |
---|---|
VI | VO 、VENC、VPSS、PCIV、VI(虚拟PIPE) |
VPSS | VO、VENC、VPSS、PCIV、VI(虚拟PIPE)、AVS |
VDEC | VO、VENC、VPSS、PCIV、VI(虚拟PIPE) |
VO(WBC) | VO、VENC、VPSS、PCIV、VI(虚拟PIPE) |
AVS | VO、VENC、VPSS、PCIV、VI(虚拟PIPE) |
AI | AO、AENC |
ADEC | AO |
海思提供绑定函数 HI_MPI_SYS_Bind 来实现数据源与数据接受者的绑定:
HI_S32 HI_MPI_SYS_Bind(const MPP_CHN_S *pstSrcChn, const MPP_CHN_S
*pstDestChn);
- pstSrcChn指源通道指针
- pstDestChn指目的通道指针
- API需求的头文件为:hi_comm_sys.h、mpi_sys.h,需求的库文件为:libmpi.a
注意:同一个数据接收者只能绑定一个数据源
VENC绑定VI为数据源示例:
HI_S32 SAMPLE_COMM_VI_UnBind_VENC(VI_PIPE ViPipe, VI_CHN ViChn, VENC_CHN VencChn)
{
MPP_CHN_S stSrcChn;
MPP_CHN_S stDestChn;
stSrcChn.enModId = HI_ID_VI;
stSrcChn.s32DevId = ViPipe;
stSrcChn.s32ChnId = ViChn;
stDestChn.enModId = HI_ID_VENC;
stDestChn.s32DevId = 0;
stDestChn.s32ChnId = VencChn;
CHECK_RET(HI_MPI_SYS_UnBind(&stSrcChn, &stDestChn), "HI_MPI_SYS_UnBind(VI-VENC)");
return HI_SUCCESS;
}
解绑的API为 HI_MPI_SYS_UnBind
HI_S32 HI_MPI_SYS_UnBind(const MPP_CHN_S *pstSrcChn, const MPP_CHN_S
*pstDestChn);
注意:pstDestChn如果找不到绑定的源通道,则直接返回成功。如果找到了绑定的源通道,但是绑定的源通道和pstSrcChn不匹配,则返回失败。
若需将上面的VENC和VI解绑,示例如下:
HI_S32 SAMPLE_COMM_VI_UnBind_VENC(VI_PIPE ViPipe, VI_CHN ViChn, VENC_CHN VencChn)
{
MPP_CHN_S stSrcChn;
MPP_CHN_S stDestChn;
stSrcChn.enModId = HI_ID_VI;
stSrcChn.s32DevId = ViPipe;
stSrcChn.s32ChnId = ViChn;
stDestChn.enModId = HI_ID_VENC;
stDestChn.s32DevId = 0;
stDestChn.s32ChnId = VencChn;
CHECK_RET(HI_MPI_SYS_UnBind(&stSrcChn, &stDestChn), "HI_MPI_SYS_UnBind(VI-VENC)");
return HI_SUCCESS;
}
VO篇
1.如何读取一张YUV420格式的图片到内存(通常用于保存固定背景图)
- 根据显示图片文件名获取图片长和宽的大小(调用strstr函数获取文件名中特定字段的位置)
- 由图片长款大小,分别计算亮度、色度、图片字节大小(亮度大小=图片分辨率,色度大小=(图片分辨率>>4)*2,图片大小=二者相加)
- 创建视频缓存池VB(默认D1分辨率704*576,ipc子码流通常为D1大小)
- 从创建好的缓存池中,获取缓存块(大小为要显示图片的大小)
-
一个帧缓存块所在缓存池的 ID
- 获取缓存块的物理地址,并转化对应的虚拟地址。
- 到此准备工作结束
- 图像帧结构体进行赋值,(缓存池ID、图像数据物理地址、图像数据虚拟地址、宽、高、视频图像像素格式-yuv420、图像数据跨距{图像宽大小}、帧场模式)
- 只读模式fopen打开要显示图像文件,fread读取图像数据到虚拟地址中
2.如何通过VO的设备ID来获取VO设备的亮度、色度、对比度、锐度等参数
- 遍历VO设备的链表,从链表节点
pos
中获取对应的VoDevEntry
结构体指针 - 判断找到的VO链表项的voID是否和传入的设备ID相等
- 如果相等,获取VO设备结构体中的亮度、色度、对比度等参数
static int GetVoDevData(int voId, VoDevStruct **devStruct)
{
int result = ERR_DEVID;
struct list_head *pos = NULL;
struct list_head *save = NULL;
//确保 voId 和 devStruct 参数的有效性
VO_CHECK_VOID(voId);
CHECK_POINTER(devStruct);
//使用 list_for_each_safe 宏安全遍历链表 voDevs
list_for_each_safe(pos, save, &voDevs)
{
//通过链表节点获取对应的 VoDevStruct 结构体指针
(*devStruct) = list_entry(pos, VoDevStruct , node);
//检查 (*devStruct) 是否为 NULL,如果是,记录错误并返回
if (NULL == (*devStruct))
{
LogErr("find vo info err. voId = %d\n", voId);
result = ERR_INPOITERNULL;
goto EXIT_0;
}
//如果找到匹配的 voId,则设置 result 为成功状态 ERR_OK,并跳出循环
if ((*devStruct)->voId == voId)
{
result = ERR_OK;
break;
}
}
EXIT_0:
return result;
}
3.VO子系统如何接收VPSS发送的视频流
4. VO模块如何实现视频流的播放控制等操作
5.VO模块如何按照不同协议进行编码并输出相应码流
文章持续更新中……