VLC源碼分析(二)

第一部分 變量及宏定義
1.消息映射宏
vlc_module_begin();
…………………..
vlc_module_end();
2.結構中包含函數
struct input_thread_t
{
VLC_COMMON_MEMBERS
/* Thread properties */
vlc_bool_t b_eof;
vlc_bool_t b_out_pace_control;
/* Access module */
module_t * p_access;
ssize_t (* pf_read ) ( input_thread_t *, byte_t *, size_t );
int (* pf_set_program )( input_thread_t *, pgrm_descriptor_t * );
int (* pf_set_area )( input_thread_t *, input_area_t * );
void (* pf_seek ) ( input_thread_t *, off_t );
}
3.宏與換行符妙用
#define VLC_COMMON_MEMBERS /** \name VLC_COMMON_MEMBERS * these members are common for all vlc objects *//**@{*/int i_object_id; int
i_object_type; char *psz_object_type; char *psz_object_name; /** Just a reminder so that people don't cast garbage */ intwindows

be_sure_to_add_VLC_COMMON_MEMBERS_to_struct;/**@}*/
#define VLC_OBJECT( x ) \
((vlc_object_t *)(x))+
0*(x)- be_sure_to_add_VLC_COMMON_MEMBERS_to_struct
struct vlc_object_t
{
VLC_COMMON_MEMBERS
};//定義一個結構來使用宏定義的公共成員
4.定義導出函數
#ifndef __PLUGIN__
# define VLC_EXPORT( type, name, args ) type name args
#else
# define VLC_EXPORT( type, name, args ) struct _u_n_u_s_e_d_
extern module_symbols_t* p_symbols;
#endif
5.定義回調函數
typedef int ( * vlc_callback_t ) ( vlc_object_t *, /* variable's object */
char const *, /* variable name */
vlc_value_t, /* old value */
vlc_value_t, /* new value */
void * ); /* callback data */
6.函數做爲參數的定義方式
Int Fun(int n,int (*pf)(int ,int),char *pstr)
{ int j =10;
pf(n,j);
}
7.回調函數的聲明
必須聲明爲global,或者static
Int vlc_callback_t (int ,int)
{。。。。。。。。。。。}api

8.回調函數的使用
Fun(0, vlc_callback_t,」test」);
9.函數表達式
#define input_BuffersInit(a) __input_BuffersInit(VLC_OBJECT(a))
void * __input_BuffersInit( vlc_object_t * );
#define module_Need(a,b,c,d) __module_Need(VLC_OBJECT(a),b,c,d)
VLC_EXPORT( module_t *, __module_Need, ( vlc_object_t *, const char *, const char *, vlc_bool_t ) );
10.定義函數
/* Dynamic array handling: realloc array, move data, increment position */
#define INSERT_ELEM( p_ar, i_oldsize, i_pos, elem ) do { if( i_oldsize ) { (p_ar) = realloc( p_ar, ((i_oldsize) + 1) * sizeof( *(p_ar) )網絡

); } else { (p_ar) = malloc( ((i_oldsize) + 1) * sizeof( *(p_ar) ) ); } if( (i_oldsize) - (i_pos) ) { memmove( (p_ar) + (i_pos) + 1, (p_ar) +架構

(i_pos), ((i_oldsize) - (i_pos)) * sizeof( *(p_ar) ) ); } (p_ar)[i_pos] = elem; (i_oldsize)++; } while( 0 )
應用爲:
INSERT_ELEM( p_new- p_libvlc- pp_objects,
p_new- p_libvlc- i_objects,
p_new- p_libvlc- i_objects,
p_new );
11.改變地址的方式傳遞其值
stream_t *input_StreamNew( input_thread_t *p_input )
{ stream_t *s = vlc_object_create( p_input, sizeof( stream_t ) );
input_stream_sys_t *p_sys;
if( s )
{
s- p_sys = malloc( sizeof( input_stream_sys_t ) );
p_sys = (input_stream_sys_t*)s- p_sys;
p_sys- p_input = p_input;
}
return s;//註解:s- p_sys改變了
}
第二部分 程序框架實現
1.播放列表文件src/playlist/playlist.c的線程
playlist_t * __playlist_Create ( vlc_object_t *p_parent )函數中建立的線程,線程函數爲
static void RunThread ( playlist_t *p_playlist )
線程思路分析:
在RunThread裏面執行循環,若是沒有任務執行,則適當的延遲,若是接到p_playlist- i_status != PLAYLIST_STOPPED的條件,則調用PlayItem(app

p_playlist )函數,在PlayItem( p_playlist )函數中重新建立輸入線程。
經過void playlist_Command( playlist_t * p_playlist, playlist_command_t i_command,int i_arg )接收來自GUI界面的各類命令,而後設置p_playlist-框架

i_status的狀態,由該狀態改變該播放列表文件主循環線程的執行。
2.輸入文件SRC/INPUT/INPUT.C的輸入線程
input_thread_t *__input_CreateThread( vlc_object_t *p_parent,
input_item_t *p_item )函數中建立的線程,線程函數爲
static int RunThread( input_thread_t *p_input )
線程思路分析:
由 input_thread_t結構的成員分析是接收文件流仍是網絡流,若是是文件流,則調用file module 的讀函數(pf_read)和打開函數(--).若是是network 則打tcp

開network module 的打開函數和讀函數(pf_read)。
在 RunThread線程函數中接收數據和調用demux 或者decode etc處理。
一旦產生新的輸入,則在播放列表線程中會首先結束該輸入線程,而後重新建立新的輸入線程。
3.視頻輸出文件src/video_output/ video_output.c的線程
vout_thread_t * __vout_Create( vlc_object_t *p_parent,
unsigned int i_width, unsigned int i_height,
vlc_fourcc_t i_chroma, unsigned int i_aspect )函數中建立的線程,線程函數爲
static void RunThread( vout_thread_t *p_vout)
線程思路分析:
在RunThread裏面執行循環,任務是顯示視頻。
4.在modules\gui\wxwindows\wxwindows.cpp中的GUI線程
static void Run( intf_thread_t *p_intf ) 函數中建立的線程,線程函數爲
static void Init( intf_thread_t *p_intf )
線程思路分析:
在Init( intf_thread_t *p_intf )裏面執行循環,建立新的GUI實例。Instance-》OnInit()(CreateDialogsProvider)-》DialogsProvider爲運行的對話ide

框。
接收網絡文件的步驟
OnOpenNet( wxCommandEvent& event )打開網絡文件的步驟。打開OpenDialog對話框,點擊Ok後調用OpenDialog::OnOk( wxCommandEvent& WXUNUSED(event)模塊化

)函數,調用playlist_Command函數改變播放列表線程的狀態。
激活線程分析:
在wxwindow.cpp中的消息映射中 set_callbacks( OpenDialogs, Close ); 則設置了module_t- pf_activate= OpenDialogs函數,
在module.c 的__module_Need( vlc_object_t *p_this, const char *psz_capability,
const char *psz_name, vlc_bool_t b_strict )
函數中用到了pf_activate激活GUI對話框;
在video_output.c 的static void RunThread( vout_thread_t *p_vout)線程中,也用到了pf_activate激活GUI對話框;
5.開始全部module 的精髓
消息映射宏
vlc_module_begin();
set_callbacks( NetOpen, NULL );
vlc_module_end();
而後設置模塊結構的成員函數爲:
#define set_callbacks( activate, deactivate ) p_submodule- pf_activate = activate; p_submodule- pf_deactivate = deactivate
在__module_Need函數中啓動pf_activate 激活相應的module。函數


網絡數據流接收處理分析
一、在input.c(src\input)文件中的主線程循環
Thread in charge of processing the network packets and demultiplexing
RunThread( input_thread_t *p_input )
{
InitThread( p_input ) ;
…………………………………………………….
input_SelectES( p_input, p_input->stream.p_newly_selected_es );
…………………………………………………….
/* Read and demultiplex some data. */
i_count = p_input->pf_demux( p_input );
}
二、在下列函數中:
分離出access , demux , name字符串 ;
根據分離出的access 字符串經過module_Need函數找到acess 指針模塊;
根據分離出的demux 字符串經過module_Need函數找到demux 指針模塊;
static int InitThread( input_thread_t * p_input )
{
msg_Dbg( p_input, "access `%s', demux `%s', name `%s'",
p_input->psz_access, p_input->psz_demux, p_input->psz_name );
/* Find and open appropriate access module */
p_input->p_access = module_Need( p_input, "access",
p_input->psz_access, VLC_TRUE );
…………………………………………………….
while( !input_FillBuffer( p_input ) )
…………………………………………………….
/* Find and open appropriate demux module */
p_input->p_demux =
module_Need( p_input, "demux",
(p_input->psz_demux && *p_input->psz_demux) ?
p_input->psz_demux : "$demux",
(p_input->psz_demux && *p_input->psz_demux) ?
VLC_TRUE : VLC_FALSE );
…………………………………………………….
}
三、在ps.c (module\demux\mpeg)文件中
a.經過消息映射宏賦值啓動函數Activate;
b.經過函數Activate賦值p_input->pf_demux = Demux;
c. 經過函數module_Need( p_input, "mpeg-system", NULL, 0 ) 激活p_input->p_demux_data->mpeg.pf_read_ps( p_input, &p_data )函數(pf_read_ps)

;
d.在InitThread函數中激活;
static int Activate( vlc_object_t * p_this )
{
/* Set the demux function */
p_input->pf_demux = Demux;
p_input->p_private = (void*)&p_demux->mpeg;
p_demux->p_module = module_Need( p_input, "mpeg-system", NULL, 0 );
}
四、在system.c (module\demux\mpeg)文件中
賦值解碼模塊mpeg_demux_t的成員函數;
static int Activate ( vlc_object_t *p_this )
{
static mpeg_demux_t mpeg_demux =
{ NULL, ReadPS, ParsePS, DemuxPS, ReadTS, DemuxTS };
mpeg_demux.cur_scr_time = -1;
memcpy( p_this->p_private, &mpeg_demux, sizeof( mpeg_demux ) );
return VLC_SUCCESS;
}
而且申明函數static ssize_t ReadPS( input_thread_t * p_input, data_packet_t ** pp_data );
五、在ps.c (module\demux\mpeg)文件中
Demux( input_thread_t * p_input )
{
i_result = p_input->p_demux_data->mpeg.pf_read_ps( p_input, &p_data );
p_input->p_demux_data->mpeg.pf_demux_ps( p_input, p_data );
}
進行讀取數據和分離工做;
六、在system.c (module\demux\mpeg)文件中
數據走向圖以下
ReadPS-> PEEK-> input_Peek(src\input\input_ext-plugins.c)-> input_FillBuffert 經過 i_ret = p_input->pf_read( p_input,
(byte_t *)p_buf + sizeof(data_buffer_t)
+ i_remains,
p_input->i_bufsize );
input_thread_t結構的pf_read函數成員若是是爲udp.c(modules\access)的RTPChoose函數
則在開啓access(UDP 模塊)時經過module_need 激活;
激活網絡讀數據模塊 RTPChoose(modules\access\ udp.c)->Read->net_Read(src\misc\net.c);
七、在input_programs.c(src\input)文件中
運行解碼器對ES流解碼
int input_SelectES( input_thread_t * p_input, es_descriptor_t * p_es )
{
p_es->p_dec = input_RunDecoder( p_input, p_es );

}
input_SelectES(src\input\input_programs.c)->input_RunDecoder(src \input\input_dec.c)->DecoderThread->DecoderDecode -

>vout_DisplayPicture

 


從接收到數據流到播放視頻的過程分析

   從網絡接收到流->對數據流進行視頻和音頻分離->對視頻用解碼器解碼->顯示解碼後的視頻流

 

    視頻顯示部分走勢線:分離->解碼->新的VOUT緩衝區->VOUT線程

Demux(modules\demux\mpeg\ps.c)->DemuxPs(modules\demux\mpeg\system.c)-> ParsePS->input_SelectES(src\input\input_programs.c)->input_RunDecoder

(src\input\input_dec.c)->CreateDecoder->

vout_new_buffer->vout_Request(src\video_output\video_output.c)->vout_Create->RunThread->vout_RenderPicture(src\video_output\vout_pictures.c)-

>pf_display

 

注意:p_dec->pf_vout_buffer_new = vout_new_buffer的pf_vout_buffer_new在ffmpeg_NewPictBuf(modules\codec\ffmpeg\video.c)函數中激活

 

   解碼部分走勢線:

Demux(modules\demux\mpeg\ps.c)->DemuxPs(modules\demux\mpeg\system.c)-> ParsePS->input_SelectES(src\input\input_programs.c)->input_RunDecoder

(src\input\input_dec.c)->CreateDecoder->


DecoderThread

  注意:在解碼線程中對數據流(AUDIO 或者VIDEO)進行解碼

詳細資料http://developers.videolan.org/vlc/    VLC API documentation  或者VLC developer documentation


Chapter 5.  The video output layer
Data structures and main loop

Important data structures are defined in include/video.h and include/video_output.h. The main data structure is picture_t, which describes

everything a video decoder thread needs. Please refer to this file for more information. Typically, p_data will be a pointer to YUV planar

picture.

Note also the subpicture_t structure. In fact the VLC SPU decoder only parses the SPU header, and converts the SPU graphical data to an

internal format which can be rendered much faster. So a part of the "real" SPU decoder lies in src/video_output/video_spu.c.

The vout_thread_t structure is much more complex, but you needn't understand everything. Basically the video output thread manages a heap of

pictures and subpictures (5 by default). Every picture has a status (displayed, destroyed, empty...) and eventually a presentation time. The

main job of the video output is an infinite loop to : [this is subject to change in the near future]

   1.

      Find the next picture to display in the heap.
2.

      Find the current subpicture to display.
3.

      Render the picture (if the video output plug-in doesn't support YUV overlay). Rendering will call an optimized YUV plug-in, which will

also do the scaling, add subtitles and an optional picture information field.
4.

      Sleep until the specified date.
5.

      Display the picture (plug-in function). For outputs which display RGB data, it is often accomplished with a buffer switching. p_vout-

>p_buffer is an array of two buffers where the YUV transform takes place, and p_vout->i_buffer_index indicates the currently displayed buffer.
6.

      Manage events.

Methods used by video decoders

The video output exports a bunch of functions so that decoders can send their decoded data. The most important function is vout_CreatePicture

which allocates the picture buffer to the size indicated by the video decoder. It then just needs to feed (void *) p_picture->p_data with the

decoded data, and call vout_DisplayPicture and vout_DatePicture upon necessary.

    *

      picture_t * vout_CreatePicture ( vout_thread_t *p_vout, int i_type, int i_width, int i_height ) : Returns an allocated picture buffer.

i_type will be for instance YUV_420_PICTURE, and i_width and i_height are in pixels.
Warning

      If no picture is available in the heap, vout_CreatePicture will return NULL.
*

      vout_LinkPicture ( vout_thread_t *p_vout, picture_t *p_pic ) : Increases the refcount of the picture, so that it doesn't get accidently

freed while the decoder still needs it. For instance, an I or P picture can still be needed after displaying to decode interleaved B pictures.
*

      vout_UnlinkPicture ( vout_thread_t *p_vout, picture_t *p_pic ) : Decreases the refcount of the picture. An unlink must be done for every

link previously made.
*

      vout_DatePicture ( vout_thread_t *p_vout, picture_t *p_pic ) : Gives the picture a presentation date. You can start working on a picture

before knowing precisely at what time it will be displayed. For instance to date an I or P picture, you must wait until you have decoded all

previous B pictures (which are indeed placed after - decoding order != presentation order).
*

      vout_DisplayPicture ( vout_thread_t *p_vout, picture_t *p_pic ) : Tells the video output that a picture has been completely decoded and

is ready to be rendered. It can be called before or after vout_DatePicture.
*

      vout_DestroyPicture ( vout_thread_t *p_vout, picture_t *p_pic ) : Marks the picture as empty (useful in case of a stream parsing error).
*

      subpicture_t * vout_CreateSubPicture ( vout_thread_t *p_vout, int i_channel, int i_type ) : Returns an allocated subpicture buffer.

i_channel is the ID of the subpicture channel, i_type is DVD_SUBPICTURE or TEXT_SUBPICTURE, i_size is the length in bytes of the packet.
*

      vout_DisplaySubPicture ( vout_thread_t *p_vout, subpicture_t *p_subpic ) : Tells the video output that a subpicture has been completely

decoded. It obsoletes the previous subpicture.
*

      vout_DestroySubPicture ( vout_thread_t *p_vout, subpicture_t *p_subpic ) : Marks the subpicture as empty.

 


VLC(五) 視頻播放的基本原理

    當初Roger看VLC代碼花了很多時間,其中很大的緣由是不太瞭解視頻播放的基本原理。如今看來,幾乎全部的視頻播放器,如VLC、MPlayer、 Xine,包括

DirectShow,在播放視頻的原理和架構上都是很是類似的,理解這個對理解VLC的源碼會有事半功倍的效果。

    大體的來講,播放一個視頻分爲4個步驟:

    1.  acess 訪問,或者理解爲接收、獲取、獲得

    2. demux 解複用,就是把一般合在一塊兒的音頻和視頻分離(還有可能的字幕) 

    3. decode 解碼,包括音頻和視頻的解碼

    4. output 輸出,也分爲音頻和視頻的輸出(aout和vout)

    拿播放一個UDP組播的MPEG TS流來講吧,access部分負責從網絡接收組播流,放到VLC的內存緩衝區中,access模塊關注IP協議,如是否IPv六、組播地址、組

協議、端口等信息;若是檢測出來是RTP協議(RTP協議在UDP頭部簡單得加上了固定12個字節的信息),還要分析RTP頭部信息。這部分能夠參看VLC源碼

/modules/access/udp.c 。在同目錄下還能夠看到大量的access模塊,如file、http、dvd、ftp、smb、tcp、dshow、mms、v4l…等等

    而demux部分首先要解析TS流的信息。TS格式是MPEG2協議的一部分,歸納地說,TS一般是固定188字節的一個packet,一個TS流能夠包含多個program(節目)

,一個program又能夠包含多個視頻、音頻、和文字信息的ES流;每一個ES流會有不一樣的PID標示。而又爲了能夠分析這些ES流,TS有一些固定的PID用來間隔發送

program和es流信息的表格:PAT和PMT表。關於TS格式的詳細信息能夠去google一下。

    VLC專門作了一個獨立的庫libdvbpsi來解析和編碼TS流,而調用它的代碼能夠參見VLC源碼 /modules/demux/ts.c。

    其實之因此須要demux,是由於音視頻在製做的時候實際上都是獨立編碼的,獲得的是分開的數據,爲了傳輸方便必需要用某種方式合起來,這就有了各類封

裝格式也就有了demux。

    demux分解出來的音頻和視頻流分別送往音頻解碼器和視頻解碼器。由於原始的音視頻都是佔用大量空間,並且冗餘度較高的數據,一般在製做的時候就會進

行某種壓縮。這就是咱們熟知的音視頻編碼格式,包括MPEG1(VCD)、MPEG2(DVD)、MPEG四、H.26四、rmvb等等。音視頻解碼器的做用就是把這些壓縮了的數據還

原成原始的音視頻數據。VLC解碼MPEG2使用了一個獨立的庫libmpeg2,調用它的源文件是 /modules/codec/libmpeg2.c。VLC關於編解碼的模塊都放

在/modules/codec目錄下,其中包括著名的龐大的 ffmpeg。

    解碼器,例如視頻解碼器輸出的是一張一張的相似位圖格式的圖像,可是要讓人從屏幕看獲得,還須要一個視頻輸出的模塊。固然能夠像一個Win32窗口程序

那樣直接把圖像畫到窗口DC上——VLC的一個輸出模塊WinGDI就是這麼幹的,可是一般這太慢了,並且消耗大量的CPU。在Windows下比較好的辦法是用DirectX的接

口,會自動調用顯卡的加速功能。

    這樣的功能分解使得模塊化更容易一點,每一個模塊住須要專一於本身的事;從總體來講功能強大並且靈活。

    可是事情老是不會那麼簡單。就拿access來講,媒體的訪問是分層的,如RTSP就涉及到IPv四、TCP、UDP、RTCP、RTSP等多個層次的協議。有些視頻格式包括了

傳輸、封裝格式和編輯碼格式如MPEG系列,有些封裝格式是獨立的容器,可是不少人會誤解它是編解碼格式,如mkv、avi這些。

    音頻和視頻在demux以後就是獨立的,可是須要有一套機制把它們同步起來。同時咱們須要有一套機制來控制速度、暫停、中止、跳進,獲取各類媒體信息,

這些都是很複雜而又很重要的事情。

    另外也許須要在某個地方插入一些修改,來實現某種效果。如音頻的EQ,視頻的亮度調整之類的,VLC專門設計了access_filter、audio_filter和

video_filter類型的模塊來作這一類事情。

    VLC比較獨特的地方是集成了原來的VLS的功能,這依賴於VLC中stream_output類型的模塊,它們能夠把正在播放的視頻以某種方式從新轉碼和發送出去,如

http、UDP、文件等等。

    MPlayer的結構與此是相似的,如/stream目錄對應的是access的功能,/mpdemux對應的demux功能,/libmpcodecs是解碼器,/libvo和/libao2分別是視頻和音

頻的輸出。

    DirectShow也是相似的,不過度類更多一些更復雜一點。DirectShow裏面的模塊叫作「filter」,filter之間經過」pin」來鏈接。access的模塊對應於

DirectShow中的Source FIlter,這一類Filter只有輸出pin沒有輸入pin。demux模塊對應於splitter filter,這種filter有一個輸入pin,多個輸出pin。解碼模

塊是一類transform filter,有一個輸入pin、一個輸出pin,輸出模塊對應於readering filter,有一個輸入pin,沒有輸出pin。固然transform filter不必定是

解碼器,也多是某種其餘的處理。

    回到VLC的話題。每一類的模塊數量都不少,那麼在打開某一個視頻時,VLC如何決定採用哪個呢?哈哈,這個之後再說 ^_^

    另外給出一個VLC的API Document,有點老了不過挺值得一看的,在VLC wiki上找不到了,就貼出來:http://rogerfd.cn/doc/vlcapi.htm

相關文章
相關標籤/搜索