vlc是通過模塊來擴展它的功能,插件一般就是實現一個模塊。
vlc的模塊有很多類型:
Access提供輸入功能,比如HTTP輸入、文件輸入
Demux提供解封裝功能,比如Asf、Mp4、Ts的解封裝
Access_Demux當然是Access、Demux兩者的組合功能
其他的與我們不太相關,就不廢話了,那個Interface是界面插件。
對應到我們的庫提供的功能,應該既有輸入,又有解封裝,vlc正好提供了Access_Demux這種類型,說明vlc已經遇到或者考慮過這種情形了,查看vlc的源碼,發現在access目錄有不少模塊是access_demux類型的,其中我聽說過的格式有rtp、dshow。
下面開始實現我們的vlc插件,沒必要重頭開始寫,拷貝一個現有的插件代碼修改修改就行了,比如我拷貝demux/asf/asf.c,其他的也行,我只是選了一個我比較熟悉的格式。
第一步:註冊我們的模塊:
staticintOpen(vlc_object_t*); staticvoidClose(vlc_object_t*); vlc_module_begin() set_category(CAT_INPUT) set_subcategory(SUBCAT_INPUT_DEMUX) set_description("Mylibdemuxer") set_capability("access_demux",200) set_callbacks(Open,Close) add_shortcut("vod") vlc_module_end()
vlc註冊模塊用的是一套標準模板,從asf.c拷貝過來,改變一下字符串描述,類型改成"access_demux",其他沒什麼要修改的。需要說明的是set_capability的200參數表示該插件的優先級,越大越可能被使用,如果是0的話就不會自動使用,只能手動強制使用。注意set_callbacks(Open,Close),這個很關鍵,其實是告訴vlc開始播放的時候調用Open,結束播放的時候調用Close。
第二步:定義自己的結構體
typedefstruct { inti_cat; es_out_id_t*p_es; Mylib_StreamInfoEx*p_si; mtime_ti_time; }mylib_track_t; structdemux_sys_t { mtime_ti_time;/*s*/ mtime_ti_length;/*lengthoffilefile*/ int64_ti_bitrate;/*globalfilebitrate*/ boolb_seek; unsignedinti_track; mylib_track_t*track[128];/*tracknumberisstoredon7bits*/ };
定義自己的結構體是用來保存一些自己的私有數據。我這裏定義了兩個結構體mylib_track_t和demux_sys_t。
mylib_track_t對應一個track,在demux之後,音視頻已經分離,音頻、視頻都是一個track。這裏的es_out_id_t可以看做是音視頻數據處理的下一個模塊,所以每個track都會有一個es_out_id_t*,我們自己的音視頻流信息保存在Mylib_StreamInfoEx,這個是我們鏈接庫中定義的結構體(都是Mylib開頭的);另外還有一個時間戳mtime_t,保存已經demux的最後一幀時間。
vlc裏面的時間戳好像都是微妙單位,用64爲整形表示。
demux_sys_t保存全局信息,比如影片總時長i_length,當前位置i_time,能否拖動b_seek,另外還有一個track數組。i_bitrate沒有用到。這裏全局當前位置其實是所有track的當前位置的最小值,當前位置在顯示進度條位置時候用到。
第三步:實現Open、Close
staticintOpen(vlc_object_t*p_this) { demux_t*p_demux=(demux_t*)p_this; demux_sys_t*p_sys; my_int32ret; charplaylink[1024]={0}; strcat(playlink,p_demux->psz_access); strcat(playlink,"://"); #ifPACKAGE_VERSION_MAJOR==1 strcat(playlink,p_demux->psz_path); #elifPACKAGE_VERSION_MAJOR==2 strcat(playlink,p_demux->psz_location); #endif msg_Dbg(p_demux,"MylibOpen%s",playlink); ret=Mylib_Open(playlink); if(ret!=mylib_success){Mylib_Close()();returnVLC_EGENERIC;} /*Setp_demuxfields*/ p_demux->pf_demux=Demux; p_demux->pf_control=Control; p_demux->p_sys=p_sys=(demux_sys_t*)calloc(1,sizeof(demux_sys_t)); /*Loadtheheaders*/ if(DemuxInit(p_demux)) { free(p_sys); Mylib_Close()(); returnVLC_EGENERIC; } returnVLC_SUCCESS; } staticvoidClose(vlc_object_t*p_this) { demux_t*p_demux=(demux_t*)p_this; DemuxEnd(p_demux); Mylib_Close(); free(p_demux->p_sys); }
Open裏面主要的工作就是設置另外幾個回調函數以及綁定上面定義的私有數據結構。vlc中對應一次播放,所需要的信息都是存放在一個大結構體中,其中access_demux模塊需要的信息在demux_t中,設置回調函數、綁定私有數據都是直接修改demux_t的成員:pf_demux、pf_control和p_sys。pf_demux在驅動處理下一個sample時調用,pf_control在做一些播放控制時調用,如拖動、暫停,也用於獲取信息:如能否拖動、當前位置、總時長。
demux類型模塊的Open還需要向vlc核心註冊音視頻流(track),這個我們放在我們的另一個函數DemuxInit裏面。
Close比較簡單,主要工作是釋放自己的私有數據。
第四步:DemuxInit,註冊音視頻流(track)
staticintDemuxInit(demux_t*p_demux) { demux_sys_t*p_sys=p_demux->p_sys; /*initcontext*/ p_sys->i_time=-1; p_sys->i_length=0; p_sys->i_bitrate=0; for(inti=0;i<128;i++) { p_sys->track[i]=NULL; } p_sys->i_length=(mtime_t)Mylib_GetDuration()()*1000; p_sys->b_seek=p_sys->i_length>0; p_sys->i_track=Mylib_GetStreamCount(); if(p_sys->i_track<=0) { msg_Warn(p_demux,"Mylibplugindiscarded(cannotfindanystream!)"); returnVLC_EGENERIC; } msg_Dbg(p_demux,"found%dstreams",p_sys->i_track); for(unsignedi_stream=0;i_stream<p_sys->i_track;i_stream++) { mylib_track_t*tk; tk=p_sys->track[i_stream]=(mylib_track_t*)malloc(sizeof(mylib_track_t)); memset(tk,0,sizeof(mylib_track_t)); tk->i_time=-1; tk->p_es=NULL; Mylib_StreamInfoEx*p_si=(Mylib_StreamInfoEx*)malloc(sizeof(Mylib_StreamInfoEx)); Mylib_GetStreamInfoEx(i_stream,p_si); tk->p_si=p_si; es_format_tfmt; if(p_si->type==mylib_audio) { es_format_Init(&fmt,AUDIO_ES,0); fmt.i_codec=VLC_FOURCC('m','p','4','a'); fmt.audio.i_channels=p_si->audio_format.channel_count; fmt.audio.i_rate=p_si->audio_format.sample_rate; fmt.audio.i_bitspersample=p_si->audio_format.sample_size; fmt.i_extra=p_si->format_size; fmt.p_extra=malloc(fmt.i_extra); memcpy(fmt.p_extra,p_si->format_buffer,fmt.i_extra); msg_Dbg(p_demux,"addednewaudiostream(codec:0x%x,ID:%d)",fmt.i_codec,i_stream); } elseif(p_si->type==mylib_video) { es_format_Init(&fmt,VIDEO_ES,0); fmt.i_codec=VLC_FOURCC('a','v','c','1'); fmt.video.i_width=p_si->video_format.width; fmt.video.i_height=p_si->video_format.height; fmt.video.i_frame_rate=10000000; fmt.video.i_frame_rate_base=1; fmt.i_extra=p_si->format_size; fmt.p_extra=malloc(fmt.i_extra); memcpy(fmt.p_extra,p_si->format_buffer,fmt.i_extra); msg_Dbg(p_demux,"addednewvideostream(ID:%d)",i_stream); } else { es_format_Init(&fmt,UNKNOWN_ES,0); } tk->i_cat=fmt.i_cat; if(fmt.i_cat!=UNKNOWN_ES) { tk->p_es=es_out_Add(p_demux->out,&fmt); } else { msg_Dbg(p_demux,"ignoringunknownstream(ID:%d)",i_stream); } es_format_Clean(&fmt); } returnVLC_SUCCESS; }
註冊音視頻流時,先填充es_format_t結構,然後調用vlc的函數es_out_Add完成註冊。
es_format_t結構中,i_codec就是那個四個字節的標準定義值,如AVC1、H264,MP4V等等,i_extra、p_extra是音視頻的配置數據,像AVC1就是AvcConfigRecord結構。
還有幾個字段是音頻和視頻不同的,音頻有採樣率、位寬、聲道數,視頻有寬、高、幀率,其實還有很多字段,不填也無所謂,vlc會自動處理的。
第五步:實現回調函數Demux
staticintDemux(demux_t*p_demux) { demux_sys_t*p_sys=p_demux->p_sys; for(;;) { my_int32ret; if(!vlc_object_alive(p_demux)) break; Mylib_SampleEx2sample; ret=Mylib_ReadSampleEx2(&sample); /*Readanddemuxapacket*/ if(ret==mylib_success){ //msg_Dbg(p_demux,"Mylib_ReadSampleEx2stream_index:%lu,start_time:%llu", //sample.stream_index,sample.start_time); if(p_sys->i_time<0) es_out_Control(p_demux->out,ES_OUT_SET_PCR,sample.start_time+1); mylib_track_t*tk=p_sys->track[sample.stream_index]; block_t*p_block=block_Alloc(sample.buffer_length); p_block->i_pts=sample.start_time+sample.composite_time_delta; p_block->i_dts=sample.start_time; memcpy(p_block->p_buffer,sample.buffer,sample.buffer_length); tk->i_time=sample.start_time; p_sys->i_time=GetMoviePTS(p_sys); es_out_Send(p_demux->out,tk->p_es,p_block); es_out_Control(p_demux->out,ES_OUT_SET_PCR,sample.start_time+1); return1; }elseif(ret==mylib_would_block){ msg_Dbg(p_demux,"Mylib_ReadSampleEx2mylib_would_block"); usleep(10000); }elseif(ret==mylib_stream_end){ msg_Dbg(p_demux,"Mylib_ReadSampleEx2mylib_stream_end"); return0; }else{ msg_Dbg(p_demux,"Mylib_ReadSampleEx2%s",Mylib_GetLastErrorMsg()()); return-1; } } return1; }
這裏使用了循環,但不是爲了一直解出所有的幀,而是爲了處理網絡延時。按vlc的定義這個回調函數只需要解出一個幀就可以了,當然多個也可以,但是這個函數最好不要長時間阻塞調用者。但是vlc又規定必須返回成功或者失敗,不存在暫時無數據的錯誤碼,對於網絡卡的時候不好處理,暫時只能阻塞了。
Demux解出的幀不需要返回給調用者,但需要push給後續處理模塊。還記得前面在私有數據裏面保存的es_out_id_t吧,對了,用es_out_Send函數push給它就可以了。注意每個track有各自的es_out_id_t。
除此之外,Demux還有一個比較重要的事情要做,就是更新vlc的pts(其實就是當前時間),pts通過取各個track的當前時間的最小值獲得,實現如下:
staticmtime_tGetMoviePTS(demux_sys_t*p_sys) { mtime_ti_time=-1; inti; for(i=0;i<128;i++) { mylib_track_t*tk=p_sys->track[i]; if(tk&&tk->p_es&&tk->i_time>0) { if(i_time<0)i_time=tk->i_time; elsei_time=__MIN(i_time,tk->i_time); } } returni_time; }
pts計算出來後,通過es_out_Control更新pts。
第六步:實現回調函數Control
staticintControl(demux_t*p_demux,inti_query,va_listargs) { demux_sys_t*p_sys=p_demux->p_sys; bool*pb_bool; int64_ti64,*pi64; doublef,*pf; switch(i_query) { caseDEMUX_CAN_SEEK: pb_bool=(bool*)va_arg(args,bool*); *pb_bool=p_sys->b_seek; returnVLC_SUCCESS; caseDEMUX_CAN_CONTROL_PACE: pb_bool=(bool*)va_arg(args,bool*); *pb_bool=false; returnVLC_SUCCESS; caseDEMUX_GET_LENGTH: pi64=(int64_t*)va_arg(args,int64_t*); *pi64=p_sys->i_length; returnVLC_SUCCESS; caseDEMUX_GET_TIME: if(p_sys->i_time<0)returnVLC_EGENERIC; pi64=(int64_t*)va_arg(args,int64_t*); *pi64=p_sys->i_time; returnVLC_SUCCESS; caseDEMUX_SET_TIME: SeekPrepare(p_demux); { va_listacpy; va_copy(acpy,args); i64=(int64_t)va_arg(acpy,int64_t); va_end(acpy); if(!SeekIndex(p_demux,i64,-1)) returnVLC_SUCCESS; } returnVLC_EGENERIC; caseDEMUX_GET_POSITION: if(p_sys->i_time<0)returnVLC_EGENERIC; if(p_sys->i_length>0) { pf=(double*)va_arg(args,double*); *pf=p_sys->i_time/(double)p_sys->i_length; returnVLC_SUCCESS; } returnVLC_EGENERIC; caseDEMUX_SET_POSITION: SeekPrepare(p_demux); if(p_sys->i_length>0) { va_listacpy; va_copy(acpy,args); f=(double)va_arg(acpy,double); va_end(acpy); if(!SeekIndex(p_demux,-1,f)) returnVLC_SUCCESS; } returnVLC_EGENERIC; default: returnVLC_EGENERIC; } }
Control函數內部就是一個大的switch語句,根據控制命令類型i_query,解釋控制參數args。
access_demux模塊需要處理的控制命令,主要有以下幾個:
DEMUX_CAN_SEEK詢問能否seek
DEMUX_CAN_CONTROL_PACE詢問能否速率控制
DEMUX_GET_LENGTH獲取總時長
DEMUX_GET_TIME獲取當前時間位置
DEMUX_GET_POSITION獲取當前位置(百分比)
DEMUX_SET_TIME設置當前時間位置,相當於拖動到某個時間點
DEMUX_SET_POSITION設置當前位置(百分比),相當於拖動到某個百分比
這裏position可以通過總時長和當前時間算出來的,但是POSITION相關的兩個命令還必須實現,否則進度條不會動,拖動進度條也沒有效果。
拖動時用了兩個自己的函數,我只列出來,不詳細說明了。
staticintSeekIndex(demux_t*p_demux,mtime_ti_date,floatf_pos) { demux_sys_t*p_sys=p_demux->p_sys; my_int32ret; msg_Dbg(p_demux,"seekwithindex:%iseconds,position%f", i_date>=0?(int)(i_date/1000000):-1,f_pos); if(i_date<0) i_date=p_sys->i_length*f_pos; ret=mylib_Seek()(i_date/1000); return(ret==mylib_success||ret==mylib_would_block)?VLC_SUCCESS:VLC_EGENERIC; } staticvoidSeekPrepare(demux_t*p_demux) { demux_sys_t*p_sys=p_demux->p_sys; p_sys->i_time=-1; for(inti=0;i<128;i++) { mylib_track_t*tk=p_sys->track[i]; if(!tk) continue; tk->i_time=1; } }
第七步:DemuxEnd,反註冊track
staticvoidDemuxEnd(demux_t*p_demux) { demux_sys_t*p_sys=p_demux->p_sys; inti; for(i=0;i<128;i++) { mylib_track_t*tk=p_sys->track[i]; if(tk) { if(tk->p_es) { es_out_Del(p_demux->out,tk->p_es); } free(tk); } p_sys->track[i]=NULL; } }
在DemuxInit中,通過es_out_Add註冊了track,在DemuxEnd中,通過es_out_Del反註冊,DemuxEnd還刪除了私有的track修改數據。
第八步:工程定義
vlc使用autotools管理makefile,我對autotools不是很熟悉,這部分看似簡單的工作反而花了比較長的時間。一開始自己搞了一個簡單的makefile編譯,鏈接都沒問題,可是一運行就crash,後來只好把自己的代碼工程加到vlc的configure體系。爲了方便像我一樣不熟悉autotools體系的程序員,我這裏加了一節說一下加入自己的插件工程需要的步驟:
1、在VLC_ROOT/module/access/下創建自己的目錄
mkdirmodule/access/mylib
(按照vlc的開發說明,簡單的插件(只有一個c文件)不創建目錄也可以,但是方式與我下面的不一樣,我覺得儘量獨立比較好,還是創建自己的一個目錄)
2、把實現文件放到該目錄
3、在該目錄下增加一個Module.am文件,方法還是照抄別人的代碼,內容如下:
SOURCES_mylib=\ mylib.c\ $(NULL) libmylib_plugin_la_CFLAGS=$(AM_CFLAGS)\ -fno-strict-aliasing libmylib_plugin_la_LIBADD=$(AM_LIBADD) libmylib_plugin_la_DEPENDENCIES= libvlc_LTLIBRARIES+=libmylib_plugin.la
4、修改VLC_ROOT/module/access/Module.am,在BASE_SUBDIRS最後加上自己的目錄
BASE_SUBDIRS=dvbmmscddartprtspvcdvcdxscreenbdzipmylib
5、修改VLC_ROOT/configure.ac,在AC_CONFIG_FILES裏面增加自己的一行
AC_CONFIG_FILES([ modules/access/mylib/Makefile extras/package/win32/vlc.win32.nsi ......
就這樣,然後運行下列命令編譯、安裝
./bootstrap ./configure make makeinstall