深入理解VLC之縱觀全局

VLC系列文章
深入理解VLC之代碼流程

VLC,著名的開源播放器項目,它雖然很龐大,但是在架構設計上也高度模塊化。幸運的是,官方wiki非常詳細,無論是大的架構設計,還是每一個模塊裏面的代碼細節,都有詳盡的介紹。wiki鏈接:https://wiki.videolan.org/Hacker_Guide/。

本文主要以vlc-android項目爲例,介紹vlc的架構設計,參考了一篇對vlc架構分析得很好的文章:https://jiya.io/archives/vlc_learn_2.html

1. 代碼結構

下載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拿到視頻幀和字幕,處理後輸出

2. Java API使用

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);

3. 線程模型

在前面提到的參考文獻中有一張很經典的圖,如下

在這裏插入圖片描述

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

可以看到,和上面圖片所描述的一致。


關注公衆號,掌握更多安卓、多媒體領域知識與資訊,開啓精彩程序人生
在這裏插入圖片描述

文章幫到你了?可以掃描如下二維碼進行打賞,打賞多少您隨意~
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章