飞凌RZ/G2L的开发板试用测评报告—视频采集开发

飞凌RZ/G2L的开发板试用测评报告—视频采集开发

大信(QQ:8125036)

      飞凌RZ/G2L的开发板有很高性能和低能耗引起我的兴趣,在结合其强大的音视频能力,感觉该开发板非常适合开发音视频产品,很快很幸运得拿到这块开发板进行试用,通过个把月的试用与探索,基本了解了开发板的基本功能,性能,接口以及开发环境等,这里就进一步结合该开发板强大的音视频功能,针对一些音视频基础功能的开发与测试。

一、硬件音视频能力了解

       OK-G2LD-C 开发板拥有丰富的多媒体资源,支持多种显示、摄像头、音频接口,满足多场景下的人机交互和图像采集需求;核心板同时配备500MHz 3D GPU Mali-G31,支持Vulkan、OpenGL、OpenCL,同时VPU支持1080P高清显示,可进行H.264 1080P分辨率的硬件编解码。

      同时板子带有2个有线网口和wifi无线网络,这也给音视频实时传输提供了硬件基础。

      因此基于RZG2l应该能够开发出多种音视频应用,比如视频的采集编码,视频直播,电视电话会议,视频实时处理等。

 图1

二、配置开发网络环境

       在前面的测试中已经建立基本的串口调试环境。为了后面更方便的在主机与开发板间传送文件,还需要建立开发板和主机的网络通讯,以便通过主机对开发板下载和上传文件。这里主机的开发环境是基于Windows 10操作系统的Ubuntu虚机系统,在Win10上安装vmware 虚机和串口超级终端,vmware里安装了 uBuntu18.4版本的linux环境。

        在启动开发板之后,使用ifconfig 命令查看网络配置,可见网卡都不通,因此需要进行网络配置。

        首先把一条有线网络插入到开发板的网口中,其中下图左边的网口对应的是系统里的 eth0, 右侧的网口对应的是 eth1

图2

插好网线后,进入系统网口配置文件所在目录:

cd /etc/systemd/network

打开10-eth.network  文件

vim 10-eth.network

根据自己网络段,配置好开发板的地址,这里使用的静态地址,在与主机相同网段内找一个空闲的IP地址,配置上即可,这样避免动态地址分配,导致每次重启可能会造成地址改变,带来不必要的麻烦。

[Match]

Name=eth0

KernelCommandLine=!root=/dev/nfs

[Network]

Address=192.168.3.232

Gateway=192.168.3.1

DNS=192.168.3.1

ConfigureWithoutCarrier=true

IgnoreCarrierLoss=true

图3

配置完后,重启开发板,再使用ifconfig查看网络设备,eth0设备已经有IP地址,并且检查ping是否能连通通外部主机。同时通过SecureCRT建立SSH登录,也能够顺利登录开发板了。

图4

三、连接检查网络摄像头

因为手边暂时没有MIPI CSI的摄像头器件。因此查看开发板文档,开发板系统是一个标准的ARM Linux 4.19系统,那么它就可以支持uvcamera, 因此找了一款通用型的网络摄像头 Logitech Webcam C270 USB网络摄像头作为视频采集的设备,把摄像头插入开发板的USB口,检测开发板是否能够支持该摄像头,。连接好摄像头后,进入系统查看驱动加载信息。

 图5 查看系统的版本

图6 连接USB摄像头

SecureCRT工具里进入开发板环境里,查看USB总线信息以及开发板USB设备信息如下图:

 图7

图8

     从系统设备驱动加载信息上看,开发板已经为这个摄像头识别出 audio 和 uvcvideo 设备,并成功加载驱动。

     使用查看USB设备详细驱动参数命令,可查看到连入开发板的所有USB设备,以及连接位置,USB总线结构等信息。进一步查看摄像头所对应的USB设备号和相关参数,如下图红色圈设备即为该摄像头设备,记下这些参数,后面软件开发时需要,否则程序将不能正确的采集到视频画面。

usb-devices

图9

四、检测摄像头支持的采集视频规格

     连接好开发板和USB摄像头后,还需要获得开发板支持这款摄像头对视频采集的规格,不同摄像头采集的规则并不相同,不同开发板支持采集的规格也不同,因此需要对当前的硬件连接做一下摄像头视频支持规则检测。

     可采集视频规格检测代码在网上就有,在github上搜索Vv4l2_apture c++ 开源工程,即可以到该原始工程,git下载到主机环境中,然后根据板子的SDK,开始修改…(此处省去代码修改的一万字),主要是修改相关参数和函数。

https://github.com/soramimi/v4l2capture



图10

 图11

经过修改调试代码后,最后在板上成功的运行,并输出开发板对摄像头支持的格式列表,如下:

Supported Formats:

   V4L2_CAP_VIDEO_CAPTURE: pixelformat = YUYV, description = 'YUYV 4:2:2'

        resolution: 640x480 fps: 30, 25, 20, 15, 10, 5 

        resolution: 160x120 fps: 30, 25, 20, 15, 10, 5 

        resolution: 176x144 fps: 30, 25, 20, 15, 10, 5 

        resolution: 320x176 fps: 30, 25, 20, 15, 10, 5 

        resolution: 320x240 fps: 30, 25, 20, 15, 10, 5 

        resolution: 352x288 fps: 30, 25, 20, 15, 10, 5 

        resolution: 432x240 fps: 30, 25, 20, 15, 10, 5 

        resolution: 544x288 fps: 30, 25, 20, 15, 10, 5 

        resolution: 640x360 fps: 30, 25, 20, 15, 10, 5 

        resolution: 752x416 fps: 25, 20, 15, 10, 5 

        resolution: 800x448 fps: 25, 20, 15, 10, 5 

        resolution: 800x600 fps: 20, 15, 10, 5 

        resolution: 864x480 fps: 20, 15, 10, 5 

        resolution: 960x544 fps: 15, 10, 5 

        resolution: 960x720 fps: 10, 5 

        resolution: 1024x576 fps: 10, 5 

        resolution: 1184x656 fps: 10, 5 

        resolution: 1280x720 fps: 10, 5 

        resolution: 1280x960 fps: 7, 5 

   V4L2_CAP_VIDEO_CAPTURE: pixelformat = MJPG, description = 'Motion-JPEG'

        resolution: 640x480 fps: 30, 25, 20, 15, 10, 5 

        resolution: 160x120 fps: 30, 25, 20, 15, 10, 5 

        resolution: 176x144 fps: 30, 25, 20, 15, 10, 5 

        resolution: 320x176 fps: 30, 25, 20, 15, 10, 5 

        resolution: 320x240 fps: 30, 25, 20, 15, 10, 5 

        resolution: 352x288 fps: 30, 25, 20, 15, 10, 5 

        resolution: 432x240 fps: 30, 25, 20, 15, 10, 5 

        resolution: 544x288 fps: 30, 25, 20, 15, 10, 5 

        resolution: 640x360 fps: 30, 25, 20, 15, 10, 5 

        resolution: 752x416 fps: 30, 25, 20, 15, 10, 5 

        resolution: 800x448 fps: 30, 25, 20, 15, 10, 5 

        resolution: 800x600 fps: 30, 25, 20, 15, 10, 5 

        resolution: 864x480 fps: 30, 25, 20, 15, 10, 5 

        resolution: 960x544 fps: 30, 25, 20, 15, 10, 5 

        resolution: 960x720 fps: 30, 25, 20, 15, 10, 5 

        resolution: 1024x576 fps: 30, 25, 20, 15, 10, 5 

        resolution: 1184x656 fps: 30, 25, 20, 15, 10, 5 

        resolution: 1280x720 fps: 30, 25, 20, 15, 10, 5 

        resolution: 1280x960 fps: 30, 25, 20, 15, 10, 5 

五、采集程序开发测试

     获得摄像头的设备参数以及能够采集的视频规格参数以后,就可以开发视频采集程序,在开发采集程序前,还需要查看一下摄像头的用户层抽象操作设备,一般在/dev/下,使用以下命令查看抽象出的设备文件:

ls –ls /dev/video*

      可以看到抽象的视频采集设备,后面将试用设备控制代码来打开操作它。

另外在看一下采集设备对 v4l2 系统的驱动,命令如下:

ls -l /sys/class/video4linux/video*

     这两个命令是在摄像头底层驱动加载工作正常后才会出现,如果底层驱动有任何问题,这两个命令将不会有相应信息显示。

采集视频采用的是V4L2视频框架。V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。在Linux中视频设备是设备文件,可以像访问普通文件一样对其进行读写,摄像头在/dev/video*下,如果只有一个视频设备,通常为/dev/video0。

V4L2在设计时,是能支持很多广泛的设备,但它们之中只有一部分是真正的视频设备:

可以支持多种设备,它可以有以下几种接口:

1. 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.

2. 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备.

3. 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.

4. 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号.

5. 收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流.

图12

 图13

代码比较长,这里放出修改的部分关键代码如下:

#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include "mainwindow.h"

#define VIDEO_FILE "/dev/video0"

#define IMAGEWIDTH      1280
#define IMAGEHEIGHT     720
#define VIDEO_FPS       10

#define ENCODE_QUEUE_FRAME_NUM       4
#define SEND_QUEUE_FRAME_NUM         (ENCODE_QUEUE_FRAME_NUM * 8)

V4L2_Video::V4L2_Video(QObject *parent) : QObject(parent)
{
    m_is_start = false;
    pthread_mutex_init(&m_encode_queue_lock, nullptr);
    pthread_mutex_init(&m_send_queue_lock, nullptr);
}


int V4L2_Video::init()
{
    struct v4l2_capability cap;
    struct v4l2_format fmt;
    struct v4l2_streamparm stream_parm;
    struct v4l2_requestbuffers req;
    struct v4l2_buffer buf;
    unsigned int buffer_n;
    pthread_t thread_id;
    int ret;


    fd = open(VIDEO_FILE, O_RDWR);
    if(fd == -1)
    {
        printf("Error opening V4L2 interface\n");
        return -1;
    }

    // 查询设备属性
    ioctl(fd, VIDIOC_QUERYCAP, &cap);
    printf("Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\n",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF);

    //显示所有支持帧格式
    struct v4l2_fmtdesc fmtdesc;
    fmtdesc.index=0;
    fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("Support format:\n");
    while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
    {
        printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
        fmtdesc.index++;
    }

    // 设置帧格式
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;         // 传输流类型
    fmt.fmt.pix.width = IMAGEWIDTH;                 // 宽度
    fmt.fmt.pix.height = IMAGEHEIGHT;               // 高度
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;    // 采样类型
//    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;      // 采样区域
    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
    if(ret < 0)
    {
        printf("Unable to set format\n");
        goto label_exit;
    }

    // 设置帧速率,设置采集帧率
    stream_parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    stream_parm.parm.capture.timeperframe.denominator = VIDEO_FPS;
    stream_parm.parm.capture.timeperframe.numerator = 1;
    ret = ioctl(fd, VIDIOC_S_PARM, &stream_parm);
    if(ret < 0)
    {
        printf("Unable to set frame rate\n");
        goto label_exit;
    }

    // 申请帧缓冲
    req.count = ENCODE_QUEUE_FRAME_NUM;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(fd, VIDIOC_REQBUFS, &req);
    if(ret < 0)
    {
        printf("request for buffers error\n");
        goto label_exit;
    }

    // 内存映射
    video_buffer = static_cast<VideoBuffer *>(malloc(req.count * sizeof(VideoBuffer)));
    for(buffer_n = 0; buffer_n < ENCODE_QUEUE_FRAME_NUM; buffer_n++)
    {
//        memset(&buf, 0, sizeof(buf));
        buf.index = buffer_n;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        ret = ioctl (fd, VIDIOC_QUERYBUF, &buf);
        if (ret < 0)
        {
            printf("query buffer error\n");
            goto label_exit;
        }
        video_buffer[buffer_n].start = static_cast<uint8_t *>(mmap(nullptr, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset));
        video_buffer[buffer_n].length = buf.length;
        if(video_buffer[buffer_n].start == MAP_FAILED)
        {
            ret = -1;
            printf("buffer map error\n");
            goto label_exit;
        }
        // 放入缓存队列
        ret = ioctl(fd, VIDIOC_QBUF, &buf);
        if (ret < 0)
        {
            printf("put in frame error\n");
            goto label_exit;
        }
    }

    // 创建帧获取线程
    pthread_create(&thread_id, nullptr, frame_process_thread, this);
    // 创建编码线程
    pthread_create(&thread_id, nullptr, yuyv422_to_H264_thread, this);

    return 0;

label_exit:
    close(fd);
    return ret;
}

int V4L2_Video::release()
{
    unsigned int buffer_n;
    enum v4l2_buf_type type;

    // 关闭流
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMON, &type);

    // 关闭内存映射
    for(buffer_n=0; buffer_n < ENCODE_QUEUE_FRAME_NUM; buffer_n++)
    {
        munmap(video_buffer[buffer_n].start, video_buffer[buffer_n].length);
    }

    // 释放自己申请的内存
    free(video_buffer);

    // 关闭设备
    close(fd);
    return 0;
}

void *V4L2_Video::h264_udp_broadcast_thread(void *ptr)
{
    UDP::UdpBroadcastImp(this,ptr);	
}


int V4L2_Video::frameHandle(VideoBuffer v_buffer)
{
    // 待编码视频帧入队
    pthread_mutex_lock(&m_encode_queue_lock);    // m_encode_queue加锁
    while(m_encode_queue.size() == ENCODE_QUEUE_FRAME_NUM)
    {
        pthread_mutex_unlock(&m_encode_queue_lock);   // m_encode_queue解锁
        printf("warning_2: m_encode_queue is overflow!\n");
        usleep(10 * 1000);
    }
    m_encode_queue.push(v_buffer);
    pthread_mutex_unlock(&m_encode_queue_lock);   // m_encode_queue解锁
    return 0;
}

void *V4L2_Video::frame_process_thread(void *ptr)
{
    V4L2_Video *v4l2_video = static_cast<V4L2_Video *>(ptr);
    enum v4l2_buf_type type;
    struct v4l2_buffer buf;
    int ret;

    // 开启视频流
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(v4l2_video->fd, VIDIOC_STREAMON, &type);
    if(ret < 0) {
          printf("VIDIOC_STREAMON failed (%d)\n", ret);
          return nullptr;
    }

    // 开始捕获摄像头数据
    while(1)
    {
        if(v4l2_video->m_is_start == false)
        {
            usleep(10 * 1000);
            continue;
        }
        if(v4l2_video->m_encode_queue.size() == ENCODE_QUEUE_FRAME_NUM)
        {
            printf("warning_1: m_encode_queue is overflow!\n");
            usleep(10 * 1000);
            continue;
        }
        // 从队列中得到一帧数据
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        ret = ioctl(v4l2_video->fd, VIDIOC_DQBUF, &buf);
        if(ret < 0)
        {
            printf("get frame failed!\n");
            usleep(10 * 1000);
            continue;
        }
        // 帧处理程序
        v4l2_video->frameHandle(v4l2_video->video_buffer[buf.index]);
        // 将帧放回队列
        ret = ioctl(v4l2_video->fd, VIDIOC_QBUF, &buf);
        if(ret < 0)
        {
            printf("put back frame failed!");
        }
        usleep(10 * 1000);

    }
    return nullptr;
}


void V4L2_Video::start()
{
    m_is_start = true;
}


void V4L2_Video::stop()
{
    m_is_start = false;
}


// 注:yuyv to yuv420p
void V4L2_Video::yuyv422ToYuv420p(int inWidth, int inHeight, uint8_t *pSrc, uint8_t *pDest)
{
    int i, j;
    //首先对I420的数据整体布局指定
    uint8_t *u = pDest + (inWidth * inHeight);
    uint8_t *v = u + (inWidth * inHeight) / 4;

    for (i = 0; i < inHeight/2; i++)
    {
        /*采取的策略是:在外层循环里面,取两个相邻的行*/
        uint8_t *src_l1 = pSrc + inWidth*2*2*i;//因为4:2:2的原因,所以占用内存,相当一个像素占2个字节,2个像素合成4个字节
        uint8_t *src_l2 = src_l1 + inWidth*2;//YUY2的偶数行下一行
        uint8_t *y_l1 = pDest + inWidth*2*i;//偶数行
        uint8_t *y_l2 = y_l1 + inWidth;//偶数行的下一行
        for (j = 0; j < inWidth/2; j++)//内层循环
        {
            // two pels in one go//一次合成两个像素
            //偶数行,取完整像素;Y,U,V;偶数行的下一行,只取Y
            *y_l1++ = src_l1[0];//Y
            *u++ = src_l1[1];//U
            *y_l1++ = src_l1[2];//Y
            *v++ = src_l1[3];//V
            //这里只有取Y
            *y_l2++ = src_l2[0];
            *y_l2++ = src_l2[2];
            //YUY2,4个像素为一组
            src_l1 += 4;
            src_l2 += 4;
        }
    }
}


// X264软件编码
void *V4L2_Video::yuyv422_to_H264_thread(void *ptr)
{
    V4L2_Video *v4l2_video = static_cast<V4L2_Video *>(ptr);
    x264_t *encoder;
    x264_picture_t pic_in;
    x264_picture_t pic_out;
    x264_param_t pParam;
    x264_nal_t *nal;
    int i_nal;
    int i_frame_size;
    int i_frame_size_sum;
    VideoBuffer yuv_buffer;

    int ret;
    int i;
    int width = IMAGEWIDTH;
    int height = IMAGEHEIGHT;
    int csp = X264_CSP_I420;              // yuv 4:2:0 planar
    VideoBuffer h264_buffer[SEND_QUEUE_FRAME_NUM];
    int h264_buffer_index = 0;
    int64_t i_pts = 0;

    uint8_t *I420_buffer = static_cast<uint8_t *>(malloc(width * height * 2 * sizeof(uint8_t)));

    for(int i= 0; i < SEND_QUEUE_FRAME_NUM; i++)
    {
        h264_buffer[i].start = static_cast<uint8_t *>(malloc(width * height * 2* sizeof(uint8_t)));
    }

    x264_param_default_preset(&pParam, "medium", NULL);

    // 设置编码器参数并打开编码器
    x264_param_default(&pParam);
    pParam.i_width = width;
    pParam.i_height = height;
    pParam.i_csp = csp;
    pParam.rc.b_mb_tree = 0;        // 为0可降低实时编码时的延迟
    pParam.i_slice_max_size = 1024;
    pParam.b_vfr_input = 0;
    pParam.i_fps_num = VIDEO_FPS;
    pParam.i_fps_den = 1;
//    pParam.i_keyint_max = 10;

    x264_param_apply_profile(&pParam, "baseline");

    x264_picture_alloc(&pic_in, pParam.i_csp, pParam.i_width, pParam.i_height);

    encoder = x264_encoder_open(&pParam);

    while(1)
    {
        pthread_mutex_lock(&v4l2_video->m_encode_queue_lock);   // m_encode_queue加锁
        if(v4l2_video->m_encode_queue.size() == 0)
        {
            pthread_mutex_unlock(&v4l2_video->m_encode_queue_lock);     // m_encode_queue解锁
            usleep(10 * 1000);
            continue;
        }
        yuv_buffer = v4l2_video->m_encode_queue.front();
        v4l2_video->yuyv422ToYuv420p(width, height, static_cast<uint8_t *>(yuv_buffer.start), I420_buffer);
        v4l2_video->m_encode_queue.pop();
        pthread_mutex_unlock(&v4l2_video->m_encode_queue_lock);     // m_encode_queue解锁

        memcpy(pic_in.img.plane[0], I420_buffer, width * height);
        memcpy(pic_in.img.plane[1], I420_buffer + width * height, width * height / 4);
        memcpy(pic_in.img.plane[2], I420_buffer + width * height + width * height / 4, width * height / 4);

        pic_in.i_pts = i_pts++;
        i_frame_size = x264_encoder_encode(encoder, &nal, &i_nal, &pic_in, &pic_out);
        if(i_frame_size < 0)
        {
            printf("x264_encoder_encode error! i_frame_size = %d\n", i_frame_size);
            continue;
        }
        else if(i_frame_size == 0)
        {
            continue;
        }
        i_frame_size_sum = 0;
        if(h264_buffer_index == SEND_QUEUE_FRAME_NUM)
            h264_buffer_index = 0;
        for (i = 0; i < i_nal; i++)
        {
            memcpy(h264_buffer[h264_buffer_index].start + i_frame_size_sum, nal->p_payload, i_frame_size);
            i_frame_size_sum += i_frame_size;
        }
        h264_buffer->type = static_cast<enum nal_unit_type_e>(nal->i_type);
        h264_buffer[h264_buffer_index].length = static_cast<unsigned int>(i_frame_size_sum);
        // 将编码好的帧放入发送队列
        pthread_mutex_lock(&v4l2_video->m_send_queue_lock);   // m_send_queue加锁
        if(v4l2_video->m_send_queue.size() < SEND_QUEUE_FRAME_NUM)
            v4l2_video->m_send_queue.push(h264_buffer[h264_buffer_index]);
        else
            printf("warning: m_send_queue is overflow!\n");
        pthread_mutex_unlock(&v4l2_video->m_send_queue_lock);   // m_send_queue解锁
        h264_buffer_index++;
    }


    free(I420_buffer);

    for(i = 0; i < SEND_QUEUE_FRAME_NUM; i++)
        free(h264_buffer[i].start);

    x264_picture_clean(&pic_in);
    x264_encoder_close(encoder);

    return 0;
}



int V4L2_Video::video_read(char **buffer, int *buffer_size)
{
    int ret;
    static unsigned char send_data_buffer[IMAGEWIDTH * IMAGEHEIGHT * 4];
    enum nal_unit_type_e type;
    if(m_is_start == false)
    {
        printf("error: v4l2 video capture is stopped!\n");
        return -1;
    }

    pthread_mutex_lock(&m_send_queue_lock);   // m_send_queue加锁
    if(m_send_queue.size() == 0)
    {
        pthread_mutex_unlock(&m_send_queue_lock);   // m_send_queue解锁
//        printf("video send queue is empty!\n");
        return -1;
    }

    *buffer_size = static_cast<int>(m_send_queue.front().length);
    memcpy(send_data_buffer, m_send_queue.front().start, static_cast<unsigned int>(*buffer_size));
    type = m_send_queue.front().type;
    m_send_queue.pop();
    pthread_mutex_unlock(&m_send_queue_lock);   // m_send_queue解锁


    *buffer = reinterpret_cast<char *>(send_data_buffer);


    return 0;
}

编写好的代码,可以参照例程工程里的Makefile文件,编写编译脚本,进入GL2编译环境下,进行编译调试(省去调试修改代码xxx字)最后把编译好的程序上传到开发板上,启动运行如下;

v4l2capture -m save -d /dev/video0 -t usb -F YUYV -w 640 -h 480 -f 15 -o /home/root/test.yuv -n 300

参数含义如下:

-d 选择采集设备

-t usb采集

-F 采集视频色彩数据组织格式,YUYV对应的ffmpeg里就是 YUV422格式

-w 采集视频画面长度,这个宽度必须是采集设备支持的规格表里的参数

-h 采集视频画面高度,同上需要满足视频规格标准

-f 采集帧率,必须是视频采集支持的规格

-o 输出文件

-n 采集帧数

图14

        采集完成后,将yuv数据上传到windows下,使用GL2工具包下的tools目录下的YUV Player.exe,播放,因为yuv文件不记录视频的长宽,以及格式信息,所以需要在yuv播放器中需要配置正确的尺寸,和视频格式信息,才能正确的播出。

 图15

       随文放了两段视频,一段是是手机拍摄,拍摄启动采集程序和移动摄像头的过程。另外一段视频为采集到的yuv数据上传到Ubuntu主机后,经过转换和编码生成mp4,可以看摄像头到实际采集到的画面效果。

六、视频采集开发测评总结

      通过对Logitech C270 摄像头的视频采集开发测试来看,RZGL2开发板支持视频采集功能很完善,支持V4L2,FFMPEG这样常用的音视频处理框架,使得很多音视频应用移植起来成为可能,也比较简单。

    从采集到的视频看,采集速率较高,支持视频的规格也很广。这为后面开发提供很好的基础,在后面较长时间(>60分钟)的视频采集测试中,采集程序和系统运行非常稳定,没有出现异常中断等现象,并且在持续视频采集中,触摸CPU感觉升温不明显,这也可以看该处理优良的功耗表现。


更多硬件开发技术见:

ALSET的个人主页 - 电子技术论坛 - 广受欢迎的专业电子论坛!

同步文章

【飞凌RZ/G2L开发板试用体验】飞凌RZ/G2L的开发板试用测评报告二 — 视频采集开发 - 飞凌嵌入式 - 电子技术论坛 - 广受欢迎的专业电子论坛!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值