VLC 0.1.99 源码分析
最近在学软件构架,找些开源软件来学习一下。
由于VLC和自己要搞的项目有些接近,因此首先从VLC开刀,但是VLC经过10多年的发展,现在的2.0版本已经非常庞大了。磨刀不误砍柴工,还是先花两天时间学习一下最初的0.1.99版本,先摸清个大概,再往高版本学习。本文就是这两天的学习记录。
带着下面几个问题,开始阅读源码:
1、 程序模块怎么划分?
2、 各个模块之间怎么进行通信?
3、 内存如何管理?
4、 怎么做日志?
5、 VLC可以根据用户的配置动态加载不同的用户界面和输入输出模块,是怎么做到的?
从程序目录来看程序模块划分:
从源码目录来看,整个应用程序分成:媒体数据输入、媒体数据解析、媒体数据解码、视频显示、音频输出、用户界面六个模块。
下面再来看看主要的数据结构:
- /*****************************************************************************
- * main_t, p_main (global variable)
- *****************************************************************************
- * This structure has an unique instance, declared in main and pointed by the
- * only global variable of the program. It should allow access to any variable
- * of the program, for user-interface purposes or more easier call of interface
- * and common functions (example: the intf_*Msg functions). Please avoid using
- * it when you can access the members you need in an other way. In fact, it
- * should only be used by interface thread.
- *****************************************************************************/
- typedef struct
- {
- /* Global properties */
- int i_argc; /* command line arguments count */
- char ** ppsz_argv; /* command line arguments */
- char ** ppsz_env; /* environment variables */
- /* Generic settings */
- boolean_t b_audio; /* is audio output allowed ? */
- boolean_t b_video; /* is video output allowed ? */
- boolean_t b_vlans; /* are vlans supported ? */
- /* Unique threads */
- p_aout_thread_t p_aout; /* audio output thread */
- p_intf_thread_t p_intf; /* main interface thread */
- /* Shared data - these structures are accessed directly from p_main by
- * several modules */
- p_intf_msg_t p_msg; /* messages interface data */
- p_input_vlan_t p_vlan; /* vlan library data */
- } main_t;
/*****************************************************************************
* main_t, p_main (global variable)
*****************************************************************************
* This structure has an unique instance, declared in main and pointed by the
* only global variable of the program. It should allow access to any variable
* of the program, for user-interface purposes or more easier call of interface
* and common functions (example: the intf_*Msg functions). Please avoid using
* it when you can access the members you need in an other way. In fact, it
* should only be used by interface thread.
*****************************************************************************/
typedef struct
{
/* Global properties */
int i_argc; /* command line arguments count */
char ** ppsz_argv; /* command line arguments */
char ** ppsz_env; /* environment variables */
/* Generic settings */
boolean_t b_audio; /* is audio output allowed ? */
boolean_t b_video; /* is video output allowed ? */
boolean_t b_vlans; /* are vlans supported ? */
/* Unique threads */
p_aout_thread_t p_aout; /* audio output thread */
p_intf_thread_t p_intf; /* main interface thread */
/* Shared data - these structures are accessed directly from p_main by
* several modules */
p_intf_msg_t p_msg; /* messages interface data */
p_input_vlan_t p_vlan; /* vlan library data */
} main_t;
这个是主程序数据结构,该数据结构包含各个模块用到的所有数据,很多东西注释都说得很清楚了,就不详述了。
需要注意的是音频输出p_aout和界面p_intf两个模块的数据结构。
好像看不到比如视频输入、解码器等的数据结构? 对,该版本把这些结构都放到intf_thread_s中了,下面便是该结构:
- typedef struct intf_thread_s
- {
- boolean_t b_die; /* `die' flag */
- /* Specific interfaces */
- p_intf_console_t p_console; /* console */
- p_intf_sys_t p_sys; /* system interface */
- /* Plugin */
- plugin_id_t intf_plugin; /* interface plugin */
- intf_sys_create_t * p_sys_create; /* create interface thread */
- intf_sys_manage_t * p_sys_manage; /* main loop */
- intf_sys_destroy_t * p_sys_destroy; /* destroy interface */
- /* XXX: Channels array - new API */
- //p_intf_channel_t * p_channel[INTF_MAX_CHANNELS];/* channel descriptions */
- /* file list - quick hack */
- char **p_playlist;
- int i_list_index;
- /* Channels array - NULL if not used */
- p_intf_channel_t p_channel; /* description of channels */
- /* Main threads - NULL if not active */
- p_vout_thread_t p_vout;
- p_input_thread_t p_input;
- } intf_thread_t;
typedef struct intf_thread_s
{
boolean_t b_die; /* `die' flag */
/* Specific interfaces */
p_intf_console_t p_console; /* console */
p_intf_sys_t p_sys; /* system interface */
/* Plugin */
plugin_id_t intf_plugin; /* interface plugin */
intf_sys_create_t * p_sys_create; /* create interface thread */
intf_sys_manage_t * p_sys_manage; /* main loop */
intf_sys_destroy_t * p_sys_destroy; /* destroy interface */
/* XXX: Channels array - new API */
//p_intf_channel_t * p_channel[INTF_MAX_CHANNELS];/* channel descriptions */
/* file list - quick hack */
char **p_playlist;
int i_list_index;
/* Channels array - NULL if not used */
p_intf_channel_t p_channel; /* description of channels */
/* Main threads - NULL if not active */
p_vout_thread_t p_vout;
p_input_thread_t p_input;
} intf_thread_t;
发现了吧,intf_thread_s里面包含了视频输出模块p_vout和媒体输入模块的数据结构p_input。
下面开始查看程序的主要运行流程吧,还是从interface/main.c文件中的main函数看起。
由于程序通过用户配置的方式来加载不同的模块,因此以下程序跟踪对用户的配置进行了假设:
1、假设程序使用gnome界面。
2、使用文件输入的方式。
3、媒体输入为TS流,视频显示使用gnome(X11)的方式。
1. 调用intf_MsgCreate(intf_msg.c)初始化消息数据结构p_main->p_msg,
2. 调用GetConfiguration(main.c)根据命令行参数设置环境变量,后面的模块通过读取环境变量还获得配置。
3. 通过命令行参数初始化界面模块中的播放文件列表数据结构main_data.p_intf->p_playlist和main_data.p_intf->i_list_index。
4. 如果配置了网络模块,则调用input_VlanCreate(input_vlan.c)加载网络模块,初始化网络模块数据结构main_data.b_vlans
5. 如果配置了音频输出模块,则调用aout_CreateThread(audio_output.c)加载音频输出模块,初始化网络模块数据结构main_data.b_audio
6. 调用intf_Create(interface.c)加载界面模块,初始化界面模块数据结构main_data.p_intf
- /* Get plugins */
- p_intf->p_sys_create
- = GetPluginFunction( p_intf->intf_plugin, "intf_SysCreate" );
- p_intf->p_sys_manage
- = GetPluginFunction( p_intf->intf_plugin, "intf_SysManage" );
- p_intf->p_sys_destroy
- = GetPluginFunction( p_intf->intf_plugin, "intf_SysDestroy" );
/* Get plugins */
p_intf->p_sys_create
= GetPluginFunction( p_intf->intf_plugin, "intf_SysCreate" );
p_intf->p_sys_manage
= GetPluginFunction( p_intf->intf_plugin, "intf_SysManage" );
p_intf->p_sys_destroy
= GetPluginFunction( p_intf->intf_plugin, "intf_SysDestroy" );
6.2 调用p_intf->p_sys_create函数创建UI,实际上是调用了intf_SysCreate(intf_gnome.c)函数:
6.2.1 调用GnomeCreateWindow创建gnome界面。
6.2.2 调用vout_CreateThread函数初始化视频输出模块p_intf->p_vout(video_output.c):
6.2.2.1 初始化视频输出模块函数指针:
- /* Get plugins */
- p_vout->p_sys_create =
- GetPluginFunction( p_vout->vout_plugin, "vout_SysCreate" );
- p_vout->p_sys_init =
- GetPluginFunction( p_vout->vout_plugin, "vout_SysInit" );
- p_vout->p_sys_end =
- GetPluginFunction( p_vout->vout_plugin, "vout_SysEnd" );
- p_vout->p_sys_destroy =
- GetPluginFunction( p_vout->vout_plugin, "vout_SysDestroy" );
- p_vout->p_sys_manage =
- GetPluginFunction( p_vout->vout_plugin, "vout_SysManage" );
- p_vout->p_sys_display =
- GetPluginFunction( p_vout->vout_plugin, "vout_SysDisplay" );
/* Get plugins */
p_vout->p_sys_create =
GetPluginFunction( p_vout->vout_plugin, "vout_SysCreate" );
p_vout->p_sys_init =
GetPluginFunction( p_vout->vout_plugin, "vout_SysInit" );
p_vout->p_sys_end =
GetPluginFunction( p_vout->vout_plugin, "vout_SysEnd" );
p_vout->p_sys_destroy =
GetPluginFunction( p_vout->vout_plugin, "vout_SysDestroy" );
p_vout->p_sys_manage =
GetPluginFunction( p_vout->vout_plugin, "vout_SysManage" );
p_vout->p_sys_display =
GetPluginFunction( p_vout->vout_plugin, "vout_SysDisplay" );
6.2.2.2 调用p_vout->p_sys_create函数创建视频显示模块。
6.2.2.3 创建RunThread(video_output.c)线程,初始化显示双缓冲区(InitThread)并进入视频输出模块事件循环:
l 检查p_vout->p_picture缓冲区中是否有已经准备好显示的图片。
l 进行色彩空间转换、图片OSD信息输出等。
l 根据PTS或者帧率计算显示后等待事件并等待。
l 调用p_vout->p_sys_display(vout_SysDisplay)函数进行显示(X11)。
l 调用p_vout->p_sys_manage函数或者Manage函数处理界面模块对视频输出所进行的参数改变,比如检查p_vout->i_changes变量。
6.2.3 创建GnomeThread线程,在线程中
6.2.3.1 使用定时器调用GnomeManageMain函数检查主程序是否退出,以便退出gtk事件循环。
6.2.3.2 调用gtk_main();进入gtk界面事件循环。
7. 调用InitSignalHandler函数注册系统信号处理函数,比如通过键盘中断退出。
8. 调用intf_Run(interface.c)运行界面模块
8.1 如果p_intf->p_playlist中包含播放对象,则调用input_CreateThread(input.c)函数,以文件输入的方式初始化输入模块p_intf->p_input。
- case INPUT_METHOD_TS_FILE: /* file methods */
- p_input->p_Open = input_FileOpen;
- p_input->p_Read = input_FileRead;
- p_input->p_Close = input_FileClose;
- break;
case INPUT_METHOD_TS_FILE: /* file methods */
p_input->p_Open = input_FileOpen;
p_input->p_Read = input_FileRead;
p_input->p_Close = input_FileClose;
break;
这三个input_File*函数主要定义在input_file.c文件中。
8.1.2调用p_input->p_Open函数( p_input )打开文件,调用ps_thread函数初始化信号量信息,并打开input_DiskThread(input_file.c)线程进入文件源输入事件循环(ps_fill函数):
l 等待包处理队列非满vlc_cond_wait(&p_in_data->notfull, &p_in_data->lock);
l 调用ps_read函数(input_file.c)从文件读入TS包数据。
l 置包队列非空信号vlc_cond_signal(&p_in_data->notempty);
8.1.3调用RunThread(input.c)函数进入包处理模块事件循环
- while( !p_input->b_die && !p_input->b_error )
- {
- /* Scatter read the UDP packet from the network or the file. */
- if( (input_ReadPacket( p_input )) == (-1) )
- {
- /* FIXME??: Normally, a thread can't kill itself, but we don't have
- * any method in case of an error condition ... */
- p_input->b_error = 1;
- }
- #ifdef STATS
- p_input->c_loops++;
- #endif
- }
while( !p_input->b_die && !p_input->b_error )
{
/* Scatter read the UDP packet from the network or the file. */
if( (input_ReadPacket( p_input )) == (-1) )
{
/* FIXME??: Normally, a thread can't kill itself, but we don't have
* any method in case of an error condition ... */
p_input->b_error = 1;
}
#ifdef STATS
p_input->c_loops++;
#endif
}
//input_ReadPacket函数:
8.1.3.1 调用p_input->p_Read从TS包队列读取包数据并对包进行解析、排序和重组,这里读取的数据流主要是TS流,对TS流不了解的可以Google,这里不详述。p_Read函数实际上就是input_FileRead函数(input_file.c),该函数执行以下操作:
1) 等待包队列非空vlc_cond_wait( &p_in_data->notempty, &p_in_data->lock);
vlc_cond_signal(&p_in_data->notempty);
8.1.3.2 调用input_SortPacket函数(input.c)处理读取的TS包,input_SortPacket函数调用input_DemuxTS函数解析TS包(input.c),input_DemuxTS函数将TS包解析并判断其中是PSI数据还是PES数据,如果是PSI数据则调用input_DemuxPSI函数进行处理,如果是PES数据则调用input_DemuxPES函数处理,下面分别说明这两个函数的处理流程:
----- input_ParsePES函数组成完整的PES包后,将PES包送到解码器fifo队列:
p_fifo->buffer[p_fifo->i_end] = p_pes;
DECODER_FIFO_INCEND(*p_fifo );
然后通知视频事件解析循环开始启动vlc_cond_signal( &p_fifo->data_wait );通知解析事件循环开始解析ES包。
----- 调用input_PsiDecode函数(input_psi.c)对PSI包进行解析,并分别对PAT\PMT\NIT表进行解析。
----- input_AddPgrmElem函数中根据解码方式打开不同的解码器线程,如果不定义OLD_DECODER宏,vdec_CreateThread(video_decoder.c)并进入解码事件循环;如果定义OLD_DECODER宏,则通过vpar_CreateThread函数(video_parser.c)打开视频解析线程,InitThread(video_parser.c)调用vdec_CreateThread创建解码事件循环后进入视频解析事件循环。
----- 解码事件循环等待FIFO队列信号,vlc_cond_wait( &p_fifo->wait,&p_fifo->lock );队列中有数据后变读取数据进行解码。
----- 视频解析事件循环等待data_wait 信号,接收到信号后开始初始化解析函数,并进入解析事件循环RunThread(video_parser.c)。解析事件循环对ES流进行解析,提取出视频序列,然后发送信号到通知解码时间循环进行解码PictureHeader(vpar_headers.c):vlc_cond_signal( &p_vpar->vfifo.wait );
- /* Main loop */
- while(!p_intf->b_die)
- {
- /* Flush waiting messages */
- intf_FlushMsg();
- /* Manage specific interface */
- p_intf->p_sys_manage( p_intf );
- /* Check attached threads status */
- if( (p_intf->p_vout != NULL) && p_intf->p_vout->b_error )
- {
- /* FIXME: add aout error detection ?? */
- p_intf->b_die = 1;
- }
- if( (p_intf->p_input != NULL) && p_intf->p_input->b_error )
- {
- input_DestroyThread( p_intf->p_input, NULL );
- p_intf->p_input = NULL;
- intf_DbgMsg("Input thread destroyed\n");
- }
- /* Sleep to avoid using all CPU - since some interfaces needs to access
- * keyboard events, a 100ms delay is a good compromise */
- msleep( INTF_IDLE_SLEEP );
- }
/* Main loop */
while(!p_intf->b_die)
{
/* Flush waiting messages */
intf_FlushMsg();
/* Manage specific interface */
p_intf->p_sys_manage( p_intf );
/* Check attached threads status */
if( (p_intf->p_vout != NULL) && p_intf->p_vout->b_error )
{
/* FIXME: add aout error detection ?? */
p_intf->b_die = 1;
}
if( (p_intf->p_input != NULL) && p_intf->p_input->b_error )
{
input_DestroyThread( p_intf->p_input, NULL );
p_intf->p_input = NULL;
intf_DbgMsg("Input thread destroyed\n");
}
/* Sleep to avoid using all CPU - since some interfaces needs to access
* keyboard events, a 100ms delay is a good compromise */
msleep( INTF_IDLE_SLEEP );
}
p_intf->p_sys_manage函数指针在intf_Create中被初始化,假定加载的UI模块是Gnome模块,则该指针实际调用了intf_SysManage(intf_gnome.c)函数,该函数接收GUI菜单、键盘和鼠标事件并处理,其中包括改变音量、改变节目频道等操作。比如其中的视频窗口大小参数改变是通过设置p_intf->p_vout->i_changes |= VOUT_GAMMA_CHANGE;变量,来通知视频输出线程。
现在开始来回答文章开始提出的问题。
1、程序模块怎么划分?
见文章中的模块图。
2、各个模块之间怎么进行通信?
每个模块都由一个主要线程运行一个事件循环,线程之间以生产者-消费者的模式进行线程通信,生产者等待队列非满时开始生产,消费者等待队列有数据开始进行消费,生产者和消费者通过信号量的方式进行通信。
比如:
文件输入线程等待TS数据包队列非满。
文件输入线程发现TS数据包队列非满,从文件中读取数据,解析出TS数据包,将TS数据包放到队列中,并置TS数据包队列非空信号。
TS包解析线程发现TS数据包队列非空,从队列中读取并解析TS数据包,将解析出的PSI\PES数据包放到PES队列中,并通知视频解码线程PES队列非空。
视频解码线程等待PES数据包非空,如果非空则读取数据进行解码,并把解码后的数据放到视频显示队列中。
视频显示队列线程等待视频显示队列非空,如果非空则读取当前视频帧进行显示。
图形界面事件循环等待用户操作,并对用户的操作事件进行相应,修改相关模块数据结构。
相关模块线程在事件循环中检查配置信息是否被修改,如果被修改则进行相应的操作。
该版本没有做专门的内存管理模块,都是直接使用malloc和free对相关的数据结构和内存缓冲地址进行内存分配和释放。
打开一个文件作为日志记录,方便程序的跟踪和调式。PrintMsg函数(intf_msg.c)负责打印输出信息、调式信息。
5、 VLC可以根据用户的配置动态加载不同的用户界面和输入输出模块,是怎么做到的?
2)程序运行后,根据程序的配置加载不同的模块和模块中不同的实现。
在RequestPlugin(plugins.c)函数中通过dlopen系统调用打开动态链接库对象。
在GetPluginFunction(plugins.c)函数中通过dlsym系统调用获得动态链接库对象中的函数指针。
VLC 0.1.99 源码分析相关推荐
- 《MySQL 8.0.22执行器源码分析(3.2)关于HashJoinIterator》
在本文章之前,应该了解的概念: 连接的一些概念.NLJ.BNL.HashJoin算法. 目录 关于join连接 probe行保存概念 Hashjoin执行流程(十分重要) HashJoinIterat ...
- Android 8.1/9.0 MTK Camera源码分析之录像快门声音控制流程
前面已经针对拍照快门声音控制流程进行了分析,接下来分析一下录像快门声音的控制流程. Android 8.1/9.0 MTK Camera源码分析之快门声音控制流程 这两篇文章其实都是相对于手机系统RO ...
- Apollo 2.0 框架及源码分析(一) | 软硬件框架
原文地址:https://zhuanlan.zhihu.com/p/33059132 前言 如引言中介绍的,这篇软硬件框架多为现有消息的整合加一些个人的想法.关于 Apollo 介绍的文章已经有许多, ...
- Android 8.1/9.0 MTK Camera源码分析之快门声音控制流程
Android 8.1/9.0 MTK Camera源码分析之快门声音控制 在Android 8.1上mtk camera有控制快门声音的接口,但是并没有了控制录像快门声音的接口.之所以会有这个现象, ...
- mosquitto客户端对象“struct mosquitto *mosq”管理下篇(mosquitto2.0.15客户端源码分析之四)
文章目录 前言 5 设置网络参数 5.1 客户端连接服务器使用的端口号 `mosq->port` 5.2 指定绑定的网络地址 `mosq->bind_address` 5.3 客户端连接服 ...
- 基于Android7.0的Launcher3源码分析(1)——框架设计分析
从事Android rom一年多,一直在负责 Launcher 相关的工作.最近打算写些文章记录下自己对这个模块的理解和源码实现的一些解析. 这些文章将会基于 Android 7.0 的 Launch ...
- vlc 调用live555的源码分析--vlc v2.1.1版本
VLC调用Live555源码解析 以前在看live555的源码和例子的时,发现live555的例子都是回调,这样我们根本无法判断命令是否发送成功,也无法判断发送是否超时:网上搜索,也没有看到有用的资料 ...
- 《MySQL 8.0.22执行器源码分析(4.1)Item_sum类以及聚合》
Item_sum类用于SQL聚合函数的特殊表达式基类. 这些表达式是在聚合函数(sum.max)等帮助下形成的.item_sum类也是window函数的基类. 聚合函数(Aggregate Funct ...
- Android6.0的Looper源码分析(1)
1 Looper简介 Android在Java标准线程模型的基础上,提供了消息驱动机制,用于多线程之间的通信.而其具体实现就是Looper. Android Looper的实现主要包括了3个 ...
最新文章
- array_multisort
- 购物搜索引擎架构的变与不变——淘宝网曲琳
- 垃圾回收器机制(二):快速解读GC算法之标记-清除,复制及标记整理-算法
- qt中关闭窗口资源释放问题
- javascript中==和===的区别
- 关于LeTax中图形放置的参数理解
- SSM+Netty项目结合思路
- PHP简单好看的表白墙网自适应源码+后台
- 南开大学20春计算机应用基础,南开大学-2020春学期《计算机应用基础》在线作业.txt.pdf...
- Element ui tree树形控件获取当前节点id和父节点id
- linux 所有邮件地址群发,linux sendmail群发邮件
- 软件著作权算法软件设计说明书_急求app软件著作权说明书模板
- 详细的vsftpd配置文件讲解
- 基于STM32MP157调试MIPI-DSI屏幕
- 【代码审计】51 TP5框架、无框架 变量覆盖反序列化
- css3中3D变换的景深和灭点
- canvas绘制飞线效果
- ftp工具FileZilla下载安装配置
- 【C++】算法STL库
- Java,第一次作业——六边形面积