播放(控制)器组件:
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 )
{