VLC系列文章
深入理解VLC之代码流程
VLC,著名的开源播放器项目,它虽然很庞大,但是在架构设计上也高度模块化。幸运的是,官方wiki非常详细,无论是大的架构设计,还是每一个模块里面的代码细节,都有详尽的介绍。wiki链接:https://wiki.videolan.org/Hacker_Guide/。
本文主要以vlc-android项目为例,介绍vlc的架构设计,参考了一篇对vlc架构分析得很好的文章:https://jiya.io/archives/vlc_learn_2.html
下载vlc-android项目代码并编译之后,得到主要目录结构如下
libvlc jni层以及java api
medialibrary 媒体数据库相关的代码
vlc native代码,后面分析的重点
vlc-android 应用的代码,编出来的apk就在vlc-andoird/debug目录下
在介绍vlc目录下的代码结构之前,先来说一下vlc的架构设计:
vlc的架构设计可以分作两层:
第一层是对整条播放通路(pipeline)的抽象,包含以下几个抽象概念
playlist: playlist表示播放列表,VLC在启动后,即创建一个playlist thread,用户输入后,动态创建input。
input: input表示输入,当用户通过界面输入一个文件或者流地址时,input thread 被动态创建,该线程的生命周期直到本次播放结束。
access: access表示访问,是VLC抽象的一个层,该层向下直接使用文件或网络IO接口,向上为stream层服务,提供IO接口。
stream: stream表示流,是VLC抽象的一个层,该层向下直接使用access层提供的IO接口,向上为demux层服务,提供IO接口。
demux: demux表示解复用,该层向下直接使用stream层提供的IO接口,数据出来后送es_out。
es_out: es_out表示输出,是VLC抽象的一个层,该层获取demux后的数据,送decode解码。
decode: decode表示解码,是视频技术中的概念,获取es_out出来的数据(通过一个fifo交互),解码后送output。
output: output表示输出,获取从decode出来的数据,送readerer。
readerer: readerer表示显示,获取从output出来的数据(通过一个fifo交互),然后显示。
第二层是对播放通路中每一个环节,都提供了诸多可选的module,例如decode,就同时提供了包含ffmpeg软解以及android mediacodec硬解在内的多个可选解码module。
在播放流程开始后,就按照平台、媒体格式选择合适的module,当然,我们也可以设置参数强制要求使用某些module。
有了以上概念之后,再来看vlc目录下的主要代码结构,如下(参考https://wiki.videolan.org/VLC_source_tree/)
.
├──contrib 依赖库相关,所有依赖库列表参见https://wiki.videolan.org/Contrib_Status
│ ├── arm-linux-androideabi build之后的依赖库
│ ├── contrib-android-arm-linux-androideabi 依赖库代码
│ ├── src 依赖库的patch和makefile
│ └── tarballs 下载的依赖库tar包
├── lib libvlc的api
├── modules 各个模块的代码
│ ├── access
│ ├── audio_filter audio resample与格式转换
│ ├── audio_output audio输出,包含android audiotrack与opensl等
│ ├── codec
│ ├── demux
│ ├── text_renderer 文本绘制
│ ├── video_chroma 视频颜色格式转换
│ ├── video_filter 视频filter,如deinterlace,还包含一些滤镜,如老电影效果
│ ├── video_output 视频输出,包含android nativewindow,opengl等
├── src
├── android Android平台相关代码
├── audio_output 初始化audio mixer,从decoder拿到audio frames处理后输出
├── config 命令行参数解析
├── input
├── interface UI相关
├── linux linux 平台相关代码
├── misc 杂项,包含消息队列,picture fifo, mime type, cpu检测等功能的代码
├── modules module的选择与加载
├── network 网络通信相关
├── playlist 播放列表相关
├── stream_output vlc不仅是一个播放器,还能做推流,这里是推流相关代码
├── text 文本相关,如字符编码等
├── video_output 从decoder拿到视频帧和字幕,处理后输出
vlc也提供了一套MediaPlayer的API,但是没有按照原生Android的MediaPlayer接口进行封装,所以用起来会比较别扭,需要重新封装一下。使用方法可以参考https://code.videolan.org/videolan/libvlc-android-samples.git
import org.videolan.libvlc.IVLCVout;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
mLibVLC = new LibVLC(this, args);//设置启动参数
mMediaPlayer = new MediaPlayer(mLibVLC);
final IVLCVout vlcVout = mMediaPlayer.getVLCVout();
vlcVout.setVideoView(mVideoSurface);//设置视频显示surface
vlcVout.setSubtitlesView(mSubtitlesSurface);//设置字幕显示surface
vlcVout.attachViews(this);//设置IVLCVout.OnNewVideoLayoutListener的回调,注意:想要使用ANativeWindow的话,这里必须注册回调
final Media media = new Media(mLibVLC, getAssets().openFd(ASSET_FILENAME));
mMediaPlayer.setMedia(media);//相当于setdatasource
media.release();
mMediaPlayer.play();
//停止播放
mMediaPlayer.stop();
mMediaPlayer.getVLCVout().detachViews();
mMediaPlayer.release();
mLibVLC.release();
//设置显示窗口的size,宽高比,scale
mMediaPlayer.getVLCVout().setWindowSize(sw, sh);
mMediaPlayer.setAspectRatio("16:9");
mMediaPlayer.setScale(0);
在前面提到的参考文献中有一张很经典的图,如下
vlc中的线程都是通过vlc_clone_attr方法来创建,我们可以给这个方法加上一个线程名参数,方便调试,如下
src/android/thread.c
static int vlc_clone_attr (vlc_thread_t *th, void *(*entry) (void *),
void *data, bool detach, const char* threadName /*线程名*/)
{
vlc_thread_t thread = malloc (sizeof (*thread));
...
pthread_attr_t attr;
pthread_attr_init (&attr);
pthread_attr_setdetachstate (&attr, detach ? PTHREAD_CREATE_DETACHED
: PTHREAD_CREATE_JOINABLE);
ret = pthread_create (&thread->thread, &attr,
detach ? detached_thread : joinable_thread, thread);
pthread_attr_destroy (&attr);
pthread_setname_np(thread->thread, threadName); //设置线程名
pthread_sigmask (SIG_SETMASK, &oldset, NULL);
*th = thread;
return ret;
}
我自己加了一些线程名之后,播放一个流,用adb shell debuggerd -b 看一下都有哪些线程,如下
vlc-input: 即input thread
vlc-fetcher: 即playlist的FetcherThread
vlc-videooutput: 即src/video_output/video_output.c中的
static void *Thread(void *object)
{
vout_thread_t *vout = object;
vout_thread_sys_t *sys = vout->p;
msg_Dbg(vout, "video output tid is %u", syscall(SYS_gettid));
mtime_t deadline = VLC_TS_INVALID;
bool wait = false;
for (;;) {
vout_control_cmd_t cmd;
if (wait)
{
const mtime_t max_deadline = mdate() + 100000;
deadline = deadline <= VLC_TS_INVALID ? max_deadline : __MIN(deadline, max_deadline);
} else {
deadline = VLC_TS_INVALID;
}
while (!vout_control_Pop(&sys->control, &cmd, deadline))
if (ThreadControl(vout, cmd))
return NULL;
deadline = VLC_TS_INVALID;
wait = ThreadDisplayPicture(vout, &deadline) != VLC_SUCCESS;
const bool picture_interlaced = sys->displayed.is_interlaced;
vout_SetInterlacingState(vout, picture_interlaced);
vout_ManageWrapper(vout);
}
}
NDK MediaCodec_:即NDK MediaCodec的线程
CodecLooper: 即stagefright中MediaCodec的线程
vlc-decoder: 即视频对应的DecoderThread,在src/input/decoder.c中
android_audiotrack:即音频对应的DecoderThread,其实也是个vlc-decoder,不过在modules/audio_output/audiotrack.c中调用android_getEnv,即AttachCurrentThread后线程名变了而已
AudioTrack:即framework的audiotrack线程
vlc-audiotrack:即音频输出线程,对应modules/audio_output/audiotrack.c中的AudioTrack_Thread
可以看到,和上面图片所描述的一致。
关注公众号,掌握更多安卓、多媒体领域知识与资讯,开启精彩程序人生
文章帮到你了?可以扫描如下二维码进行打赏,打赏多少您随意~