• 37、mipg-streamer的使用讲解


    讲解mjpg-streamer

    其功能:

    1、控制摄像头采集数据(通过ioctl采集数据,所有不支持CMOS,CMOS之前写驱动的时候是通过read,所有需要修改mjpg-streamer的源码或者CMOS驱动);

    2、把采集的数据传输到路由器上或者无线网卡上;

    我们的JZ2440可以接一个usb-hub,上面接上无线网卡和摄像头,可以一起使用

    1. 如何将mjpg-streamer移植到开发板上

    文件系统:fs_mini_mdev_new_auto_wifi_ap.tar.bz2

    (1) libjpeg 的移植
    tar xzvf libjpeg-turbo-1.2.1.tar.gz
    cd libjpeg-turbo-1.2.1
    mkdir tmp
    ./configure --prefix=/work/jz2440/libjpeg-turbo-1.2.1/tmp --host=arm-linux
    make
    make install
    cp /work/jz2440/libjpeg-turbo-1.2.1/tmp/lib/*so* /work/nfs_root/fs_mini_mdev_new/lib/ -d

    (2) mjpg-streamer 的移植:
    tar xvf mjpg-streamer-r63.tar.gz
    cd mjpg-streamer-r63

    修改所有的Makefile
    --(1) 将 CC=gcc 修改为 CC=arm-linux-gcc
    --(2) 修改plugins/input_uvc/Makfile
    a. 将
    CFLAGS += -O2 -DLINUX -D_GNU_SOURCE -Wall -shared -fPIC
    改为
    CFLAGS += -O2 -DLINUX -D_GNU_SOURCE -Wall -shared -fPIC -I /work/jz2440/libjpeg-turbo-1.2.1/tmp/include
    注意:
    -I /work/jz2440/libjpeg-turbo-1.2.1/tmp/include // 是编译libjpeg 生成的文件

    b. 将
    input_uvc.so: $(OTHER_HEADERS) input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo
          $(CC) $(CFLAGS) -ljpeg -o $@ input_uvc.c v4l2uvc.lo
    jpeg_utils.lo dynctrl.lo
    改为
    input_uvc.so: $(OTHER_HEADERS) input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo
            $(CC) $(CFLAGS) -ljpeg -L /work/jz2440/libjpeg-turbo-1.2.1/tmp/lib -o
              $@ input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo

    make
    cp mjpg_streamer /work/nfs_root/fs_mini_mdev_new/bin/
    cp *so* /work/nfs_root/fs_mini_mdev_new/lib/ -d

     运行指令:

    二合一摄像头(-i表示输入渠道,-o表示输出渠道)
    mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"

    一般摄像头
    mjpg_streamer -i "input_uvc.so -f 10 -r 320*240 -y" -o "output_http.so -w www"(-y表示压缩图片成jpeg格式,方便传输)

    2.分析源码

    摄像头数据地址buf、源地址input 、目的地址output,都保存在Mipg_streamer.h这个头文件中的_globals结构体中

    编译mjpg-streamer生成的so库中input开头的表示各个输入渠道,out开头的用于表示各个输出渠道

    通过-i和-o指定输入和输出渠道后,每个input和output表示一个结构体:这个结构体有init函数、open函数、cmd函数、stop函数

    举例:当执行mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"指令时,对应的输入源代码参考input._uvc.c文件,里面有各个函数:

    input_init

      init_videoIn(videoIn,dev,width,height,fps,format)//设置图像长、宽、帧率、格式

        设置vdIn结构体

        init_v4l2//里面就是v4l2应用程序的框架

          open()//打开摄像头的设备节点,根据传入的参数或者默认参数决定

          ioctl(VIDIOC_QUERYCAP)//查看打开的设备的属性,下面会判断是否是视频捕获设备

          ioctl(VIDIOC_S_FMT)//设置摄像头的输出格式(分辨率、输出格式)

          ioctl(VIDIOC_S_PARM)//设置摄像头的参数,比如输出帧率

          ioctl(VIDIOC_REQBUFS)//申请缓存

          for(缓存个数)  

            ioctl(VIDIOC_QUERYBUF)//获取内核空间的视频缓冲区的信息

            mmap()//根据获得的信息做映射操作

          for(缓存个数)

            ioctl(VIDIOC_QBUF)//将一个空的缓冲区放入视频缓冲区队列

        分配临时缓冲区(vd->framebuffer)用来接收摄像头数据,MJPEG需要分配两个,见代码

       if(dynctrls)//动态控制摄像头功能初始化

        initDynCtrls 

      input_cmd()//通过命令行动态控制摄像头参数,比如高级摄像头的焦距控制

    input_run

      分配一段buf用来存储视频数据(仓库)

      pthread_create(cam_thread)//创建线程,线程函数是cam_thread

        cam_thread

          while(!pglobal->stop)//stop会在按下CTRL+C的时候会被置1

            uvcGrab//获得一帧数据

              video_enable//使能视频捕获设备

                ioctl(VIDIOC_STREANMON)//将一个空的缓冲区放入视频缓冲区队列 

              ioctl(VIDIOC_DQBUF)//从视频缓冲区队列取出一个已经存有一帧视频数据的缓冲区buf

              根据获得的信息处理数据及根据视频格式把数据复制到init阶段创建的vd->framebuffer中去,MJPEG格式稍微有点不同,其是放到tempbuffer中

               ioctl(VIDIOC_QBUF)//把这个已获得数据的空的缓冲区放入视频缓冲区队列

              

            判断数据的格式是YUV或者jpeg,如果是jpeg这直接放入globl里面的buf(仓库),否则将YUV转换为jpeg后在保存在buf

             pthread_cond_broadcase(&pglobal->db_update)//通知输出通道可以去数据发出了

    对于output_http.so,就是使用sock编程还模拟http协议,查看源码,其中init函数做socket的初始化,run函数从buf中取出数据,然后发送数据

    (见output_http.c)

    output_init()

      仅解析指令参数,然后给相应的变量赋值

    output_run()

      pthread_create(server_thread)//创建线程,线程函数是server_thread

        server_thread

          socket(PF_INET,SOCK_STREAM,0)//创建一个socket(其是作为服务器)    

          setsockopt()//当端口被其他进程使用时,通过这个函数里面指定的参数可以设置重复使用同一个IP和端口号

          bind()//绑定端口和IP地址

          listen()//启动检测数据,第二个参数表示最多客户端数量

          while((!pglobal->stop))//同input_run里面的分析

            分配一个cfd结构体

            accept()//休眠等待客户端连接,并建立连接

            pthread_create(client_thread)//创建线程,线程函数是client_thread

                 client_thread//参数是上面设置的cfd结构体

                init_iobuffer(&iobuf)//初始化一个iobuf,在后面仅是一个临时缓存

                init_request(&req)//http协议,需要客户端给服务器发送一个请求,而request就是用来保存请求的

                _readline()//从客户端接受一些数据,用来表示客户端发来的请求,才知道给客户发什么数据

                      这个函数里面调用select函数,当客户端发来数据或者超时的时候返回去调用read函

                      数从客户端获得数据并保存在iobuf中,接着从iobuf中将数据拷贝到buffer中(这个过程

                      并不是简单的复制,可以查看代码分析)

                      因此客户端必须发送一个请求字符串,以换行符作为结束,比如当使用浏览器输入URL

                      的时候http://192.168.1.17:8080/?action=stream(action=stream这个就表示请求)

                从buffer中提取请求并保存在request中

    ··           do

                 _readline() 再次读取一次客户端发送过来的字符串

                 处理buffer中的数据并保存,比如如果有用户名,则保存到req.client;有密码保存到req.credential

                如果服务器有账户和密码,则比较客户端传入的账户密码是否匹配

                根据第一次客户端发来的请求做不同动作

                case A_SNAPSHOT(拍照请求)

                  send_snapshot(发送照片)

                    pthread_cond_wait(等待input通道通知有数据可取了)

                    分配一帧缓冲区,并从global->buf(仓库)中取出数据保存

                    构造一个http数据头在buffer数据,并将这个数据发送出去(这个数据头表示请求)

                    如果上一步成功,就把一帧图片发送出去

                case A_STREAM

                  send_stream 

                    构造一个http数据头在buffer数据,并将这个数据发送出去(这个数据头表示请求)

                    while(!pglobal->stop)

                      pthread_cond_wait(等待input通道通知有数据可取了)

                      如果帧framer空间不够就分配空间,并从global->buf(仓库)中取出数据保存

                      构造一个http数据头(报文)在buffer数据,并将这个数据发送出去(这个数据头表示请求,里面有下面发送帧图片数据大小)

                      如果上一步成功,就把一帧图片发送出去

                      接着发送一个“boundarydonotcross”字符串,表示一帧数据已接受完

                    

    input_init();
    output_init();
    input_run();
    output_run();


    3.自己写客服端(在“在LCD上显示摄像头”的7th_pc上修改程序)

    (1).发送一个请求字符串
    "GET /?action=snapshot "
    "GET /?action=stream "
    "GET /?action=command "

    (2).再发送一次字符串
    如果我们不使用登录密码功能!则只需发送任意长度为小于2字节的字符串,就会跳过处理账户和密码的代码比如:
    "f "

    如果发送的请求是:"GET /?action=snapshot "

    (3).需要接收一次字符串(是服务器发过来的报文)

    (4).接收一帧图片

    如果发送的请求是:"GET /?action=stream "

    (3).需要接收一次字符串(是服务器发过来的报文)

    while(1)
    {
    (4).再接收一次报文,解析它,得到一帧图片的大小(size)

    (5).接收size个字节的数据
    }

    1、在ubuntu上连接2440上无线网卡的wifi热点

    2、按住ctrl+Alt+F1进入文本模式,输入用户名和密码

    3、运行客户端程序( ./应用程序 开发板IP地址)

    4、ctrl+Alt+F4

    启动内核:
    set ipaddr 192.168.7.122 && set serverip 192.168.7.111 && tftp 0x30000000 uImage
    set bootargs root=/dev/nfs nfsroot=192.168.7.202:/work/nfs_root/fs_mini_mdev_new ip=192.168.7.17 console=ttySAC0,115200 && bootm 0x30000000

  • 相关阅读:
    快速得到栈、队列的最大值
    原型与原型链
    人家跟你谈生意,你连份明码标价的菜单都拿不出来,有什么资格好撒气的?
    一个坑:java.sql.ResultSet.getInt==》the column value; if the value is SQL NULL, the value returned is 0
    static在实例Extends、Overload中理解
    JVM-ClassLoader(转)
    关于eclipse和javac编译结果不一致的问题的分析与解决 (转)
    蜗牛—JSP学习之JavaBean初识
    ibatis 开发中的经验 (一)ibatis 和hibernate 在开发中的理解
    关于64位Linux配置android开发环境出现 No such file or directory
  • 原文地址:https://www.cnblogs.com/liusiluandzhangkun/p/8971522.html
Copyright © 2020-2023  润新知