开源软件mjpg-streamer 简要分析(上)

本文详细探讨了Mjpg-streamer项目的工作原理、模块组件、核心代码结构及其线程管理策略,揭示了如何利用多线程技术优化图像处理流程,确保在嵌入式设备上高效传输视频流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 Mjpg-streamer是一个开源项目,也是一个可以从单一组件获取图像并传输到多个输出组件的命令行式的应用程序,它可以将JPEG的文件视频流化并通过互联网将视频流从网络摄像头传送到像Firfox、VLC、带有浏览器的移动设备等显示装置。主要针对RAM和CPU资源有限制的嵌入式设备而编写的,为了减少CPU的负担它充分利用了网络摄像头一定程度上的硬件压缩能力,主要通过输入插件从摄像头获取图像后再通过网页输出插件将图像输出,故它对CPU的占用率较低。这款工具代码简洁、组件功能明确,衔接清晰明了,使用Linux C语言开发,可根据开源软件GPL V2的条款进行改进和发行,并可移植到不同的计算机平台,比较适合嵌入式系统。其系统工作原理如图··所示。

图1.13  Mjpg-streamer主要工作原理图示
Mjpg-streamer源代码共有78个文件组成,www文件件下为网页文件(html,js,css等文件),具体源代码目录树参照关系如图所示。


图 mjpg-streamer目录树关系图
由目录树可以看书此开源项目比较大,可以分为如图所示的模块组件。主程序在mjpg-streamer.c的文件中,其左侧为输入模块组件,右侧为输出模块组件。其中Input_gspcav1为CMOS摄像头输入组件,Input_uvc为支持uvc的USB摄像头输入组件,而其他输入组件为测试用。输出组件中,Output_file将采集到的图像以jpg格式的图像输出到指定的文件目录,Output_http则会通过HTTP方式将图像视频输出,同时输出必要的网页内容,这是Mjpg-streamer最主要的输出方式。通过启动mjpg-streamer时的输入的参数选择使用哪个组件,同时传递参数给组件。
图 mjpg-streamer模块组件
整个开源项目中核心模块为主程序模块即mjpg-streamer.c和mjpg-streamer.h文件,它根据用户输入的参数选择性地调用其他模块中的函数。Utils.c中的deamon_mode函数会使得程序以守护进程的方式运行,即用fork函数创建子进程等操作。在整个程序中全局结构体变量 global起程序运行状态记录、重要参数存储和数据缓存的作用,其结构体类型名为_globals,内部成员变量如图··所示。


图··全局结构体变量global的成员
其中,buf为图像帧数据全局缓存区(相当于一种共享资源),size为缓存区的大小,db为缓存区的互斥锁,db_update为条件变量,stop为全局结束标志(将其值为1则程序终止),in、out均为结构体变量(包含组件名、描述符、参数、函数指针等),分别为调用输入输出组件的接口,outcnt记录输出组件的个数。在这样的数据结构基础下,主程序(mjpg-streamer.c)中进行的操作流程如图··所示:
 
图 mjpg-streamer主程序流程图
主程序中根据用户选择的组件,dlopen()函数以指定模式打开指定的动态连接库文件,并返回一个句柄(指针)给调用进程,此指针作为dlsym的参数,通过dlsym检索库内的run、init、stop等函数,返回相关函数的函数指针供调用使用。而在主程序中会将返回的函数指针赋给global中的输入输出组件接口。
为了减小输入组件与输出之间的延时和提高程序执行效率,Mjpg-streamer开源项目采用多线程方式。目前许多流行的多任务操作系统都提供线程机制,线程是一个进程内的基本调度单位,也可以称为轻量级进程。线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源,如文件描述和信号处理。因此,大大减少了上下文切换的开销。一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。利用多线程进行程序设计,就是将一个程序(进程)的任务划分为执行的多个部分(线程),每一个线程为一个顺序的单控制流,而所有线程都是并发执行的,这样多线程程序就可以实现并行计算,高效利用多处理器。线程可分为用户级线程和内核级线程两种基本类型。Mjpg-streamer中为用户级多线程,用户级线程不需要内核支持,可以在用户程序中实现,线程调度、同步与互斥都需要用户程序自己完成。内核级线程需要内核参与,由内核完成线程调度并提供相应的系统调用,用户程序可以通过这些接口函数对线程进行一定的控制和管理。Linux操作系统提供了LinuxThreads库,它是符合POSIX1003.1c标准的内核级多线程函数库。在linuxthreads库中提供了一些多线程编程的关键函数,在多线程编程时应包括pthread.h文件。当系统使用input_uvc、output_http、output_file组件时,涉及到的线程及数据如数据流图··所示


图 mjpg-streamer主数据流图
从数据流图中可以看出,有若干并行的线程处理图像等数据,同时多个线程都对内存中的同一段区域(全局缓存区global.buf)操作,这一段内存区域相当于线程间的共享资源。当client_thread线程或worker_thread线程对这段全局缓存区读取数据时,要保证这时cam_thread线程不能对全局缓存区进行写操作,否则client_thread线程或worker_thread线程读取的数据将是错误的,为了保证线程之间这种顺序执行和避免并发冲突需要寻求一种机制来进行线程同步。

通常情况下线程同步有三种方式,一是互斥锁;二是条件变量;三是信号灯;在mjpg-streamer中使用的是互斥锁和条件变量的方式,通常这两种方式配合在一起使用。互斥锁机制用于多线程中,防止两个线程同时对一公共共享资源(例如全局缓存区)进行读写操作。只有对共享资源上锁的线程对有权限对公共资源进行操作,其他试图对共享资源进行操作的线程都会被阻塞,对共享资源上锁的线程在对共享资源操作结束后需要解锁,以备其它线程对共享资源的操作。与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。

在mjpg-streamer中,cam_thread对全局缓存区进行写帧操作之前调用 pthread_mutex_lock( &pglobal->db )锁住互斥锁,然后向缓存区写帧。写帧结束后调用pthread_cond_broadcast(&pglobal->db_update)函数通知线程条件已经满足,即向线程或条件变量发送信号,解除相关线程的阻塞。对共享资源操作结束之后调用 pthread_mutex_unlock( &pglobal->db )解锁互斥锁。而在client_thread 和work_thread中,在从全局缓存区复制帧出来之前都会调用pthread_cond_wait(&pglobal->db_update,&pglobal->db)等待全局缓存区中的帧更新,当前线程被阻塞,一旦帧更新后会收到由cam_thread发送来的信号,进而解除当前线程的阻塞,从全局缓存区复制帧出来。最后完成复制后调用pthread_mutex_unlock( &pglobal->db )后解锁互斥锁。采用这种机制来实现多个线程之间的同步。

解决了线程同步的问题后,注意到还有一个问题,如果线程在在运行时被非正常终止,那么他所占用的资源如何释放呢?非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的,如果没有一种机制来保证线程非正常终止时也能施放其所占有的资源,那么将会导致内存泄漏等严重问题、甚至导致系统崩溃。在POSIX线程API中提供了一个pthread_cleanup_push()和pthread_cleanup_pop()函数对,用于自动释放资源。从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数,在mjpg-streamer中采用了 pthread_cleanup_push和pthread_cleanup_pop函数对来实现线程资源的完全回收,特别是对于临界区。例如在cam_thread线程中,在线程开始前执行pthread_cleanup_push(cam_cleanup, NULL)将清理函数cam_cleanup压入清理函数栈,在线程结束时调用了pthread_cleanup_pop(1)从函数栈中弹出清理函数,只要push和pop之间线程被异常终止都将会调用cam_cleanup函数释放cam_thread线程所占用资源。同时由于在线程结束时调用的pthread_cleanup_pop(1)中传递的参数为1,所以在弹出清理函数cam_cleanup的同时会执行它,释放资源。若参数为0,则弹出时不执行它。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值