暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

read_camera_thread()读取摄像头数据线程源码解析!

txp玩Linux 2022-07-31
1109

一、前言:

今天介绍一下我们的开源项目:音视频人脸、目标识别,音视频推流!

目前我们整个项目的人脸识别这块代码已经完成了:


RV1126人脸检测代码:
git clone https://gitee.com/harry12345123/rv1126_face_project.git

RV1126人脸识别代码:
git clone https://gitee.com/harry12345123/rv1126_face_recognize.git

板子移植教程暂时还没出来;现在马上把人脸识别这块的工程代码详细讲解完毕,就会出rv1126的板子的视频教程,这个板子是要大家自己去买的哈(我自己也是买的板子,给大家做教程!),这里选择rv1126,也是性价比比较高,有的朋友说rk3399或者rk3568可不可以,当然是可以,不过这里rk3399他没有npu支持的,也就是说,支持不了人脸识别和物体识别,这个要注意;这个可以在rk的官方介绍可以看到,rk3399pro是支持的:

  • rv1126:
rv1126介绍1
rv1126介绍2
  • rk3399:
rk3399介绍1
rk3399介绍2

后期的打算:

1、人脸识别、物体识别(目前已经完成人脸识别),马上开搞物体识别;

2、编解码框架梳理(硬解码的实现,这里其实都在mpp里面)和mpp 的讲解

3、rtmp/srt 推流实现

4、ffmpeg 的讲解(根据我们这个项目里面用到的知识来讲解,ffmpeg本身是比较大的!)

5、还有工作当中积累的技术经验分享!

ok ,我们今天继续人脸识别工程代码详细解读,上次已经把这两个接口详细介绍完了:

二、:read_camera_thread线程接口源码解读:

在讲解这块的源码之前,我先画执行流程图:

AVFifoBuffer *m_videoFifo = NULL;
int main(int argc, char *argv[])
{

    int ret;
    m_videoFifo = av_fifo_alloc(30 * av_image_get_buffer_size(AV_PIX_FMT_YUV420P, 640, 480, 1));
    
    opencv_queue = new OPENCV_QUEUE();
    init_asfort_device(APPID, SDKKEY);
    init_face_data();

    pthread_t pid;
    ret = pthread_create(&pid, NULL, read_camera_thread, NULL);
    if (ret != 0)
    {
        printf("Create Read_Camera_Thread Failed\n");
    }
}

这里主要给 m_videoFifo缓冲队列去进行内存分配,AVFifoBuffer是ffmpeg里面的一个视频缓冲去,当然他还有一个音频缓冲去叫做AVAudioFifo;我们这里现在只专门处理视频,所以就不用这个音频缓冲区了。

恩,这就简单介绍到这里,下面我开始介绍read_camera_thread线程处理函数:


void *read_camera_thread(void *args)
{
    pthread_detach(pthread_self());
    avdevice_register_all();
    //寻找自己的采集图像设备
    //根据输入格式的名字查找AVInputFormat,在测试代码中,
    //windows平台使用”vfwcap”(video for windows capture),linux平台使用”v4l2”(Video4Linux2);
    AVInputFormat *in_fmt = av_find_input_format("video4linux2");
    if (in_fmt == NULL)
    {
        printf("can't find_input_format\n");
        // return;
    }

    // 设置摄像头的分辨率
    AVDictionary *option = NULL;
    char video_size[10];
    sprintf(video_size, "%dx%d", WIDTH, HEIGHT);
    av_dict_set(&option, "video_size", video_size, 0);

    AVFormatContext *fmt_ctx = NULL;
    //主要用来打开输入流并存储到格式化上下文AVFormatContext中。
    if (avformat_open_input(&fmt_ctx, CAMERA_DEV, in_fmt, &option) < 0)
    {
        printf("can't open_input_file\n");
        // return;
    }
    else
    {
        printf("Success Open Camera\n");
    }
    // printf device info
    av_dump_format(fmt_ctx, 0, CAMERA_DEV, 0);

    struct SwsContext *sws_ctx;
    // 图像格式转换:CAMERA_FMT --> ENCODE_FMT
    sws_ctx = sws_getContext(WIDTH, HEIGHT, CAMERA_FMT,
                             WIDTH, HEIGHT, ENCODE_FMT, 0, NULL, NULL, NULL);

    uint8_t *yuy2buf[4];
    int yuy2_linesize[4];
    //此函数的功能是按照指定的宽、高、像素格式来分析图像内存
    int yuy2_size = av_image_alloc(yuy2buf, yuy2_linesize, WIDTH, HEIGHT, CAMERA_FMT, 1);

    uint8_t *yuv420pbuf[4];
    int yuv420p_linesize[4];
    int yuv420p_size = av_image_alloc(yuv420pbuf, yuv420p_linesize, WIDTH, HEIGHT, ENCODE_FMT, 1);

    // 初始化packet,存放编码数据
    AVPacket *camera_packet = av_packet_alloc();

    // 初始化frame,存放原始数据
    int y_size = WIDTH * HEIGHT;
    //函数的作用是通过指定像素格式、图像宽、图像高来计算所需的内存大小
    int frame_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, WIDTH, HEIGHT, 1);

    for (;;)
    {
        // 摄像头获取图像数据
        av_read_frame(fmt_ctx, camera_packet);
        memcpy(yuy2buf[0], camera_packet->data, camera_packet->size);
        // 图像格式转化
        sws_scale(sws_ctx, (const uint8_t **)yuy2buf, yuy2_linesize,
                  0, HEIGHT, yuv420pbuf, yuv420p_linesize);
        //该接口使用了如下调用,该接口主要作用是清理AVPacket中的所有空间数据,
        //清理完毕后进行初始化操作,并且将 data 与 size 置为0,方便下次调用
        av_packet_unref(camera_packet);
         //以字节数的方式返回当前AVFifoBuffer中剩余的容量,
         //也就是你可以写入的内容的大小
        if (av_fifo_space(m_videoFifo) >= frame_size)
        {
            pthread_mutex_lock(&workmutex);
            //写size字节的数据到AVFifoBuffer中
            av_fifo_generic_write(m_videoFifo, yuv420pbuf[0], y_size, NULL);
            av_fifo_generic_write(m_videoFifo, yuv420pbuf[1], y_size / 4, NULL);
            av_fifo_generic_write(m_videoFifo, yuv420pbuf[2], y_size / 4, NULL);
            pthread_mutex_unlock(&workmutex);
        }
    }
//进行内存释放回收
    av_packet_free(&camera_packet);
    avformat_close_input(&fmt_ctx);
    sws_freeContext(sws_ctx);
    av_freep(yuy2buf);
    av_freep(yuv420pbuf);
}


代码说明:

  • pthread_detach(pthread_self()):将主线程和子线程分离开,子线程结束后,自动回收内存资源。

  • avdevice_register_all():在使用libavdevice之前,必须先运行avdevice_register_all()对设备进行注册,否则就会出错,后面操作都是无效的,比如打开摄像头等操作!

  • AVInputFormat *in_fmt = av_find_input_format("video4linux2"):这里的AVInputFormat是解复用结构体,暂时不用管里面的具体成员,这里调用av_find_input_format这个接口,主要是用传参“video4linux2”来判断摄像头的输入格式是v4l2,这里也就是打开摄像头。这里顺便提一下什么是复用和解复用,所谓的复用就是把解复用出的数据重新合并成一个格式,解复用就是相反的意思了。

  • av_dict_set():这个接口主要来设置摄像头的分辨率大小

  • avformat_open_input()和 AVFormatContext:主要用于打开获取到摄像头输入媒体流并获取媒体流的信息,并摄像头获取到的信息流保存到AVFormatContext;而AVFormatContext是存放音视频格式信息上下文结构体

  • av_dump_format():打印上面的媒体流信息。

  • av_image_alloc():这里主要辅助查看不同数据格式所需要的内存大小,比如我们这里的代码使用了这个两种格式:

#define CAMERA_FMT AV_PIX_FMT_YUYV422 、、
#define ENCODE_FMT AV_PIX_FMT_YUV420P

uint8_t *yuy2buf[4];
    int yuy2_linesize[4];
    //此函数的功能是按照指定的宽、高、像素格式来分析图像内存
    int yuy2_size = av_image_alloc(yuy2buf, yuy2_linesize, WIDTH, HEIGHT, CAMERA_FMT, 1);

    uint8_t *yuv420pbuf[4];
    int yuv420p_linesize[4];
    int yuv420p_size = av_image_alloc(yuv420pbuf, yuv420p_linesize, WIDTH, HEIGHT, ENCODE_FMT, 1);

这两种格式可以在ffmpeg源码找到:

关于这两种packed和planar格式,具体的差别可以去查资料学习一下。

  • for 循环里面的处理:
  for (;;)
    {
 // 摄像头捕获每一帧数据并保存到camera_packet里面去
 av_read_frame(fmt_ctx, camera_packet);
//把camera_packet里面的数据拷贝到yuy2buf里面去
 memcpy(yuy2buf[0], camera_packet->data, camera_packet->size);
// 图像格式转化,把packed格式转换成planar
sws_scale(sws_ctx, (const uint8_t **)yuy2buf, yuy2_linesize,
                  0, HEIGHT, yuv420pbuf, yuv420p_linesize);
//该接口使用了如下调用,该接口主要作用是清理AVPacket中的所有空间数据,
//清理完毕后进行初始化操作,并且将 data 与 size 置为0,方便下次调用,不然这里面就保存不了下次读取的数据了
av_packet_unref(camera_packet);
//以字节数的方式返回当前AVFifoBuffer中剩余的容量,
//也就是你可以写入的内容的大小,这里的视频缓冲去大小必须足够大
if (av_fifo_space(m_videoFifo) >= frame_size)
 {
   pthread_mutex_lock(&workmutex);
   //不断写size字节的数据到AVFifoBuffer中
   av_fifo_generic_write(m_videoFifo, yuv420pbuf[0], y_size, NULL);
  av_fifo_generic_write(m_videoFifo, yuv420pbuf[1], y_size / 4, NULL);
  av_fifo_generic_write(m_videoFifo, yuv420pbuf[2], y_size / 4, NULL);
    pthread_mutex_unlock(&workmutex);
        }
    }

最后通过av_fifo_generic_write()接口不断往视频缓冲区里面去写yuv数据,方便我们后面要讲的process_asfort_recognize_thread线程处理函数不断从这个视频缓冲去里面去读数据!

三、最后:

今天的源码详细解读就分享到这里了!

明天我们这个项目工程会涨价到300,现在的价格在120!

这里说一下,我们这个工程项目收费是已经很低了,包括的内容也是比较多的,还有解答服务;不满大家说,我自己以前也是从买视频学习成长起来的(这算是对自己的一种投资!),市面上的视频教程,动不动就大好几千,所以相比我们这个,真的非常便宜了!

最为重要的是,我们这个工程,都是实战性的东西,非常符合平时各大芯片平台开发内容!我自己本身也是做这块的,还有另外一个工程师他也是做音视频这块的!所以这个内容放心!你现在看我的文章源码解析,都是有视频教程的,有不懂的地方,可以在解答群里面随时问!


文章转载自txp玩Linux,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论