视频监控系统

news2024/12/27 12:56:54

一、音视频推流框架概述

1.什么是推流

转载:原文链接:【知识拓展】音视频中的推流与拉流_推流拉流_No8g攻城狮的博客-CSDN博客

推流:把采集阶段封包好的内容传输到服务器的过程。其实就是将现场的视频信号传到网络的过程。“推流”对网络要求比较高,如果网络不稳定,直播效果就会很差,观众观看直播时就会发生卡顿等现象,观看体验很是糟糕。

要想用于推流还必须把音视频数据使用传输协议进行封装变成流数据。常用的流传输协议有RTSP、RTMP、HLS等,使用RTMP传输的延时通常在1–3秒,对于手机直播这种实时性要求非常高的场景,RTMP也成为手机直播中最常用的流传输协议。最后通过一定的Qos算法将音视频流数据推送到网络断,通过CDN进行分发。

在直播场景中,网络不稳定是非常常见的,这时就需要Qos来保证网络不稳情况下的用户观看直播的体验,通常是通过主播端和播放端设置缓存,让码率均匀。另外,针对实时变化的网络状况,动态码率和帧率也是最常用的策略。

直播中使用广泛的“推流协议”一般是RTMP(Real Time Messaging Protocol——实时消息传输协议)。该协议是一个基于TCP的协议族,是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。支持该协议的软件包括Adobe Media Server/Ultrant Media Server/red5等。

2.什么是拉流

拉流是指服务器已有直播内容,根据协议类型(如RTMP、RTP、RTSP、HTTP等),与服务器建立连接并接收数据,进行拉取的过程。拉流端的核心处理在播放器端的解码和渲染,在互动直播中还需集成聊天室、点赞和礼物系统等功能。

拉流端现在支持RTMP、HLS、HDL(HTTP-FLV)三种协议,其中,在网络稳定的情况下,对于HDL协议的延时控制可达1s,完全满足互动直播的业务需求。RTMP是Adobe的专利协议,开源软件和开源库都支持的比较好,延时一般在1-3秒。HLS是苹果提出的基于HTTP的流媒体传输协议,优先是跨平台性比较好,HTML5可以直接打开播放,移动端兼容性良好,但是缺点是延迟比较高。注意,切片越多,关键帧量就越多,需要内存就越大,传输就慢。

3.编码解码原理

3.1为啥压缩

在推流和拉流过程中,都要根据协议对源数据进行编码或者解码,以压缩数据便于传输。

3.2方法

  • 空间冗余:图像相邻像素之间有较强的相关性
  • 时间冗余:视频序列的相邻图像之间内容相似
  • 编码冗余:不同像素值出现的概率不同
  • 视觉冗余:人的视觉系统对某些细节不敏感
  • 知识冗余:规律性的结构可由先验知识和背景知识得到

3.3压缩分类

无损压缩(Lossless):压缩前、解压缩后图像完全一致X=X',压缩比低(2:1~3:1)。典型格式例如:Winzip,JPEG-LS。

有损压缩(Lossy):压缩前解压缩后图像不一致X≠X',压缩比高(10:1~20:1),利用人的视觉系统的特性。典型格式例如:MPEG-2,H.264/AVC,AVS

3.4编码步骤

  1. 预测:对一幅图像中的每个块,根据某几个相邻的像素值,在指定的方向上对下一个像素点值用公式做预测。作用:获取差值,差值矩阵存储内存较小(包含信息能量低),为了到达编码阶段,编码压缩的数据量更小,压缩效率高。

方法:a.帧内预测:一幅图像;

b.帧间预测:运动轨迹预测;

一些概念:

  • 关键帧I:用来做预测的帧,信息量大,后续获取的图像都是基于关键帧预测的(原始图像的残差)
  • 预测帧(P):根据前一幅图像预测得到的本帧图像。
  • B帧:根据前面的图像和后的图像进行双向预测得到的本帧图像。
  • GOPI+nB+nP
  • CDN:内容分发网络(分布式网络架构),为了提高内容传输速率和效率,采用类似就近原则。
  1. 变换

DCT变换(离散余弦变换):将低频的、幅值高的信重要息放在矩阵左上角,反之放在右下角,即对频率做划分。改变队形为后续编码做准备。

  1. 量化

根据步长对数据进行分级,这样右下侧较小的数据都被归为0。(缺点:使数据失真。)

再对数据矩阵从左上角进行“Z”字扫描,方便把非0数据放一起。

  1. 熵编码

第一步:行程编码:把重复的数据用数据加重复次数表示。例如aabbcc->a2b2c2

第二步:变长编码:如霍夫曼编码,将出现频率高的字符串用最短的码来替换,从整体上减少了原始数据长度。

解码顺序就是编码的逆序。

二、视频监控项目

将使用 RTMP 流媒体服务来实现视频监控

推流端负责将视频数据通过 RTMP 流媒体协议传输给 RTMP 流媒体服务器,拉流端可以从流媒体服务

器中通过 RTMP 协议获取到视频数据;而流媒体服务器负责接收推流端的视频数据、当有客户端(拉流端)想要获取视频数据时再将其发给相应的客户端。

推流我们可以使用 FFmpeg 来做,拉流则可以实现 VLC 播放器来做,而流媒体客户端则使用 Nginx 来搭建。

方案:使用usb摄像头采集数据,然后搭建nginx服务器,用FFmpeg对数据进行推流,最后在Windows端使用VLC播放器拉流。

1.usb摄像头获取数据

1.V4L2 摄像头应用编程概述

V4L2 是 Video for linux two 的简称,是 Linux 内核中视频类设备的一套驱动框架,为视频类设备驱动开发和应用层提供了一套统一的接口规范。

使用 V4L2 设备驱动框架注册的设备会在 Linux 系统/dev/目录下生成对应的设备节点文件,设备节点的名称通常为 videoX(X 标准一个数字编号,0、1、2、3……),每一个 videoX 设备文件就代表一个视频类设备。应用程序通过对 videoX 设备文件进行 I/O 操作来配置、使用设备类设备,

2. V4L2 摄像头应用程序步骤

1. 首先是打开摄像头设备;

2. 查询设备的属性或功能;

3. 设置设备的参数,譬如像素格式、帧大小、帧率;

4. 申请帧缓冲、内存映射;

5. 帧缓冲入队;

6. 开启视频采集;

7. 帧缓冲出队、对采集的数据进行处理;

8. 处理完后,再次将帧缓冲入队,往复;

9. 结束采集

摄像头视频采集流程图

从图上可以看出几乎对摄像头的所有操作都是通过 ioctl()来完成,搭配不同的 V4L2 指令(request参数)请求不同的操作,这些指令定义在头文件linux/videodev2.h 中,在摄像头应用程序代码中,需要包含头文件 linux/videodev2.h,该头文件中申明了很多与摄像头应用编程相关的数据结构以及宏定义。

每一个 不 同 的 指 令 宏 就 表 示 向 设 备 请 求 不 同 的 操 作 , 从 上 面 可 以 看 到 , 每 一 个 宏 后 面(_IOWR/_IOR/_IOW)还携带了一个 struct 数据结构体,譬如 struct v4l2_capability、struct v4l2_fmtdesc,这就是调用 ioctl()时需要传入的第三个参数的类型;调用 ioctl()前,定义一个该类型变量,调用 ioctl()时、将变量的指针作为 ioctl()的第三个参数传入。

常见的iotcl指令如下:

eg:

struct v4l2_capability cap;
……
ioctl(fd, VIDIOC_QUERYCAP, &cap);

2.1.打开摄像头设备

视频类设备对应的设备节点为/dev/videoX,X 为数字编号,通常从 0 开始;摄像头应用编程的第一步便

是打开设备,调用 open 打开,得到文件描述符 fd;

int fd = -1;
/* 打开摄像头 */
fd = open("/dev/video0", O_RDWR);
if (0 > fd) {
fprintf(stderr, "open error: %s: %s\n", "/dev/video0", strerror(errno));
return -1;
}

2.2查询设备的属性或功能

打开设备之后,接着需要查询设备的属性,确定该设备是否是一个视频采集类设备、以及其它一些属性

ioctl(int fd, VIDIOC_QUERYCAP, struct v4l2_capability *cap);

此ioctl将获取到一个struct v4l2_capability类型数据,该数据结构描述了设备的一些属性,其中capabilities字段描述了设备拥有的能力

struct v4l2_capability {
 __u8 driver[16]; /* 驱动的名字 */
 __u8 card[32]; /* 设备的名字 */
 __u8 bus_info[32]; /* 总线的名字 */
 __u32 version; /* 版本信息 */
 __u32 capabilities; /* 设备拥有的能力 */
 __u32 device_caps;
 __u32 reserved[3]; /* 保留字段 */
};

V4L2_CAP_VIDEO_CAPTURE字段内容

#define V4L2_CAP_VIDEO_CAPTURE  0x00000001 /* Is a video capture device */

对于摄像头设备来说,它的capabilities字段必须包含 V4L2_CAP_VIDEO_CAPTURE,表示它支持视频采集功能。所以我们可以通过判断 capabilities字段是否包含 V4L2_CAP_VIDEO_CAPTURE、来确定它是否是一个摄像头设备;

/* 查询设备功能 */
ioctl(fd, VIDIOC_QUERYCAP, &cap);
/* 判断是否是视频采集设备 */
if (!(V4L2_CAP_VIDEO_CAPTURE &cap.capabilities)) {
fprintf(stderr, "Error: No capture video device!\n");
return -1;
}

2.3 设置设备的参数,譬如像素格式、帧大小、帧率;

一个摄像头通常会支持多种不同的像素格式,譬如 RGB、YUYV 以及压缩格式 MJPEG 等,并且还支

持多种不同的视频采集分辨率,譬如 640*480、320*240、1280*720 等,除此之外,同一分辨率可能还支持多种不同的视频采集帧率(15fps、30fps)。

摄像头支持像素格式:VIDIOC_ENUM_FMT

ioctl(int fd, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc *fmtdesc);

摄像头支持分辨率:VIDIOC_ENUM_FRAMESIZES

ioctl(int fd, VIDIOC_ENUM_FRAMESIZES, struct v4l2_frmsizeenum *frmsize);

摄像头采集帧率:VIDIOC_ENUM_FRAMEINTERVALS

ioctl(int fd, VIDIOC_ENUM_FRAMEINTERVALS, struct v4l2_frmivalenum *frmival);

查看或设置当前的格式:VIDIOC_G_FMT、VIDIOC_S_FMT

int ioctl(int fd, VIDIOC_G_FMT, struct v4l2_format *fmt);
int ioctl(int fd, VIDIOC_S_FMT, struct v4l2_format *fmt);

设置或获取当前的流类型相关参数:VIDIOC_G_PARM、VIDIOC_S_PARM

ioctl(int fd, VIDIOC_S_PARM, struct v4l2_streamparm *streamparm);

2.4申请帧缓冲、内存映射

使用streaming I/O 方式(高级I/O)来读取摄像头数据。

申请帧缓冲 struct v4l2_requestbuffers *reqbuf

ioctl(int fd, VIDIOC_REQBUFS, struct v4l2_requestbuffers *reqbuf);

streaming I/O 方式会在内核空间中维护一个帧缓冲队列,驱动程序会将从摄像头读取的一帧数据写入到

队列中的一个帧缓冲,接着将下一帧数据写入到队列中的下一个帧缓冲;当应用程序需要读取一帧数据时,需要从队列中取出一个装满一帧数据的帧缓冲,这个取出过程就叫做出队;当应用程序处理完这一帧数据后,需要再把这个帧缓冲加入到内核的帧缓冲队列中,这个过程叫做入队

将帧缓冲映射到进程地址空间 struct v4l2_buffer *buf

使用VIDIOC_QUERYBUF查询的长度、偏移量等信息

ioctl(int fd, VIDIOC_QUERYBUF, struct v4l2_buffer *buf);

调用 mmap()将帧缓冲映射到用户地址空间。

2.5入队

使用 VIDIOC_QBUF 指令将帧缓冲放入到内核的帧缓冲队列中

ioctl(int fd, VIDIOC_QBUF, struct v4l2_buffer *buf);

2.6开启视频采集

使用 VIDIOC_DQBUF 指令开启视频采集

ioctl(int fd, VIDIOC_STREAMON, int *type); //开启视频采集
ioctl(int fd, VIDIOC_STREAMOFF, int *type); //停止视频采集

2.7读取数据、对数据进行处理

接读取每一个帧缓冲的在用户空间的映射区即可读取到摄像头采集的每一帧图像数据,需要将帧缓冲从内核的帧缓冲队列中取出,这个操作叫做帧缓冲出队。使用 VIDIOC_DQBUF 指令执行出队操作:

ioctl(int fd, VIDIOC_DQBUF, struct v4l2_buffer *buf);

3.实验代码

下面是我所使用的usb摄像头实例,该摄像头支持YUYV格式,但不支持RGB格式,而使用的LCD屏幕为7 寸 800*480 RGB 屏,像素深度为 16(一个像素点的颜色值将使用 16bit(也就是 2 个字节)来表示),所以需要对采集的数据做格式转换才能显示到LCD上。

/***************************************************************

 Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.

 文件名 : v4l2_camera.c

 作者 : 邓涛

 版本 : V1.0

 描述 : V4L2摄像头应用编程实战

 其他 : 东改

 论坛 : www.openedv.com

 日志 : 初版 V1.0 2021/7/09 邓涛创建 

 ***************************************************************/



#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include <string.h>

#include <errno.h>

#include <sys/mman.h>

#include <linux/videodev2.h>

#include <linux/fb.h>



#define FB_DEV "/dev/fb0"   // LCD设备节点

#define FRAMEBUFFER_COUNT 3 //帧缓冲数量



/*** 摄像头像素格式及其描述信息 ***/

typedef struct camera_format

{

    unsigned char description[32]; //字符串描述信息

    unsigned int pixelformat;      //像素格式

} cam_fmt;



/*** 描述一个帧缓冲的信息 ***/

typedef struct cam_buf_info

{

    unsigned short *start; //帧缓冲起始地址

    unsigned long length;  //帧缓冲长度

} cam_buf_info;



static int width;                          // LCD宽度

static int height;                         // LCD高度

static unsigned short *screen_base = NULL; // LCD显存基地址

static int fb_fd = -1;                     // LCD设备文件描述符

static int v4l2_fd = -1;                   //摄像头设备文件描述符

static cam_buf_info buf_infos[FRAMEBUFFER_COUNT];

static cam_fmt cam_fmts[10];

static int frm_width, frm_height; //视频帧宽度和高度



static int fb_dev_init(void)

{

    struct fb_var_screeninfo fb_var = {0};

    struct fb_fix_screeninfo fb_fix = {0};

    unsigned long screen_size;



    /* 打开framebuffer设备 */

    fb_fd = open(FB_DEV, O_RDWR);

    if (0 > fb_fd)

    {

        fprintf(stderr, "open error: %s: %s\n", FB_DEV, strerror(errno));

        return -1;

    }



    /* 获取framebuffer设备信息 */

    ioctl(fb_fd, FBIOGET_VSCREENINFO, &fb_var);

    ioctl(fb_fd, FBIOGET_FSCREENINFO, &fb_fix);



    screen_size = fb_fix.line_length * fb_var.yres;

    width = fb_var.xres;

    height = fb_var.yres;



    /* 内存映射 */

    screen_base = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);

    if (MAP_FAILED == (void *)screen_base)

    {

        perror("mmap error");

        close(fb_fd);

        return -1;

    }



    /* LCD背景刷白 */

    memset(screen_base, 0xFF, screen_size);

    return 0;

}



static int v4l2_dev_init(const char *device)

{

    struct v4l2_capability cap = {0};



    /* 打开摄像头 */

    v4l2_fd = open(device, O_RDWR);

    if (0 > v4l2_fd)

    {

        fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));

        return -1;

    }



    /* 查询设备功能 */

    ioctl(v4l2_fd, VIDIOC_QUERYCAP, &cap);



    /* 判断是否是视频采集设备 */

    if (!(V4L2_CAP_VIDEO_CAPTURE & cap.capabilities))

    {

        fprintf(stderr, "Error: %s: No capture video device!\n", device);

        close(v4l2_fd);

        return -1;

    }



    return 0;

}



static void v4l2_enum_formats(void)

{

    struct v4l2_fmtdesc fmtdesc = {0};



    /* 枚举摄像头所支持的所有像素格式以及描述信息 */

    fmtdesc.index = 0;

    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FMT, &fmtdesc))

    {



        // 将枚举出来的格式以及描述信息存放在数组中

        cam_fmts[fmtdesc.index].pixelformat = fmtdesc.pixelformat;

        strcpy(cam_fmts[fmtdesc.index].description, fmtdesc.description);

        fmtdesc.index++;

    }

}



static void v4l2_print_formats(void)

{

    struct v4l2_frmsizeenum frmsize = {0};

    struct v4l2_frmivalenum frmival = {0};

    int i;



    frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    for (i = 0; cam_fmts[i].pixelformat; i++)

    {



        printf("format<0x%x>, description<%s>\n", cam_fmts[i].pixelformat,

               cam_fmts[i].description);



        /* 枚举出摄像头所支持的所有视频采集分辨率 */

        frmsize.index = 0;

        frmsize.pixel_format = cam_fmts[i].pixelformat;

        frmival.pixel_format = cam_fmts[i].pixelformat;

        while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize))

        {



            printf("size<%d*%d> ",

                   frmsize.discrete.width,

                   frmsize.discrete.height);

            frmsize.index++;



            /* 获取摄像头视频采集帧率 */

            frmival.index = 0;

            frmival.width = frmsize.discrete.width;

            frmival.height = frmsize.discrete.height;

         

            while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival))

            {



                printf("<%dfps>", frmival.discrete.denominator /

                                      frmival.discrete.numerator);

                frmival.index++;

            }

            printf("\n");

        }

        printf("\n");

    }

}



static int v4l2_set_format(void)

{

    struct v4l2_format fmt = {0};

    struct v4l2_streamparm streamparm = {0};



    /* 设置帧格式 */

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // type类型

    fmt.fmt.pix.width = width;              //视频帧宽度

    fmt.fmt.pix.height = height;            //视频帧高度

    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;



    // V4L2_PIX_FMT_RGB565;  //像素格式



    if (0 > ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt))

    {

        fprintf(stderr, "ioctl error: VIDIOC_S_FMT: %s\n", strerror(errno));

        return -1;

    }



    /*** 判断是否已经设置为我们要求的YUYV像素格式

    如果没有设置成功表示该设备不支持YUYV像素格式 */

    if (V4L2_PIX_FMT_YUYV != fmt.fmt.pix.pixelformat)

    {

        fprintf(stderr, "Error: the device does not support RGB565 format!\n");

        return -1;

    }

    else

    {

        printf("像素格式V4L2_PIX_FMT_YUYV");

    }



    frm_width = fmt.fmt.pix.width;   //获取实际的帧宽度

    frm_height = fmt.fmt.pix.height; //获取实际的帧高度

    printf("视频帧大小<%d * %d>\n", frm_width, frm_height);



    /* 获取streamparm */

    streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ioctl(v4l2_fd, VIDIOC_G_PARM, &streamparm);



    /** 判断是否支持帧率设置 **/

    if (V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability)

    {

        streamparm.parm.capture.timeperframe.numerator = 1;

        streamparm.parm.capture.timeperframe.denominator = 30; // 30fps

        if (0 > ioctl(v4l2_fd, VIDIOC_S_PARM, &streamparm))

        {

            fprintf(stderr, "ioctl error: VIDIOC_S_PARM: %s\n", strerror(errno));

            return -1;

        }

    }



    return 0;

}



static int v4l2_init_buffer(void)

{

    struct v4l2_requestbuffers reqbuf = {0};

    struct v4l2_buffer buf = {0};



    /* 申请帧缓冲 */

    reqbuf.count = FRAMEBUFFER_COUNT; //帧缓冲的数量

    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    reqbuf.memory = V4L2_MEMORY_MMAP;

    if (0 > ioctl(v4l2_fd, VIDIOC_REQBUFS, &reqbuf))

    {

        fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));

        return -1;

    }



    /* 建立内存映射 */

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++)

    {



        ioctl(v4l2_fd, VIDIOC_QUERYBUF, &buf);

        buf_infos[buf.index].length = buf.length;

        buf_infos[buf.index].start = mmap(NULL, buf.length,

                                          PROT_READ | PROT_WRITE, MAP_SHARED,

                                          v4l2_fd, buf.m.offset);

        if (MAP_FAILED == buf_infos[buf.index].start)

        {

            perror("mmap error");

            return -1;

        }

    }



    /* 入队 */

    for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++)

    {



        if (0 > ioctl(v4l2_fd, VIDIOC_QBUF, &buf))

        {

            fprintf(stderr, "ioctl error: VIDIOC_QBUF: %s\n", strerror(errno));

            return -1;

        }

    }



    return 0;

}



static int v4l2_stream_on(void)

{

    /* 打开摄像头、摄像头开始采集数据 */

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;



    if (0 > ioctl(v4l2_fd, VIDIOC_STREAMON, &type))

    {

        fprintf(stderr, "ioctl error: VIDIOC_STREAMON: %s\n", strerror(errno));

        return -1;

    }



    return 0;

}

// framebuffer:一帧数据 长度min_w*2



#define WIDTH   640     //摄像头拍摄以及显示的大小

#define HEIGHT  480

 

void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb565)

{

      unsigned int i;

    unsigned char* y0 = yuv + 0;

    unsigned char* u0 = yuv + 1;

    unsigned char* y1 = yuv + 2;

    unsigned char* v0 = yuv + 3;



    unsigned short* rgb0 = (unsigned short*)rgb565;

    unsigned short* rgb1 = (unsigned short*)(rgb565 + 2);



    float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0;



    for (i = 0; i <= (WIDTH * HEIGHT) / 2; i++) {

        bt0 = 1.164 * (*y0 - 16) + 2.018 * (*u0 - 128);

        gt0 = 1.164 * (*y0 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);

        rt0 = 1.164 * (*y0 - 16) + 1.596 * (*v0 - 128);



        bt1 = 1.164 * (*y1 - 16) + 2.018 * (*u0 - 128);

        gt1 = 1.164 * (*y1 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);

        rt1 = 1.164 * (*y1 - 16) + 1.596 * (*v0 - 128);



        if (rt0 > 31) rt0 = 31;

        if (rt0 < 0) rt0 = 0;



        if (gt0 > 63) gt0 = 63;

        if (gt0 < 0) gt0 = 0;



        if (bt0 > 31) bt0 = 31;

        if (bt0 < 0) bt0 = 0;



        if (rt1 > 31) rt1 = 31;

        if (rt1 < 0) rt1 = 0;



        if (gt1 > 63) gt1 = 63;

        if (gt1 < 0) gt1 = 0;



        if (bt1 > 31) bt1 = 31;

        if (bt1 < 0) bt1 = 0;



        *rgb0 = ((unsigned short)rt0 << 11) | ((unsigned short)gt0 << 5) | (unsigned short)bt0;

        *rgb1 = ((unsigned short)rt1 << 11) | ((unsigned short)gt1 << 5) | (unsigned short)bt1;



        yuv = yuv + 4;

        rgb565 = rgb565 + 4;

        if (yuv == NULL)

            break;



        y0 = yuv;

        u0 = yuv + 1;

        y1 = yuv + 2;

        v0 = yuv + 3;



        rgb0 = (unsigned short*)rgb565;

        rgb1 = (unsigned short*)(rgb565 + 2);

    }

}

void lcd_show_rgb(unsigned char *rgbdata, int w ,int h)

{

    unsigned short *ptr; 

    ptr = screen_base;  //lcd基地址操作

    for(int i = 0; i <h; i++) {

        for(int j = 0; j < w; j++) {

                memcpy(ptr+j,rgbdata+j*2,2);//rgb565用3个字节表示一个像素点

        }

        ptr += width;

        rgbdata += w*2;

    }

}



static void v4l2_read_data(void)

{

    struct v4l2_buffer buf = {0};

    unsigned short *base;

    unsigned short *start;

    int min_w, min_h;

    int j;

    //定义一个空间存储解码后的rgb

    unsigned char rgbdata[WIDTH * HEIGHT * 3];



    if (width > frm_width)

        min_w = frm_width;

    else

        min_w = width;

    if (height > frm_height)

        min_h = frm_height;

    else

        min_h = height;



    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    for (;;)

    {



        for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++)

        {



            ioctl(v4l2_fd, VIDIOC_DQBUF, &buf); //出队



            yuyv_to_rgb((unsigned char*)buf_infos[buf.index].start,rgbdata);

            lcd_show_rgb(rgbdata,640,480);

           

            // 数据处理完之后、再入队、往复

            ioctl(v4l2_fd, VIDIOC_QBUF, &buf);

            

        }

    }

}

    int main(int argc, char *argv[])

    {

        if (2 != argc)

        {

            fprintf(stderr, "Usage: %s <video_dev>\n", argv[0]);

            exit(EXIT_FAILURE);

        }



        /* 初始化LCD */

        if (fb_dev_init())

            exit(EXIT_FAILURE);



        /* 初始化摄像头 */

        if (v4l2_dev_init(argv[1]))

            exit(EXIT_FAILURE);



        /* 枚举所有格式并打印摄像头支持的分辨率及帧率 */

        v4l2_enum_formats();

        v4l2_print_formats();



        /* 设置格式 */

        if (v4l2_set_format())

            exit(EXIT_FAILURE);



        /* 初始化帧缓冲:申请、内存映射、入队 */

        if (v4l2_init_buffer())

            exit(EXIT_FAILURE);



        /* 开启视频采集 */

        if (v4l2_stream_on())

            exit(EXIT_FAILURE);



        /* 读取数据:出队 */

        v4l2_read_data(); //在函数内循环采集数据、将其显示到LCD屏



        exit(EXIT_SUCCESS);

    }

4.问题

摄像头格式转换

补充:

YUYV格式

YUV 颜色编码采用的是 明亮度色度 来指定像素的颜色。其中,Y 表示明亮度(Luminance、Luma),而 U 和 V 表示色度(Chrominance、Chroma)。

RGB 到 YUV 的转换

就是将图像所有像素点的 R、G、B 分量转换到 Y、U、V 分量

公式:

YUV 采样格式

YUV 图像的主流采样方式有如下三种:

YUV 4:4:4 采样

YUV 4:2:2 采样

YUV 4:2:0 采样

其中,我们采集的格式为4:2:2

YUV 4:2:2 采样,意味着 UV 分量是 Y 分量采样的一半,Y 分量和 UV 分量按照 2 : 1 的比例采样。如果水平方向有 10 个像素点,那么采样了 10 个 Y 分量,而只采样了 5 个 UV 分量。

举个例子 :
  
 假如图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]

 那么采样的码流为:Y0 U0 Y1 V1 Y2 U2 Y3 V3 

 其中,每采样过一个像素点,都会采样其 Y 分量,而 U、V 分量就会间隔一个采集一个。

 最后映射出的像素点为 [Y0 U0 V1]、[Y1 U0 V1]、[Y2 U2 V3]、[Y3 U2 V3]

一张 1280 * 720 大小的图片(像素深度8位),在 YUV 4:2:2 采样时的大小为:

(1280 * 720 * 8 + 1280 * 720 * 0.5 * 8 * 2)/ 8 / 1024 / 1024 = 1.76 MB
在YUV 4:2:2采样中,每个像素的亮度(Y)分量保持完整,而色度(U和V)分量则以2:1的水平采样率进行采样。
对于一张1280x720大小的图片,在YUV 4:2:2采样下,计算大小的方法如下:
计算Y分量的大小:
Y分量的大小等于图像的宽度乘以高度乘以每个像素的位数(通常为8位)。
Y大小 = 1280 * 720 * 8 = 7372800 bits
计算U和V分量的大小:
U和V分量的水平采样率为2:1,即每2个像素共用一组U和V分量数据。
首先计算每行中共享的U和V分量的数量,即图像的宽度除以2。
共享的U和V分量数量 = 1280 / 2 = 640
然后计算每个共享的U和V分量的大小,即每个像素的位数乘以2。
每个共享的U和V分量大小 = 8 * 2 = 16 bits
最后计算U和V分量的总大小,即共享的U和V分量的数量乘以每个共享的U和V分量的大小,再乘以图像的高度。
U和V大小 = 640 * 16 * 720 = 7372800 bits
计算总大小:
总大小等于Y分量的大小加上U和V分量的大小。
总大小 = Y大小 + U和V大小 = 7372800 + 7372800 = 14745600 bits
将总大小转换为字节:
总大小(字节)= 总大小(bits)/ 8 = 14745600 / 8 = 1843200 bytes
因此,一张1280x720大小的图片在YUV 4:2:2采样时的大小为1843200字节(约为1.76 MB)。

参考:一文读懂 YUV 的采样与格式 - 知乎 (zhihu.com)

YUYV 4:2:2 to RGB888 to RGB565

读取时对出队的每一帧图像数据作格式转换

void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb565)

2.LCD显示问题

补充:LCD显示原理

2.FFmpeg

3.RTMP

4.VLC播放器实现效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/964076.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java学习笔记之----I/O(输入/输出)一

在变量、数组和对象中存储的数据是暂时存在的&#xff0c;程序结束后它们就会丢失。想要永久地存储程序创建的数据&#xff0c;就需要将其保存在磁盘文件中(就是保存在电脑的C盘或D盘中&#xff09;&#xff0c;而只有数据存储起来才可以在其他程序中使用它们。Java的I/O技术可…

STS4 New 安装Spring Bean Configuration File

背景介绍 在创建spring项目后&#xff0c;如果想想创建spring bean Configuration的时候&#xff0c;发下菜单没有这个选项&#xff0c;需要通过下载Spring Roo插件可满足该操作。 参考案例 参考地址&#xff1a; STS4 New 菜单没有Spring Bean Configuration File选项_SQZHA…

02-Windows Server搭建AD服务

1、安装AD 等待安装 2、配置 等待安装&#xff08;10分钟&#xff09;完后自动重启 3、安装成功

javaee之黑马乐优商城2

简单分析一下商品分类表的结构 先来说一下分类表与品牌表之间的关系 再来说一下分类表和品牌表与商品表之间的关系 面我们要开始就要创建sql语句了嘛&#xff0c;这里我们分析一下字段 用到的数据库是heima->tb_category这个表 现在去数据库里面创建好这张表 下面我们再去编…

批量文件重命名,轻松在文件夹中插入相同文字符号!

你是否曾经需要批量修改文件夹中的文件名&#xff0c;或者需要在文件名中插入特定的文字符号&#xff1f;现在&#xff0c;我们为你提供了一种快速、简单的方法&#xff0c;让你轻松实现这一需求&#xff01; 首先第一步&#xff0c;我们要打开文件批量重命名高手&#xff0c;…

微信小程序 选择学期控件 自定义datePicker组件 不复杂

我的时间选择组件在common文件夹里 datePicker组件代码 html: <view class"date_bg_view"> </view> <view class"date_content"><view class"date_title"><image src"/image/icon_close_black.png" clas…

SKU助手

属性SKU助手可以帮你快速选中目标商品属性 下载安装与运行 下载、安装与运行 语雀 如何使用 下面以1688批量自动下单为例&#xff0c;演示用法&#xff0c;同样适用于淘宝天猫拼多多批量自动下单 功能说明 SKU助手弹出的时机 同时满足如下两个条件 Excel提供的SKU与真实…

知识储备--基础算法篇-数组

1.学习 2.数组 2.1第53题-最大子数组和 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 心得&#xff1a;一直在纠结这个连续的事情&…

YOLOv5算法改进(13)— 替换主干网络之PP-LCNet

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。PP-LCNet是一个由百度团队针对Intel-CPU端加速而设计的轻量高性能网络。它是一种基于MKLDNN加速策略的轻量级卷积神经网络&#xff0c;适用于多任务&#xff0c;并具有提高模型准确率的方法。与之前预测速度相近的模型相比…

你真的懂分数吗?(二)——分数模型应用初探

早点关注我&#xff0c;精彩不错过&#xff01; 上回我们聊到了分数的数学结构和数学建模&#xff0c;构成了分数的基本数学模型。相关内容请戳&#xff1a; 你真的懂分数吗&#xff1f;&#xff08;一&#xff09;——分数的数学结构和建模 但是&#xff0c;这样的分数是定义在…

基于单片机的点阵电子显示屏上下左右移加减速系统

一、系统方案 本设计的任务就是完成一个1616的点阵设计&#xff0c;并能滚动显示“********************”内容。 主要内容是&#xff0c;能同时流动显示汉字&#xff1b;能实现显示汉字无闪烁&#xff1b;能实屏幕亮度较高。本LED显示屏能够以动态扫描的方式显示一个1616点阵汉…

性能可靠it监控系统,性能监控软件的获得来源有哪些

性能可靠的IT监控系统是企业IT运维的重要保障之一。以下是一个性能可靠的IT监控系统应该具备的特点&#xff1a; 高可用性 高可用性是IT监控系统的一个重要特点&#xff0c;它可以保证系统在24小时不间断监控的同时&#xff0c;保证系统服务的可用性和稳定性。为了实现高可用性…

TiDB x 安能物流丨打造一栈式物流数据平台

作者&#xff1a;李家林 安能物流数据库团队负责人 本文以安能物流作为案例&#xff0c;探讨了在数字化转型中&#xff0c;企业如何利用 TiDB 分布式数据库来应对复杂的业务需求和挑战。 安能物流作为中国领先的综合型物流集团&#xff0c;需要应对大规模的业务流程&#xff…

linux中dmesg命令用法

在Linux系统中&#xff0c;dmesg&#xff08;diagnostic message&#xff09;是一个非常有用的命令行工具&#xff0c;用于显示和控制内核环形缓冲区中的消息。这些消息通常包含系统启动时的内核生成的信息&#xff0c;例如硬件设备的状态&#xff0c;驱动程序的加载&#xff0…

【Python基础教程】快速找到多个字典中的公共键(key)的方法

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 方法一&#xff1a;for in循环 from random import randint, samplea1 {k: randint(1, 4) for k in abcdefg} a2 {k: randint(1, 4) for k in abc123456…

Qt应用开发(基础篇)——输入对话框 QInputDialog

一、前言 QInputDialog类继承于QDialog&#xff0c;是一个简单方便的对话框&#xff0c;用于从用户获取单个值。 对话框窗口 QDialog QInputDialog输入对话框带有一个文本标签、一个输入框和标准按钮。输入内容可以字符、数字和选项&#xff0c;文本标签用来告诉用户应该要输入…

一文看遍半监督学习模型(Semi-Supervised Learning)

一、半监督学习的总体框架 二、一致性正则化模型 该算法旨在&#xff1a;一个模型对于同一个未标记图像&#xff0c;在图像添加额外噪声前后的预测值应该保持一致。 添加噪声的方法&#xff0c;如图像增强&#xff08;空间维度增强、像素维度增强&#xff09;。 同样&#x…

线性DP问题

目录 数字三角形DP 动态规划 [自上向下二维数组]DP 动态规划 [自上向下一维数组]DP 动态规划 [自下而上二维数组]DP 动态规划 [自下而上一维数组]记忆化搜索 DFS 最长上升子序列一维状态数组实现扩展&#xff1a;最长序列输出 最长上升子序列 II贪心二分优化算法思路代码实现扩…

2023蓝帽杯初赛

比赛总结就是首先审题要仔细&#xff0c;确定题目意思再去找才不会找错。 内存取证vol工具的使用不够熟练 然后容易走进死胡同&#xff0c;如果一个软件不能得到答案可以换一个看看&#xff0c;说不定就有答案了。 还有服务器取证很生疏&#xff0c;还是要多花时间做点题 取…

黑客之批处理编写

文章目录 一、批处理作用二、如何创建批处理三、批处理语法 一、批处理作用 自上而下成批的处理每一条命令&#xff0c;直到执行最后一条。这里的命令指的是DOS命令&#xff0c;在之前的【黑客常用DOS命令】博客中&#xff0c;我介绍了大量的常用DOS命令。不过我们之前输入命令…