目录
一:概述
版本 v3.0.16 源码
http://get.videolan.org/vlc/3.0.16/vlc-3.0.16.tar.xz
每一完整的软件系统都会有一个参数配置模块,用来实现运行时不同的参数输入,有些软件系统会做得比较简单,直接通过方法的输入,作为参数,有些系统做得比较通用复杂,比如vlc,也是比较完整独立的通用的配置。
vlc 使用c语言编写,实质是一个多媒体播放器, 有OB面向对象的设计,对一个结构体的创建,可以类比为创建一个对象,vlc都通过使用malloc等函数,动态申请内存来存储这个对象(结构体变量)。它花哨的地方在于,每一个对象的创建(也就是申请这个结构体内存空间),都在这个结构体相邻的位置多申请了一块空间,用来存储一个内置的结构体--vlc_object_internals_t。 而在这个内置的vlc_object_internals_t中,放置了存储配置的二叉排序树,var_root。 vlc用c语言实现了自己的二叉树存储,里面存放的统一的参数配置类型,键值对,以字符串作为索引,存储一个自定义的通用联合体类型的结构体数据。 等同于c++的map了吧。
不过这个数据还加了一套机制,就是可以给每一个参数设置一个单独的回调,当参数被修改,可以触发回调,这个就很有作用了。
这样每一个对象(结构体变量),都有独立的一份 二叉排序树,用来存储读取自己的配置参数。
vlc 使用c语言编写,实质是一个多媒体播放器, 有OB面向对象的设计,对一个结构体的创建,可以类比为创建一个对象,vlc都通过使用malloc等函数,动态申请内存来存储这个对象(结构体变量)。它花哨的地方在于,每一个对象的创建(也就是申请这个结构体内存空间),都在这个结构体相邻的位置多申请了一块空间,用来存储一个内置的结构体--vlc_object_internals_t。 而在这个内置的vlc_object_internals_t中,放置了存储配置的二叉排序树,var_root。 vlc用c语言实现了自己的二叉树存储,里面存放的统一的参数配置类型,键值对,以字符串作为索引,存储一个自定义的通用联合体类型的结构体数据。 等同于c++的map了吧。
不过这个数据还加了一套机制,就是可以给每一个参数设置一个单独的回调,当参数被修改,可以触发回调,这个就很有作用了。
这样每一个对象(结构体变量),都有独立的一份 二叉排序树,用来存储读取自己的配置参数。
来一张比较概略的关系图:
分析代码入口
上面的v3.0.16下载源码,跟踪分析源码的流程可以参考我这篇博客https://blog.csdn.net/u012459903/article/details/87631486
从bin/vlc.c main()函数作为入口(这个是linux上命令版本的程序,没有ui界面干扰)
二:特性
1.0 vlc 用c语言实现的"面向对象"
每一个对象都是使用malloc等函数从堆上申请存储空间,每一个对象(结构体)内部第一个成员为 object, 后续对这个对象的操作只需要知道object的指针,在具体使用的使用利用成员变量相对于结构体的偏移量,从object向前偏移就可以得到结构体指针,这样所有函数的参数和返回值都可以统一成object指针的形式。
#define container_of(ptr, type, member) \
((type *)(((char *)(ptr)) - offsetof(type, member)))
2.0 支持的参数数据类型
统一为自实现的一个键值对,字符串--数据。数据支持的类型除了基本类型外,还可以扩展,比较全面。
3.0 参数变动时的监听回调
每一项具体的参数都可以为其设置专门的回调,当参数被修改时,可以触发这个回调
4.0 参数读写查找的操作效率
vlc内部自行实现的一颗二叉排序树,对参数的插入,查找都是经过这课树,等同于c++的map的效率了。
5.0 多线程安全
参数配置对象,都有自己独立的锁。 对本对象的配置修改都会通过这个锁来确保线程安全,并且每一个参数在被修改的时候也会进入条件等待等到上一次触发的回调执行完,才触发本次回调。
struct vlc_object_internals
{
alignas (max_align_t) /* ensure vlc_externals() is maximally aligned */
char *psz_name; /* given name */
/* Object variables */
void *var_root;
vlc_mutex_t var_lock;
vlc_cond_t var_wait;
/* Objects management */
atomic_uint refs;
vlc_destructor_t pf_destructor;
/* Objects tree structure */
vlc_object_internals_t *next; /* next sibling */
vlc_object_internals_t *prev; /* previous sibling */
vlc_object_internals_t *first; /* first child */
vlc_mutex_t tree_lock;
/* Object resources */
struct vlc_res *resources;
};
三:详细分析下其参数的存储,读写
1.参数定义
名称+值,回调函数,使用统计。
这是一个联合体,除了基本的数据类型之外,也可以支持void* 指针,扩展也比较容易
/**
* VLC value structure
*/
typedef union
{
int64_t i_int;
bool b_bool;
float f_float;
char * psz_string;
void * p_address;
vlc_list_t * p_list;
struct { int32_t x; int32_t y; } coords;
} vlc_value_t;
/**
* The structure describing a variable.
* \note vlc_value_t is the common union for variable values
*/
struct variable_t
{
char * psz_name; /**< The variable unique name (must be first) */
/** The variable's exported value */
vlc_value_t val;
/** The variable display name, mainly for use by the interfaces */
char * psz_text;
const variable_ops_t *ops;
int i_type; /**< The type of the variable */
unsigned i_usage; /**< Reference count */
/** If the variable has min/max/step values */
vlc_value_t min, max, step;
/** List of choices */
vlc_list_t choices;
/** List of friendly names for the choices */
vlc_list_t choices_text;
/** Set to TRUE if the variable is in a callback */
bool b_incallback;
/** Registered value callbacks */
callback_table_t value_callbacks;
/** Registered list callbacks */
callback_table_t list_callbacks;
};
2.参数创建
创建的时候,根据key值,插入到一颗二叉排序树中去。
tsearch
() 函数
/**
* Initialize a vlc variable
*
* We hash the given string and insert it into the sorted list. The insertion
* may require slow memory copies, but think about what we gain in the log(n)
* lookup phase when setting/getting the variable value!
*
* \param p_this The object in which to create the variable
* \param psz_name The name of the variable
* \param i_type The variables type. Must be one of \ref var_type combined with
* zero or more \ref var_flags
*/
int var_Create( vlc_object_t *p_this, const char *psz_name, int i_type )
{
assert( p_this );
variable_t *p_var = calloc( 1, sizeof( *p_var ) );
if( p_var == NULL )
return VLC_ENOMEM;
p_var->psz_name = strdup( psz_name );
p_var->psz_text = NULL;
p_var->i_type = i_type & ~VLC_VAR_DOINHERIT;
p_var->i_usage = 1;
p_var->choices.i_count = 0;
p_var->choices.p_values = NULL;
p_var->choices_text.i_count = 0;
p_var->choices_text.p_values = NULL;
p_var->b_incallback = false;
p_var->value_callbacks = (callback_table_t){ 0, NULL };
/* Always initialize the variable, even if it is a list variable; this
* will lead to errors if the variable is not initialized, but it will
* not cause crashes in the variable handling. */
switch( i_type & VLC_VAR_CLASS )
{
case VLC_VAR_BOOL:
p_var->ops = &bool_ops;
p_var->val.b_bool = false;
break;
case VLC_VAR_INTEGER:
p_var->ops = &int_ops;
p_var->val.i_int = 0;
p_var->min.i_int = INT64_MIN;
p_var->max.i_int = INT64_MAX;
break;
case VLC_VAR_STRING:
p_var->ops = &string_ops;
p_var->val.psz_string = NULL;
break;
case VLC_VAR_FLOAT:
p_var->ops = &float_ops;
p_var->val.f_float = 0.f;
p_var->min.f_float = -FLT_MAX;
p_var->max.f_float = FLT_MAX;
break;
case VLC_VAR_COORDS:
p_var->ops = &coords_ops;
p_var->val.coords.x = p_var->val.coords.y = 0;
break;
case VLC_VAR_ADDRESS:
p_var->ops = &addr_ops;
p_var->val.p_address = NULL;
break;
case VLC_VAR_VOID:
p_var->ops = &void_ops;
break;
default:
vlc_assert_unreachable ();
}
if (i_type & VLC_VAR_DOINHERIT)
var_Inherit(p_this, psz_name, i_type, &p_var->val);
vlc_object_internals_t *p_priv = vlc_internals( p_this );
variable_t **pp_var, *p_oldvar;
int ret = VLC_SUCCESS;
vlc_mutex_lock( &p_priv->var_lock );
pp_var = tsearch( p_var, &p_priv->var_root, varcmp );
if( unlikely(pp_var == NULL) )
ret = VLC_ENOMEM;
else if( (p_oldvar = *pp_var) == p_var ) /* Variable create */
p_var = NULL; /* Variable created */
else /* Variable already exists */
{
assert (((i_type ^ p_oldvar->i_type) & VLC_VAR_CLASS) == 0);
p_oldvar->i_usage++;
p_oldvar->i_type |= i_type & VLC_VAR_ISCOMMAND;
}
vlc_mutex_unlock( &p_priv->var_lock );
/* If we did not need to create a new variable, free everything... */
if( p_var != NULL )
Destroy( p_var );
return ret;
}
3.给参数设置回调(observer的形式)
可以给每一参数设置一个回调,在该参数被修改之后,会触发这个回调。比如media_player.c 中对音量参数设置了一个回调
//media_player.c libvlc_media_player_new()
var_AddCallback(mp, "volume", volume_changed, NULL);
4.参数修改
修改参数之前,要先创建参数。然后设置,设置的时候先确定数据没有正在被上一次的修改回调调用,如果正在调用上一次修改的回调,则会执行条件等待,修改完之后触发回调。
//要先创建
var_Create( p_libvlc, "app-id", VLC_VAR_STRING );
var_SetString( p_libvlc, "app-id", "org.VideoLAN.VLC" );
int var_SetChecked( vlc_object_t *p_this, const char *psz_name,
int expected_type, vlc_value_t val )
{
variable_t *p_var;
vlc_value_t oldval;
assert( p_this );
vlc_object_internals_t *p_priv = vlc_internals( p_this );
//要先查找确保有该参数
p_var = Lookup( p_this, psz_name ); //内部会 lock上锁
if( p_var == NULL )
{
vlc_mutex_unlock( &p_priv->var_lock );
return VLC_ENOVAR;
}
//确保 设置的参数类型 和 设置的数据值类型 一致
assert( expected_type == 0 ||
(p_var->i_type & VLC_VAR_CLASS) == expected_type );
assert ((p_var->i_type & VLC_VAR_CLASS) != VLC_VAR_VOID);
//条件等待,等到数据没有在使用
WaitUnused( p_this, p_var );
/* Duplicate data if needed */
p_var->ops->pf_dup( &val );
/* Backup needed stuff */
oldval = p_var->val;
/* Check boundaries and list */
CheckValue( p_var, &val );
/* Set the variable */
p_var->val = val;
/* Deal with callbacks */
TriggerCallback( p_this, p_var, psz_name, oldval );
/* Free data if needed */
p_var->ops->pf_free( &oldval );
vlc_mutex_unlock( &p_priv->var_lock );
return VLC_SUCCESS;
}
/**
* Waits until the variable is inactive (i.e. not executing a callback)
*/
static void WaitUnused(vlc_object_t *obj, variable_t *var)
{
vlc_object_internals_t *priv = vlc_internals(obj);
mutex_cleanup_push(&priv->var_lock);
while (var->b_incallback)
vlc_cond_wait(&priv->var_wait, &priv->var_lock);
vlc_cleanup_pop();
}
static void TriggerCallback(vlc_object_t *obj, variable_t *var,
const char *name, vlc_value_t prev)
{
assert(obj != NULL);
size_t count = var->value_callbacks.i_entries;
if (count == 0)
return;
callback_entry_t *entries = var->value_callbacks.p_entries;
vlc_object_internals_t *priv = vlc_internals(obj);
assert(!var->b_incallback);
var->b_incallback = true;
vlc_mutex_unlock(&priv->var_lock);
for (size_t i = 0; i < count; i++)
entries[i].pf_value_callback(obj, name, prev, var->val,
entries[i].p_data);
vlc_mutex_lock(&priv->var_lock);
var->b_incallback = false;
vlc_cond_broadcast(&priv->var_wait);
}
5.参数获取
有两种获取方法,比如获取bool形数据:
var_GetBool() //在本对象中获取
var_InheritBool() //向上遍历,继承属性。 这个的路线是基于实例的关系,和vlc_object_internal的链表关系无关,所以,有些属性可以从父对象中“继承”
/*
* Override default configuration with config file settings
*/
if( !var_InheritBool( p_libvlc, "ignore-config" ) )
{
if( var_InheritBool( p_libvlc, "reset-config" ) )
config_SaveConfigFile( p_libvlc ); /* Save default config */
else
config_LoadConfigFile( p_libvlc );
}
/**
* Finds the value of a variable. If the specified object does not hold a
* variable with the specified name, try the parent object, and iterate until
* the top of the tree. If no match is found, the value is read from the
* configuration.
*/
int var_Inherit( vlc_object_t *p_this, const char *psz_name, int i_type,
vlc_value_t *p_val )
{
i_type &= VLC_VAR_CLASS;
for( vlc_object_t *obj = p_this; obj != NULL; obj = obj->obj.parent )
{
if( var_GetChecked( obj, psz_name, i_type, p_val ) == VLC_SUCCESS )
return VLC_SUCCESS;
}
/* else take value from config */
switch( i_type & VLC_VAR_CLASS )
{
case VLC_VAR_STRING:
p_val->psz_string = config_GetPsz( p_this, psz_name );
if( !p_val->psz_string ) p_val->psz_string = strdup("");
break;
case VLC_VAR_FLOAT:
p_val->f_float = config_GetFloat( p_this, psz_name );
break;
case VLC_VAR_INTEGER:
p_val->i_int = config_GetInt( p_this, psz_name );
break;
case VLC_VAR_BOOL:
p_val->b_bool = config_GetInt( p_this, psz_name ) > 0;
break;
default:
vlc_assert_unreachable();
case VLC_VAR_ADDRESS:
return VLC_ENOOBJ;
}
return VLC_SUCCESS;
}