播放(控制)器组件:
org.videolan.libvlc.MediaPlayer
媒体数据实体类:
org.videolan.medialibrary.interfaces.media.MediaWrapper
播放布局组件:
org.videolan.libvlc.util.VLCVideoLayout
播放器设置:
val media = mediaFactory.getFromUri(VLCInstance.getInstance(service), uri)
1、设置播放开始时间点字段:
media.addOption(":start-time=${start/1000L}")
2、设置禁止音频输出字段:
media.addOption(":no-audio")
media.addOption(":no-spu")
3、设置是否硬解字段:
media.addOption(":codec=mediacodec_ndk,mediacodec_jni,none")
lib vlc so库加载:
VLCOptions配置lib vlc so库的配置条件,通过LibVlC对象内部进行加载【LibVLC是在初始化MediaPlayer对象时创建的】
额外优化处理:
1、MediaPlayer:Audio外部设备插入和拔出的兼容处理
2、VLCUtil:检查安卓设备CPU频率及其架构ABI和VLC so库的运行匹配兼容性处理
3、网络缓存时间长度字段:"–network-caching=$networkCaching" 【VLCOptions】
4、视频渲染方式设置字段:
if (opengl == 1) options.add("–vout=gles2,none") // enable OpenGL
else if (opengl == 0) options.add("–vout=android_display,none")
5、系统属性通过反射获取
6、LibVLC中检查当前安卓设备是否有硬解码组件并使用
点击播放调用链:
VideoGridFragment点击Item -->VideosViewModel.playVideo()–>MediaUtils.openMedia()–>PlaybackService.load()–>PlaylistManager.load()&playIndex()–>VideoPlayerActivity.startOpened()&onStart()&startPlayback()&loadMedia()–>PlaybackServie.playindex()–>PlayListManager.playIndex()–>PlayerController.startPlayback()–>MediaPlayer.play()&nativePlay()进入JNI native层播放器控制
java层比较好自行分析,因此不展开分析。
JNI层播放控制流程:
一、在LibVlC对象创建时创建native层对象:
1、调用jni层:lib/vlc/libvlcjni.c中,定义如下:
void Java_org_videolan_libvlc_LibVLC_nativeNew(JNIEnv *env, jobject thiz,
jobjectArray jstringArray,
jstring jhomePath)
{
vlcjni_object *p_obj = NULL;
libvlc_instance_t *p_libvlc = NULL;
jstring *strings = NULL;
const char **argv = NULL;
int argc = 0;
if (jhomePath)
{
const char *psz_home = (*env)->GetStringUTFChars(env, jhomePath, 0);
if (psz_home)
{
// 设置环境变量
setenv("HOME", psz_home, 1);
(*env)->ReleaseStringUTFChars(env, jhomePath, psz_home);
}
}
// 设置环境变量
setenv("VLC_DATA_PATH", "/system/usr/share", 1);
if (jstringArray)
{
argc = (*env)->GetArrayLength(env, jstringArray);
argv = malloc(argc * sizeof(const char *));
strings = malloc(argc * sizeof(jstring));
if (!argv || !strings)
{
// 内存分配失败,走失败流程
argc = 0;
goto error;
}
for (int i = 0; i < argc; ++i)
{
strings[i] = (*env)->GetObjectArrayElement(env, jstringArray, i);
if (!strings[i])
{
argc = i;
goto error;
}
argv[i] = (*env)->GetStringUTFChars(env, strings[i], 0);
if (!argv)
{
argc = i;
goto error;
}
}
}
// 最终创建libvlc jni对象,参数为jstringArray内容即vlc的配置参数
p_libvlc = libvlc_new(argc, argv);
error:
// 省略......
}
2、libvlc_new(argc, argv)实现如下:【位于vlc/lib/core.c文件中】
libvlc_instance_t * libvlc_new( int argc, const char *const *argv )
{
// 初始化vlc线程系统,使用安卓native库创建
libvlc_threads_init ();
libvlc_instance_t *p_new = malloc (sizeof (*p_new));
if (unlikely(p_new == NULL))
return NULL;
const char *my_argv[argc + 2];
my_argv[0] = "libvlc"; /* dummy arg0, skipped by getopt() et al */
for( int i = 0; i < argc; i++ )
my_argv[i + 1] = argv[i];
my_argv[argc + 1] = NULL; /* C calling conventions require a NULL */
// 创建libvlc对象:空对象并初始化默认callback类型等
// 该部分内容不展开分析,主要进行libvlc对象初始化,涉及多平台设备定义的类和宏定义比较多,
// 基本使用C语言实现
libvlc_int_t *p_libvlc_int = libvlc_InternalCreate();
if (unlikely (p_libvlc_int == NULL))
goto error;
// 对libvlc空对象进行赋值,参数为vlc播放器的配置参数指令解析并初始化VLC模块组件、
// 消息队列、输入的callback初始化【var.c】、播放列表结构包括节目获取方式和超时时间等。
// 通过环境变量【"HOME"】路径以及【.config】后缀读取VLC配置文件信息
if (libvlc_InternalInit( p_libvlc_int, argc + 1, my_argv ))
{
libvlc_InternalDestroy( p_libvlc_int );
goto error;
}
p_new->p_libvlc_int = p_libvlc_int;
p_new->vlm = NULL;
p_new->ref_count = 1;
p_new->p_callback_list = NULL;
vlc_mutex_init(&p_new->instance_lock);
return p_new;
error:
free (p_new);
libvlc_threads_deinit ();
return NULL;
}
自此第一步根据配置参数创建除了libvlc相关初始化对象。
二、分析播放接口前先分析native层播放器的初始化流程:
1、由前面概述知道,java层播放控制器组件为【org.videolan.libvlc.MediaPlayer】,上一小节已分析native层libvlc对象的创建【当然是有java层LibVLC创建的】,则分析其对应构造函数:
/**
* Create an empty MediaPlayer
*
* @param ILibVLC a valid libVLC
*/
public MediaPlayer(ILibVLC ILibVLC) {
super(ILibVLC);
nativeNewFromLibVlc(ILibVLC, mWindow);
}
// JNI java定义原型
private native void nativeNewFromLibVlc(ILibVLC ILibVLC, AWindow window);
2、JNI层实现:位于【libvlc/jni/libvlc-mediaplayer.c】
void
Java_org_videolan_libvlc_MediaPlayer_nativeNewFromLibVlc(JNIEnv *env,
jobject thiz,
jobject libvlc,
jobject jwindow)
{
// 内部实现通过JAVA虚拟机初始化【JNI_OnLoad()】时初始化的java层对象
// 即从VLCObject的native层全局对象来获取其对应的[fields.VLCObject.mInstanceID]
// 字段值,并强转为指针【vlcjni_object *】,
// mInstanceID对应java层字段为【private long mInstance = 0;】,
// 并且使用jweak来弱引用java层VLCObject的MediaPlayer对象
vlcjni_object *p_obj = VLCJniObject_newFromJavaLibVlc(env, thiz, libvlc);
if (!p_obj)
return;
// 创建播放器播放环境:选择并初始化音视频输出模块等
/* Create a media player playing environment */
p_obj->u.p_mp = libvlc_media_player_new(p_obj->p_libvlc);
// 根据java层window创建native层此引用并缓存,以及播放控制回调事件注册等
MediaPlayer_newCommon(env, thiz, p_obj, jwindow);
}
此处提一下播放控制事件回调java层的jni实现:
// 位于【libvlc/jni/libvlcjni-vlcobject.c】
static void
VLCJniObject_eventCallback(const libvlc_event_t *ev, void *data)
{
vlcjni_object *p_obj = data;
JNIEnv *env = NULL;
assert(p_obj->p_libvlc);
java_event jevent = {
-1, 0, 0, 0.0, NULL };
if (!(env = jni_get_env(THREAD_NAME)))
return;
if (!p_obj->p_owner->pf_event_cb(p_obj, ev, &jevent))
return;
jstring string = jevent.argc1 ? (*env)->NewStringUTF(env, jevent.argc1) : NULL;
// weak对象为此前分析过的java层MediaPlayer对象的native层弱引用,
// 调用其【dispatchEventFromNative】方法即完成将事件通知java层
if (p_obj->p_owner->weak)
(*env)->CallVoidMethod(env, p_obj->p_owner->weak,
fields.VLCObject.dispatchEventFromNativeID,
jevent.type, jevent.arg1, jevent.arg2,
jevent.argf1, string);
if (string)
(*env)->DeleteLocalRef(env, string);
}
// Java层定义: 位于【VLCObject.java】
private synchronized void dispatchEventFromNative(int eventType, long arg1, long arg2, float argf1, @Nullable String args1)
三、接着才开始分析nativePlay()播放接口流程:
1、JNI层实现:位于【libvlc/jni/libvlc-mediaplayer.c】
void
Java_org_videolan_libvlc_MediaPlayer_nativePlay(JNIEnv *env, jobject thiz)
{
// 通过前面分析,此处获取是前面初始化中缓存的实例
vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz);
if (!p_obj)
return;
libvlc_media_player_play(p_obj->u.p_mp);
}
2、播放请求:【vlc/lib/media_player.c】
int libvlc_media_player_play( libvlc_media_player_t *p_mi )
{
// 对输入锁加锁,以免多线程并发问题
lock_input( p_mi );
input_thread_t *p_input_thread = p_mi->input.p_thread;
if( p_input_thread )
{
// 播放输入线程实例已存在则发送播放控制指令,该部分分析见后面的第2.1部分分析
// 注意:此处描述的线程实例指定是包含了一个VLC实现的LOOP功能的线程通道
/* A thread already exists, send it a play message */
input_Control( p_input_thread, INPUT_SET_STATE, PLAYING_S );
unlock_input( p_mi );
return 0;
}
/* Ignore previous exception */
lock(p_mi);
if( !p_mi->p_md )
{
unlock(p_mi);
unlock_input( p_mi );
libvlc_printerr( "No associated media descriptor" );
return -1;
}
for( size_t i = 0; i < ARRAY_SIZE( p_mi->selected_es ); ++i )
p_mi->selected_es[i] = ES_INIT;
// 此处实现:为p_md中的成员【&p_md->p_input_item->event_manager】
//(vlc_event_manager_t)中的每个事件类型对应的callback数组中
// 插入当前请求事件的类型callback
// 即每一个事件都对应有一个callback回调事件
media_attach_preparsed_event( p_mi->p_md );
// 播放输入线程实例不存在则创建,该部分分析见后面的第2.2部分分析
// 注意:注意:此处描述的线程实例指定是包含了一个VLC实现的LOOP功能的线程通道
p_input_thread = input_Create( p_mi, p_mi->p_md->p_input_item, NULL,
p_mi->input.p_resource,
p_mi->input.p_renderer );
unlock(p_mi);
if( !p_input_thread )
{
unlock_input(p_mi);
media_detach_preparsed_event( p_mi->p_md );
libvlc_printerr( "Not enough memory" );
return -1;
}
// 注册当前变量名的变量变化回调事件
var_AddCallback( p_input_thread, "can-seek", input_seekable_changed, p_mi );
var_AddCallback( p_input_thread, "can-pause", input_pausable_changed, p_mi );
var_AddCallback( p_input_thread, "program-scrambled", input_scrambled_changed, p_mi );
var_AddCallback( p_input_thread, "intf-event", input_event_changed, p_mi );
add_es_callbacks( p_input_thread, p_mi );
// 【input_Start】核心功能启动一个LOOP循环线程来执行任务,该部分分析见后面的第2.3部分分析
if( input_Start( p_input_thread ) )
{
// 循环线程初始化失败处理
unlock_input(p_mi);
del_es_callbacks( p_input_thread, p_mi );
var_DelCallback( p_input_thread, "intf-event", input_event_changed, p_mi );
var_DelCallback( p_input_thread, "can-pause", input_pausable_changed, p_mi );
var_DelCallback( p_input_thread, "program-scrambled", input_scrambled_changed, p_mi );
var_DelCallback( p_input_thread, "can-seek", input_seekable_changed, p_mi );
input_Close( p_input_thread );
media_detach_preparsed_event( p_mi->p_md );
libvlc_printerr( "Input initialization failure" );
return -1;
}
p_mi->input.p_thread = p_input_thread;
unlock_input(p_mi);
return 0;
}
2.1、input_Control( p_input_thread, INPUT_SET_STATE, PLAYING_S )分析,实现如下:
// 位于[vlc/src/input/control.c]中
int input_Control( input_thread_t *p_input, int i_query, ... )
{
va_list args;
int i_result;
// args用于获取可变参数部分指即PLAYING_S
va_start( args, i_query );
i_result = input_vaControl( p_input, i_query, args );
va_end( args );
return i_result;
}
int input_vaControl( input_thread_t *p_input, int i_query, va_list args )
{
// 省略无关代码...
switch( i_query )
case INPUT_SET_STATE:
i_int = va_arg( args, int );
// 设置该状态值给变量【"state"】
return var_SetInteger( p_input, "state", i_int );
// 省略无关代码...
}
// 位于【vlc/include/vlc_variables.h】
static inline int var_SetInteger( vlc_object_t *p_obj, const char *psz_name,
int64_t i )
{
// 注意:此处指针p_obj类型和传入值p_input看着不是同一个类型,但其实没有错,
// 因为在上面的调用处其实进行了同名宏定义方法的转换
vlc_value_t val;
val.i_int = i;
return var_SetChecked( p_obj, psz_name, VLC_VAR_INTEGER, val );
}
// 位于【vlc/src/variables.c】中
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 );
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);
// 此处实现为:等待该变量结构未被使用,若正在使用则根据标识挂起即内部实现原理为条件锁
// 如若在执行它的回调事件【如给native/java层】那么将会挂起并设置条件锁后续进行唤醒【即后面的处理】
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;
// 此处触发新旧值的回调事件【如给native/java层】,并对上面【WaitUnused】功能的锁进行唤醒。
// 注意:此处的变量变化回调事件在整个VLC native层中都是非常重要的一个功能执行通道,
// 即功能执行传递命令等通过前面播放器初始化时注册的事件回调方法进行传输和控制
// 且整个VLC的事件回调通道可以为一个“变量”或事件类型注册事件回调来监控
/* 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;
}
2.2、input_Create实现如下:
// 位于【vlc/src/input/input.c】
input_thread_t *input_Create( vlc_object_t *p_parent,
input_item_t *p_item,
const char *psz_log, input_resource_t *p_resource,
vlc_renderer_item_t *p_renderer )
{
return Create( p_parent, p_item, psz_log, false, p_resource, p_renderer );
}
// 调用
static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item,
const char *psz_header, bool b_preparsing,
input_resource_t *p_resource,
vlc_renderer_item_t *p_renderer )
{
/* Allocate descriptor */
input_thread_private_t *priv;
// 此处实现为:通过输入模块类型"input"创建该对象并初始化一些值
//【注:内部实现较为复杂,可自行分析,此处不展开分析了】
priv = vlc_custom_create( p_parent, sizeof( *priv ), "input" );
if( unlikely(priv == NULL) )
return NULL;
input_thread_t *p_input = &priv->input;
// 获取输入模块名
char * psz_name = input_item_GetName( p_item );
msg_Dbg( p_input, "Creating an input for %s'%s'",
b_preparsing ? "preparsing " : "", psz_name);
free( psz_name );
// 更新应用输入选择值给p_input,分析后面见2.2.1部分
// 注意:内部调用了变量事件回调方法
/* Parse input options */
input_item_ApplyOptions( VLC_OBJECT(p_input), p_item );
p_input->obj.header = psz_header ? strdup( psz_header ) : NULL;
/* Init Common fields */
priv->b_preparsing = b_preparsing;
priv->b_can_pace_control = true;
priv->i_start = 0;
priv->i_time = 0;
priv->i_stop = 0;
priv->i_title = 0;
priv->title = NULL;
priv->i_title_offset = input_priv(p_input)->i_seekpoint_offset = 0;
priv->i_state = INIT_S;
priv->is_running = false;
priv->is_stopped = false;
priv->b_recording = false;
priv->i_rate = INPUT_RATE_DEFAULT;
memset( &priv->bookmark, 0, sizeof(priv->bookmark) );
TAB_INIT( priv->i_bookmark, priv->pp_bookmark );
TAB_INIT( priv->i_attachment, priv->attachment );
priv->attachment_demux = NULL;
priv->p_sout = NULL;
priv->b_out_pace_control = false;
priv->p_renderer = p_renderer && b_preparsing == false ?
vlc_renderer_item_hold( p_renderer ) : NULL;
priv->viewpoint_changed = false;
// 从缓存中获取该字段对应的结构体值【vlc_viewpoint_t】
/* Fetch the viewpoint from the mediaplayer or the playlist if any */
vlc_viewpoint_t *p_viewpoint = var_InheritAddress( p_input, "viewpoint" );
if (p_viewpoint != NULL)
priv->viewpoint = *p_viewpoint;
else
vlc_viewpoint_init( &priv->viewpoint );
input_item_Hold( p_item ); /* Released in Destructor() */
priv->p_item = p_item;
/* Init Input fields */
priv->master = NULL;
vlc_mutex_lock( &p_item->lock );
if( !p_item->p_stats )
p_item->p_stats = stats_NewInputStats( p_input );
/* setup the preparse depth of the item
* if we are preparsing, use the i_preparse_depth of the parent item */
if( !priv->b_preparsing )
{
char *psz_rec = var_InheritString( p_parent, "recursive" );
if( psz_rec != NULL )
{
if ( !strcasecmp( psz_rec, "none" ) )
p_item->i_preparse_depth = 0;
else if ( !strcasecmp( psz_rec, "collapse" ) )
p_item->i_preparse_depth = 1;
else
p_item->i_preparse_depth = -1; /* default is expand */
free (psz_rec);
} else
p_item->i_preparse_depth = -1;
}
else
p_input->obj.flags |= OBJECT_FLAGS_QUIET | OBJECT_FLAGS_NOINTERACT;
/* Make sure the interaction option is honored */
if( !var_InheritBool( p_input, "interact" ) )
p_input->obj.flags |= OBJECT_FLAGS_NOINTERACT;
else if( p_item->b_preparse_interact )
{
/* If true, this item was asked explicitly to interact with the user
* (via libvlc_MetadataRequest). Sub items created from this input won't
* have this flag and won't interact with the user */
p_input->obj.flags &= ~OBJECT_FLAGS_NOINTERACT;
}
vlc_mutex_unlock( &p_item->lock );
/* No slave */
priv->i_slave = 0;
priv->slave = NULL;
/* */
if( p_resource )
{
priv->p_resource_private = NULL;
priv->p_resource = input_resource_Hold( p_resource );
}
else
{
priv->p_resource_private = input_resource_New( VLC_OBJECT( p_input ) );
priv->p_resource = input_resource_Hold( priv->p_resource_private );
}
input_resource_SetInput( priv->p_resource, p_input );
/* Init control buffer */
vlc_mutex_init( &priv->lock_control );
vlc_cond_init( &priv->wait_control );
priv->i_control = 0;
vlc_interrupt_init(&priv->interrupt);
// 创建并设置音视频播放控制相关对象变量值,如音频视频、字幕、播放控制参数等
/* Create Object Variables for private use only */
input_ConfigVarInit( p_input );
// 创建所有控制对象变量及其变量变化回调事件(方法)【InputAddCallbacks】
/* Create Objects variables for public Get and Set */
input_ControlVarInit( p_input );
/* */
if( !priv->b_preparsing )
{
char *psz_bookmarks = var_GetNonEmptyString( p_input, "bookmarks" );
if( psz_bookmarks )
{
/* FIXME: have a common cfg parsing routine used by sout and others */
char *psz_parser, *psz_start, *psz_end;
psz_parser = psz_bookmarks;
while( (psz_start = strchr( psz_parser, '{' ) ) )
{
seekpoint_t *p_seekpoint;
char backup;
psz_start++;
psz_end = strchr( psz_start, '}' );
if( !psz_end ) break;
psz_parser = psz_end + 1;
backup = *psz_parser;
*psz_parser = 0;
*psz_end = ',';
p_seekpoint = vlc_seekpoint_New();
if( unlikely( p_seekpoint == NULL ) )
break;
while( (psz_end = strchr( psz_start, ',' ) ) )
{
*psz_end = 0;
if( !strncmp( psz_start, "name=", 5 ) )
{
free( p_seekpoint->psz_name );
p_seekpoint->psz_name = strdup(psz_start + 5);
}
else if( !strncmp( psz_start, "time=", 5 ) )
{
p_seekpoint->i_time_offset = atof(psz_start + 5) *
CLOCK_FREQ;
}
psz_start = psz_end + 1;
}
msg_Dbg( p_input, "adding bookmark: %s, time=%"PRId64,
p_seekpoint->psz_name,
p_seekpoint->i_time_offset );
input_Control( p_input, INPUT_ADD_BOOKMARK, p_seekpoint );
vlc_seekpoint_Delete( p_seekpoint );
*psz_parser = backup;
}
free( psz_bookmarks );
}
}
/* Remove 'Now playing' info as it is probably outdated */
input_item_SetNowPlaying( p_item, NULL );
input_item_SetESNowPlaying( p_item, NULL );
// 发送一个媒体变化事件回调
input_SendEventMeta( p_input );
/* */
memset( &priv->counters, 0, sizeof( priv->counters ) );
vlc_mutex_init( &priv->counters.counters_lock );
priv->p_es_out_display = input_EsOutNew( p_input, priv->i_rate );
priv->p_es_out = NULL;
// 将析构器方法即释放内存方法及其对象绑定成一个释放结构体,有点面向对象的思想
/* Set the destructor when we are sure we are initialized */
vlc_object_set_destructor( p_input, input_Destructor );
return p_input;
}
2.2.1、input_item_ApplyOptions分析:位于【vlc/src/input/item.c】
void input_item_ApplyOptions(vlc_object_t *obj, input_item_t *item)
{
vlc_mutex_lock(&item->lock);
assert(item->optflagc == (unsigned)item->i_options);
// 将所有的输入选择解析并创建对应的对象变量并缓存在obj对象指针中
for (unsigned i = 0; i < (unsigned)item->i_options; i++)
var_OptionParse(obj, item->ppsz_options[i],
!!(item->optflagv[i] & VLC_INPUT_OPTION_TRUSTED));
for (const input_item_opaque_t *o = item->opaques; o != NULL; o = o->next)
{
// 创建该nam对应的变量结构体到obj中
var_Create(obj, o->name, VLC_VAR_ADDRESS);
// 注意此处每次set操作都会调用【var_SetChecked】方法即前面分析过的会调用变量事件回调方法
var_SetAddress(obj, o->name, o->value);
}
vlc_mutex_unlock(&item->lock);
}
2.3、【input_Start】核心功能启动一个LOOP线程来执行任务,实现:位于【vlc/src/input/input.c】
/**
* Start a input_thread_t created by input_Create.
*
* You must not start an already running input_thread_t.
*
* \param the input thread to start
*/
int input_Start( input_thread_t *p_input )
{
input_thread_private_t *priv = input_priv(p_input);
// pthread线程将要执行的方法指针,该部分分析见2.3.1部分分析
void *(*func)(void *) = Run;
if( priv->b_preparsing )
// pthread线程将要执行的方法指针,该部分分析见2.3.2部分分析
func = Preparse;
assert( !priv->is_running );
// 此处主要实现的是启动线程并执行任务方法(指针),不展开分析
/* Create thread and wait for its readiness. */
priv->is_running = !vlc_clone( &priv->thread, func, priv,
VLC_THREAD_PRIORITY_INPUT );
if( !priv->is_running )
{
input_ChangeState( p_input, ERROR_S );
msg_Err( p_input, "cannot create input thread" );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
2.3.1、Run实现分析:位于【vlc/src/input/input.c】
/*****************************************************************************
* Run: main thread loop 【指的vlc任务工作(主)线程而非APP的主线程】
* This is the "normal" thread that spawns the input processing chain,
* reads the stream, cleans up and waits
*****************************************************************************/
static void *Run( void *data )
{
input_thread_private_t *priv = data;
input_thread_t *p_input = &priv->input;
// 设置线程被中断的监听
vlc_interrupt_set(&priv->interrupt);
// Init分析请见2.3.1.1部分分析
if( !Init( p_input ) )
{
// 成功
if( priv->b_can_pace_control && priv->b_out_pace_control )
{
// 不让线程占用整个CPU时间片,就不设置很高的线程优先级别
/* We don't want a high input priority here or we'll
* end-up sucking up all the CPU time */
vlc_set_priority( priv->thread, VLC_THREAD_PRIORITY_LOW );
}
// VLC工作任务主线程循环开启
// Init分析请见2.3.1.2部分分析
MainLoop( p_input, true ); /* FIXME it can be wrong (like with VLM) */
// 主线程循环结束,清除内存
/* Clean up */
End( p_input );
}
// 主线程结束【挂掉】则发出通知
input_SendEventDead( p_input );
return NULL;
}
2.3.1.1、Init实现:位于【vlc/src/input/input.c】
static int Init( input_thread_t * p_input )
{
input_thread_private_t *priv = input_priv(p_input);
input_source_t *master;
if( var_Type( p_input->obj.parent, "meta-file" ) )
{
// 如果输入是媒体元文件,则禁用不必要的参数选项
msg_Dbg( p_input, "Input is a meta file: disabling unneeded options" );
var_SetString( p_input, "sout", "" );
var_SetBool( p_input, "sout-all", false );
var_SetString( p_input, "input-slave", "" );
var_SetInteger( p_input, "input-repeat", 0 );
var_SetString( p_input, "sub-file", "" );
var_SetBool( p_input, "sub-autodetect-file", false );
}
// 一个logging开关实现【libvlc_priv_t.b_stats字段】:
// 用于收集样本数据的统计结果数据的分析即debug等。
// 初始化音视频样本数据统计计数器,如解复用、解码、缓冲区、已展示或丢帧等功能计数
InitStatistics( p_input );
#ifdef ENABLE_SOUT
// 若启动了渲染器流输出链模块进行音视频输出(RTP、UDP、标准输出等),则初始化sout模块【stream_out】,
// 如果已存在有效的sout则重复使用,否则创建一个新的链流输出模块。
// 即根据多种输出链方式进行初始化对应的多个输出流模块即输出流模块链
// 注:该实现内部有对输出链流方式如URI的解析处理等,此处不展开分析,内部的【"sout"】变量的值如输出流URI
if( InitSout( p_input ) )
goto error;
#endif
// 创建ES输出模块
/* Create es out */
priv->p_es_out = input_EsOutTimeshiftNew( p_input, priv->p_es_out_display,
priv->i_rate );
if( priv->p_es_out == NULL )
goto error;
/* */
// 更新播放器状态值并发送值变化回调事件
input_ChangeState( p_input, OPENING_S );
// 更新和发送cache变量值变化回调事件
input_SendEventCache( p_input, 0.0 );
// 该实现代码比较多,不展开分析,主要如下功能:
// 创建一个输入源所有信息集合结构体:如媒体源地址解析、
// 【会检查是否已创建】创建解复用模块结构信息【如通过输入流的mime-type名或扩展名匹配解复用名来创建】、
// 创建流结构体信息、从媒体源解析媒体名和seek信息、关联流block块访问结构体信息方法指针、
// 添加用户流过滤器、显示的将流过滤器和流绑定、使用已创建的访问流创建常规demux结构体信息
// 创建解复用过滤渲染器、是否可以seek/播放速度/帧率控制等,【"clock-synchro"】时间同步策略
/* */
master = InputSourceNew( p_input, priv->p_item->psz_uri, NULL, false );
if( master == NULL )
goto error;
priv->master = master;
// 初始化Title片段等
InitTitle( p_input );
// 加载媒体主要信息,如媒体时长并发送时长和开始播放位置等事件
/* Load master infos */
/* Init length */
mtime_t i_length;
if( demux_Control( master->p_demux, DEMUX_GET_LENGTH, &i_length ) )
i_length = 0;
if( i_length <= 0 )
i_length = input_item_GetDuration( priv->p_item );
input_SendEventLength( p_input, i_length );
input_SendEventPosition( p_input, 0.0, 0 );
if( !priv->b_preparsing )
{
StartTitle( p_input );
SetSubtitlesOptions( p_input );
// 加载字幕文件等信息
LoadSlaves( p_input );
// 初始化程序:计算PTS时长、通知解复用块参数等
InitPrograms( p_input );
double f_rate = var_InheritFloat( p_input, "rate" );
if( f_rate != 0.0 && f_rate != 1.0 )
{
// 码率
vlc_value_t val = {
.i_int = INPUT_RATE_DEFAULT / f_rate };
input_ControlPush( p_input, INPUT_CONTROL_SET_RATE, &val );
}
}
if( !priv->b_preparsing && priv->p_sout )
{
priv->b_out_pace_control = priv->p_sout->i_out_pace_nocontrol > 0;
msg_Dbg( p_input, "starting in %ssync mode",
priv->b_out_pace_control ? "a" : "" );
}
vlc_meta_t *p_meta = vlc_meta_New();
if( p_meta != NULL )
{
// 初始化媒体元数据信息:title、artist、url等
/* Get meta data from users */
InputMetaUser( p_input, p_meta );
/* Get meta data from master input */
InputSourceMeta( p_input, master, p_meta );
/* And from slave */
for( int i = 0; i < priv->i_slave; i++ )
InputSourceMeta( p_input, priv->slave[i], p_meta );
es_out_ControlSetMeta( priv->p_es_out, p_meta );
vlc_meta_Delete( p_meta );
}
msg_Dbg( p_input, "`%s' successfully opened",
input_priv(p_input)->p_item->psz_uri );
// 初始化完成,通知播放状态改变
/* initialization is complete */
input_ChangeState( p_input, PLAYING_S );
return VLC_SUCCESS;
error:
input_ChangeState( p_input, ERROR_S );
// ...省略部分代码
return VLC_EGENERIC;
}
2.3.1.2、MainLoop实现:位于【vlc/src/input/input.c】
/**
* MainLoop
* The main input loop.
*/
static void MainLoop( input_thread_t *p_input, bool b_interactive )
{
mtime_t i_intf_update = 0;
mtime_t i_last_seek_mdate = 0;
// 此处[b_interactive]为true,再根据变量【"start-paused"】的值判断是否需要暂停
if( b_interactive && var_InheritBool( p_input, "start-paused" ) )
ControlPause( p_input, mdate() );
bool b_pause_after_eof = b_interactive &&
var_InheritBool( p_input, "play-and-pause" );
bool b_paused_at_eof = false;
demux_t *p_demux = input_priv(p_input)->master->p_demux;
const bool b_can_demux = p_demux->pf_demux != NULL;
// while条件实现了主工作线程的线程循环即让当前主工作线程不要执行完毕就结束,
// 在没有错误的情况下必须等待任务进入唤醒再次工作
// 条件:只要是输入线程状态未退出且执行状态不为错误状态,则执行线程循环任务
while( !input_Stopped( p_input ) && input_priv(p_input)->i_state != ERROR_S )
{
mtime_t i_wakeup = -1;
bool b_paused = input_priv(p_input)->i_state == PAUSE_S;
// 注意:此处提示特殊情况下会有bug【如暂停状态下进行seek操作】,因此此处进行check并纠正
// 如果已经是暂停状态,则判断是否有输出端缓冲区buffer或者当前媒体数据是否已经结束(EOF)
// 主要作用:check是否是真的已经暂停(读取媒体数据结束)
/* FIXME if input_priv(p_input)->i_state == PAUSE_S the access/access_demux
* is paused -> this may cause problem with some of them
* The same problem can be seen when seeking while paused */
if( b_paused )
b_paused = !es_out_GetBuffering( input_priv(p_input)->p_es_out )
|| input_priv(p_input)->master->b_eof;
if( !b_paused )
{
// 未结束
if( !input_priv(p_input)->master->b_eof )
{
// 解复用器解析未结束,当次解析更多数据
bool b_force_update = false;
// 实现:判断或获取当前播放的开始时间、暂停时间、当前时间等,解复用解析一些数据
// 分析见2.3.1.2.1分析
MainLoopDemux( p_input, &b_force_update );
if( b_can_demux ) // 可以进行解复用,获取解复用模块的唤醒时间即解复用更多数据的耗时时间
i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );
if( b_force_update )
i_intf_update = 0;
b_paused_at_eof = false;
}
else if( !es_out_GetEmpty( input_priv(p_input)->p_es_out ) )
{
// 等待解析数据的使用完毕
// ES 等待解码器数据清空,并获取当前时间加上一个线程空闲睡眠时长来等待数据清空【时钟步长100毫秒】
msg_Dbg( p_input, "waiting decoder fifos to empty" );
i_wakeup = mdate() + INPUT_IDLE_SLEEP;
}
// 当输入信息为可暂停时,则在数据解析结束eof后暂停,
// 这样就不会无缘无故浪费时间了
/* Pause after eof only if the input is pausable.
* This way we won't trigger timeshifting for nothing */
else if( b_pause_after_eof && input_priv(p_input)->b_can_pause )
{
// 如果已经在eof时暂停过了,则执行退出当前线程LOOP循环机制,结束当前输入源的播放等
if( b_paused_at_eof )
break;
// 暂停状态
vlc_value_t val = {
.i_int = PAUSE_S };
// 发送暂停状态,如果当前正在播放状态才执行暂停状态切换
msg_Dbg( p_input, "pausing at EOF (pause after each)");
Control( p_input, INPUT_CONTROL_SET_STATE, val );
b_paused = true;
b_paused_at_eof = true;
}
else
{
// 其他情况则根据重复播放次数来判断是否需要当前输入源数据重复播放操作,失败则退出
if( MainLoopTryRepeat( p_input ) )
break;
}
// 如果当前时间大于需要更新的时间点时间,则立即更新时间信息和各状态变量统计值信息
/* Update interface and statistics */
mtime_t now = mdate();
if( now >= i_intf_update )
{
// 更新时间信息和各状态变量统计值信息[输入源信息、解复用信息、音视频输出信息等]
MainLoopStatistics( p_input );
// 250毫秒的更新间隔时间
i_intf_update = now + INT64_C(250000);
}
}
// 此循环体用来处理控制线程唤醒操作等
/* Handle control */
for( ;; )
{
// 唤醒时间点
mtime_t i_deadline = i_wakeup;
// 是否应该延期睡眠等待即延迟seek等待ES缓冲完成或者至少等待125毫秒时长
// 此处条件为ES缓冲数据没有了或者解复用未结束【eof】时则执行延期等待
/* Postpone seeking until ES buffering is complete or at most
* 125 ms. */
bool b_postpone = es_out_GetBuffering( input_priv(p_input)->p_es_out )
&& !input_priv(p_input)->master->b_eof;
if( b_postpone )
{
// 需要延迟获取数据
mtime_t now = mdate();
// 查找[seek]时每20ms重新检查ES缓冲区数据缓冲状态
// 判断条件:当然时间点在上次seek时间点125毫秒以内,
// 且唤醒时间点未设置时或者唤醒时间点超过当前时间点20毫秒,
// 则纠正修改唤醒时间为当前时间点延迟20毫秒后进行唤醒。
// 反之则不需要延迟等待数据
/* Recheck ES buffer level every 20 ms when seeking */
if( now < i_last_seek_mdate + INT64_C(125000)
&& (i_deadline < 0 || i_deadline > now + INT64_C(20000)) )
i_deadline = now + INT64_C(20000);
else
b_postpone = false;
}
int i_type;
vlc_value_t val;
// 该方法实现:如果没有控制命令模块【buffer】时或需要延迟获取数据且有延迟时间,
// 则进行线程wait操作,等待唤醒。并且POP出释放已减少的控制命令模块内存
if( ControlPop( p_input, &i_type, &val, i_deadline, b_postpone ) )
{
// 若唤醒时间到期后则进入此处,如果b_postpone为需要延迟执行则继续for循环,
// 否则若唤醒时间到了则结束for循环从而让外部while循环去获取当前解析出来的相关数据
if( b_postpone )
continue;
break; /* Wake-up time reached */
}
#ifndef NDEBUG
msg_Dbg( p_input, "control type=%d", i_type );
#endif
// 执行当前控制指令及其更新发送控制数据
if( Control( p_input, i_type, val ) )
{
// 控制指令成功后则记录当次seek操作时间点,并要求
if( ControlIsSeekRequest( i_type ) )
i_last_seek_mdate = mdate();
i_intf_update = 0;
}
// 更新等待解复用的唤醒时间
/* Update the wakeup time */
if( i_wakeup != 0 )
i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );
}
}
}
2.3.1.2.1 MainLoopDemux实现:位于【vlc/src/input/input.c】
/**
* MainLoopDemux
* It asks the demuxer to demux some data
*/
static void MainLoopDemux( input_thread_t *p_input, bool *pb_changed )
{
input_thread_private_t* p_priv = input_priv(p_input);
demux_t *p_demux = p_priv->master->p_demux;
int i_ret = VLC_DEMUXER_SUCCESS;
*pb_changed = false;
if( p_priv->i_stop > 0 )
{
// 设置了播放的停止时间
// 获取解复用信息后的当前播放时间,若获取失败则从头开始
if( demux_Control( p_demux, DEMUX_GET_TIME, &p_priv->i_time ) )
p_priv->i_time = 0;
// 若播放的停止时间小于当前播放时间,则标识当前媒体数据源解析结束
if( p_priv->i_stop <= p_priv->i_time )
i_ret = VLC_DEMUXER_EOF;
}
// 注意此处只解析一帧数据
if( i_ret == VLC_DEMUXER_SUCCESS )
i_ret = demux_Demux( p_demux );
i_ret = i_ret > 0 ? VLC_DEMUXER_SUCCESS : ( i_ret < 0 ? VLC_DEMUXER_EGENERIC : VLC_DEMUXER_EOF);
if( i_ret == VLC_DEMUXER_SUCCESS )
{
// 解复用信息检查更新
if( demux_TestAndClearFlags( p_demux, INPUT_UPDATE_TITLE_LIST ) )
// 更新title列表信息【可能有多个title】等
UpdateTitleListfromDemux( p_input );
if( p_priv->master->b_title_demux )
{
// title解析成功,则进行title和seekpoint参数的更新和通知
i_ret = UpdateTitleSeekpointFromDemux( p_input );
// 强制更新
*pb_changed = true;
}
// 媒体元通用属性值的获取更新和通知
UpdateGenericFromDemux( p_input );
}
if( i_ret == VLC_DEMUXER_EOF )
{
msg_Dbg( p_input, "EOF reached" );
p_priv->master->b_eof = true;
es_out_Eos(p_priv->p_es_out);
}
else if( i_ret == VLC_DEMUXER_EGENERIC )
{
input_ChangeState( p_input, ERROR_S );
}
else if( p_priv->i_slave > 0 )
// 解复用成功时保存当前输入源媒体数据,并使用循环体解复用解析出足够数据
SlaveDemux( p_input );
}
2.3.2、Preparse实现:位于【vlc/src/input/input.c】,当前方法执行条件为正在进行预解析
static void *Preparse( void *data )
{
input_thread_private_t *priv = data;
input_thread_t *p_input = &priv->input;
vlc_interrupt_set(&priv->interrupt);
// 此处Init分析请参考Run方法中Init的分析流程
if( !Init( p_input ) )
{
/* if the demux is a playlist, call Mainloop that will call
* demux_Demux in order to fetch sub items */
bool b_is_playlist = false;
// 如果demux是一个播放列表,那么调用Mainloop,
// 它将调用demux_Demux来获取子项。
// 是否应该预解析子项,若需要并且执行控制指令失败了则直接播放失败结束
if ( input_item_ShouldPreparseSubItems( priv->p_item )
&& demux_Control( priv->master->p_demux, DEMUX_IS_PLAYLIST,
&b_is_playlist ) )
b_is_playlist = false;
if( b_is_playlist )
MainLoop( p_input, false ); // 参考Run方法中的分析
End( p_input );
}
input_SendEventDead( p_input );
return NULL;
}
3、至此,播放请求的数据解析流程和控制交互流程、线程循环体Loop结构的主要数据解析、交互流程分析完毕,接下来将分析控制交互和demux解复用层的具体数据传递和控制流程,以及decoder解码流程和输出展示流程。
四、播放控制交互与demux解复用层的具体数据传递和控制流程
1、见后续章节分析
五、Decoder解码流程层的具体数据传递和控制流程
1、见后续章节分析
六、Out输出层的具体数据传递和控制流程
1、见后续章节分析