嵌入式 vlc源码分析锦集

http://blog.sina.com.cn/s/blog_8795b0970101eeku.html

嵌入式 vlc源码分析锦集

VLC源码分析

目录

1 VLC源码结构

vlc核心的是libvlc,它提供界面,应用处理功能,所有的libvlc的源代码都放在src目录及其子目录

1.1 ./config/

从命令行和配置文件中加载配置

1.2 ./control/

提供动作控制功能,如播放等操作

1.3  ./extras/

大多是平台的特殊代码

1.4  ./modules/

模块管理

1.5  ./network/

提供网络接口(socket管理,网络接口)

1.6  ./osd/

显示屏幕上的操作

1.7  ./test/

libvlc测试模块

1.8  ./text/

字符集

1.9  ./interface/

提供代码中可以调用的接口,如按键后的硬件作出反应

1.10  ./playlist/

管理播放功能

1.11  ./input/

建立并读取一个输入流,并且分离其中的音频和视频,然后把分离好的音频和视频流发给解码器

1.12  ./audio_output/

初始化音频混合器,即设置正确的同步频率,并对从解码器传来的音频流重新取样

 

1.13  ./video_output/

初始化视频播放器,把从解码器得到视频画面转化格式从yuv到rgb,然后播放

1.14  ./stream_output/ 

输出音频流和视频流到网络

1.15  ./misc/

libvlc使用的其他部分功能,如线程系统,消息队列等.

2 configure详解

 

概述

VLC 属于Video LAN开源项目组织中的一款全开源的流媒体服务器和多媒体播放器。作为流媒体服务器,VLC跨平台,支持多操作系统和计算机体系结构;作 为多媒体播放器,VLC可以播放多种格式的媒体文件。主要包括有:WMV、ASF、MPG、MP、AVI、H.264等多种常见媒体格式。

VLC 采用全模块化结构,在系统内部,通过动态的载入所需的模块,放入一个module_bank的结构体中统一管理,连VLC的Main模块也是通过插件的方 式动态载入的(通过module_InitBank函数在初始化建立module_bank时)。对于不支持动态载入插件的系统环境中,VLC也可以采用 builtin的方式,在VLC启动的时候静态载入所需要的插件,并放入module_bank统一管理。

VLC 的模块分成很多类别主要有:access、access_filter、access_output、audio_filter、 audio_mixer、audio_output、codec、control、demux、gui、misc、mux、packetizer、 stream_output、video_filter、video_output、interface、input、playlist等(其中黑体为核 心模块)。VLC无论是作为流媒体服务器还是多媒体播放器,它的实质思路就是一个“播放器”,之所以这么形象描述,是因为(The core gives a framework to do the media processing, from input (files, network streams) to output (audio or video, on ascreen or a network), going through various muxers, demuxers, decoders and filters. Even the interfaces are plugins for LibVLC. It is up to the developer to choose which module will be loaded. 摘 于官网说明) 它实质处理的是ES、PES、PS、TS等流间的转换、传输与显示。对于流媒体服务器,如果从文件作为输入 即:PS->DEMUX->ES->MUX->TS;对于多媒体播放器如果采用UDP方式传输 即:TS->DEMUX->ES。

1. 插件管理框架

在VLC中每种类型的模块中都有一个抽象层/结构体,在抽象层或结构体中定义了若干操作的函数指针,通过这些函数指针就能实现模块的动态载入,赋值相关的函数指针的函数地址,最后通过调用函数指针能调用实际模块的操作。

对 于VLC所有的模块中,有且仅有一个导出函数:vlc_entry__(MODULE_NAME)。(其中MODULE_NAME为宏定义,对于main 模块,在\include\modules_inner.h中定义为main)动态载入模块的过程是:使用module_Need函数,在 module_bank中根据各个插件的capability等相关属性,寻找第一个能满足要求并激活的模块。所谓激活是指,调用插件的初始化函数成功。 对于各个插件的初始化函数和析构函数均在vlc_entry__(MODULE_NAME)函数中指定了相关函数地址。因此载入各个插件(动态库)的过 程,就成为了解析动态库文件,并找到其中vlc_entry__函数的地址,然后运行。这样各个模块的激活函数就会赋值各个操作的函数地址,以待后面函数 动态调用。

具体函数调用过程如下:

l Main模块的载入过程:

int main( int i_argc, char *ppsz_argv[] )(src\vlc.c)->i_ret = VLC_Init( 0, i_argc, ppsz_argv )-> module_InitBank( p_vlc ) (src\libvlc.c void __module_InitBank( vlc_object_t *p_this ))-> module_LoadMain( p_this ) (src\misc \modules.c)->AllocateBuiltinModule( p_this, vlc_entry__main )->pf_entry( p_module ) (激活了main模块,以上为main模块的载入过程,对于main模块调用的实际函数为导出函数vlc_entry__main,其它模块导出的均为 vlc_entry__0_8_6)

l Module_Need函数实现载入任意模块的过程:

module_t * __module_Need( vlc_object_t *p_this, const char *psz_capability,

                          const char *psz_name, vlc_bool_t b_strict )(src\misc\modules.c)-> vlc_list_find(将所有已经载入的模块查询出来)->然后循环,根据 capability查找第一个最合适的module->AllocatePlugin(动态载入所需要的插件,该函数会在动态库所在目录,遍历所 有动态库文件,)->p_module->pf_activate(调用激活函数)

l VLC_Init函数流程:

module_InitBank->module_LoadBuiltins(载入静态插件)->module_LoadPlugins(载入动态插件->VLC_AddIntf(添加interface插件,VLC会静态载入hotkeys模块)

在VLC中根据处理任务不同,会静态载入不同的模块,main、memcpy、hotkeys等;动态载入的模块根据处理任务不同,差异很大。

2. VLC流媒体服务器体系结构

以下主要讨论VLC作为流媒体服务器时的体系结构。针对一个节目单文件,调试其运行过程,并最后给出总结。

该实例的播放节目单为如下:

New br broadcast enabled

Setup br input /mnt/hgfs/movie/caiyan.mpg

Setup br output #standard{mux=ts,access=udp,url=234.0.1.4,sap,name=ch1}

在例子中,通过VLC提供API:libvlc_new,libvlc_vlm_new,libvlc_vlm_play_media,libvlc_vlm_load_file等(有些API是自己添加的)可以完成对广播节目br的播放。

下面让我们仔细看看通过这几个接口,VLC内部到底是怎么工作完成了流媒体发布的。

1. 首先程序调用libvlc_new(\src\control\core.c)接口,实现创建一个VLC运行实例libvlc_instance_t,该实例在程序运行过程中唯一。

2. 在libvlc_new接口中,调用了VLC_Init函数实现具体的初始化工作。

3. VLC_Init(\src \libvlc.c)函数中,首先通过system_Init函数完成传入参数对系统的相关初始化,接着通过module_InitBank(\src \misc\modules.c)函数初始化module_bank结构体,并创建了main模块,然后通过module_LoadBuiltins载入 静态模块,通过module_LoadPlugins(\src\misc\modules.c)函数载入动态模块,通过 module_Need(\src\misc\modules.c)函数载入并激活memcpy模块,通过playlist_Create(\src \playlist\playlist.c)函数,创建了一个playlist播放管理的线程,其线程处理函数为RunThread(\src \playlist\playlist.c),通过VLC_AddIntf(\src\libvlc.c)函数添加并激活hotkeys模块,最后根据系 统设置定义了宏HAVE_X11_XLIB_H,因此还需要添加screensaver模块。

4. 总结:此时加载的模块有main,hotkeys,screensaver,memcpy;多创建了一个线程,用于管理playlist,该线程无限循环,直到p_playlist->b_die状态为止。

5. 其次程序中调用libvlc_vlm_new接口,创建VLM对象(该接口为自己添加的)。

6. 该接口调用的是vlm_New(\src\misc\vlm.c)函数,实现VLM对象的创建,函数返回值是指向vlm_t的指针。

7. Vlm_new 函数中,创建了一个vlm管理线程,线程处理函数为Manage(\src\misc\vlm.c)。该函数循环处理当前各种媒体(vod、 broadcast、schedule)的播放实例,控制其每个播放细节(如:从一个input切换到下一个input;schedule周期循环调度 等)。与playlist线程不同的是,Manage主要针对播放实例的操作,而RunThread主要针对播放列表的管理,也就是说VLC管理是分级 的,播放列表级和播放列表中媒体播放实例级。

8. 其次程序调用libvlc_vlm_load_file接口,载入播放节目单(该接口也为自己添加,播放节目单如上所述)。

9. 该接口调用的是vlm_Load(\src\misc\vlm.c)函数,在该函数中,依次调用如下函数:stream_UrlNew、stream_Seek、stream_Read、Load,以下详细介绍各个函数作用。

a) 首 先是stream_UrlNew(\src\input\stream.c)函数。先调MRLSplit(\src\input\input.c)函数完 成对access、demux和path的解析。具体对于本例解析的结果为:access= " ",demux=" ",path=" aa"。然后调 用access2_New(\src\input\access.c)函数创建一个access_t结构体并初始化。具体运行时载入模块的相关参数 是:capability="access2",name="access_file",psz_filename=access/libaccess_file_plugin.so。 最后调用stream_AccessNew(\src\input\stream.c)函数,创建stream_t结构体对象,并初始化对象中所有函数指 针;

b) 再调用stream_Seek(\include\vlc_stream.h)内联函数,设置起始位置;

c) 再调用stream_Size(\include\vlc_stream.h)获得大小;

d) 再调用stream_Read(\include\vlc_stream.h),读取到缓冲区;

e) 最 后调用Load(\src\misc\vlm.c),完成实际的载入节目单。对于节目单文件,是一行行解析,并调用 ExecuteCommand(\src\misc\vlm.c)完成解析的。Load函数的调用仅仅是设置了相关参数,如:设置input字符串值,设 置output字符串值,设置mux的值及与播放相关的enabled、loop等参数。Load工作仅仅是为了下一步发布流做准备的。

10. 程序中调用libvlc_vlm_play_media接口,将节目流发布出去。(自己添加接口)

11. 在libvlc_vlm_play_media接口中,实质是创建了命令“control br play”再调用 vlm_ExecuteCommand(\src\misc\vlm.c),完成对命令的执行,根据命令类型,由vlm_MediaControl(\src\misc\vlm.c)函数处理。

12. 在vlm_MediaControl函数中,会调用vlc_input_item_Init(\include\vlc_input.h)函数完成播放实例 的初始化,并调用input_CreateThread2(\src\input\input.c)函数完成播放线程的创建。该线程的处理函数为 Run(\src\input\input.c)。

13. Run线程是整个VLC作为流媒体服务器的核心。其主要分为如下几个步骤:Init、MainLoop和End。其中MainLoop是一个无限循环,是完成流媒体的整个发布过程。

a) 首先调用Init(\src\input\input.c)函数,初始化相关统计参数;

b) 其次再调用input_EsOutNew(\src\input\es_out.c)函数,初始化es_out_t结构体对象和es_out_sys_t结构体对象,并设置相关函数指针;

c) 再调用InputSourceInit(\src\input\input.c)函数,初始化input_thread_t对象中的input_source_t对象,主要有access_t、stream_t、demux_t三个结构体对象;

d) 总结此时各个模块实际载入的情况:

1) (access_t)type="access",name="access_filter",capability="access2",psz_filename="access/libaccess_file_plugin.so";

2) (stream_t)type="stream",pf_read="AStreamReadStream",pf_seek="AStreamPeekStream",pf_control="AStreamControl",pf_destory="AStreamDestory";

3) (demux_t)type="demux",capability="demux2",shortcuts="ps";

4) (sout_instance_t)type="stream out",psz_capability="sout stream",shortcut="stream_out_standard",psz_filename="/stream_out/libstream_out_standard_plugin.so";

5) (es_out_t)pf_add="ESOutAdd",pf_send="ESOutSend",pf_del="ESOutDel",pf_control="ESOutControl";

e) 再调用MainLoop(\src\input\input.c)函数,完成读取、解复用、解码、复用和传输;

f) MainLoop函数为无限循环,直到input_thread_t对象存在b_die、b_error、b_eof时为止。在该函数中,存在如下行代码:

i_ret=p_input->input.p_demux->pf_demux(p_input->input.p_demux);

它就是流媒体服务器运行的起点,所有的后续操作都会在该函数中继续衍生。

g) Pf_demux调用的是(\modules\demux\ps.c)中的Demux函数,在该函数中主要完成如下操作:

1) 先调用ps_pkt_resynch(\modules\demux\ps.c)函数,完成PS流中数据包重新同步(这里应该涉及到多媒体相关知识,需要补补);

2) 再调用ps_pkt_read(\modules\demux\ps.c)函数,最终调用stream_Block函数,这个函数内部会根据实际情况,调用stream_t模块中的pf_read或pf_block函数,函数结果会返回一个读取的buffer;

3) 根据数据包的i_code的值,做不同的处理,对于音视频数据流,调用es_out_Send(\include\vlc_es_out.h)函数处理;

4) es_out_Send一个抽象层函数,其通过函数指针,实际调用的是EsOutSend(\src\input\es_out.c)函数;

5) EsOutSend函数最终会调用input_DecoderDecode(\src\input\decoder.c)函数;

6) input_DecoderDecode函数会调用DecoderDecode(\src\input\decoder.c)函数完成解码;

7) DecoderDecode函数会调用pf_packetize(\modules\packetizer\mpegvideo.c)函数实现PES的打包;

8) DecoderDecode函数会调用sout_InputSendBuffer(\src\stream_output\stream_output.c)函数,实现发送;

9) sout_InputSendBuffer函数中的pf_send指针,指向的是(\modules\stream_out\standard.c)Send函数;

10) Send 函数调用的是流化输出(stream_output)的抽象层(\src\stream_output\stream_output.c)中的 sout_MuxSendBuffer函数,首先将要发送的数据放入fifo队列中,然后调用pf_mux函数指针,完成多路复用;

11) Pf_mux函数指针指向的是(\modules\mux\mpeg\ts.c)的Mux函数,完成多路复用后,最终调用(\modules\mux\mpeg\ts.c)TSSchedule函数,准备调度发送了;

12) TSSchedule函数中调用了TSDate(\modules\mux\mpeg\ts.c)函数;

13) TSDate函数中调用了流化输出(stream_output)的抽象层(\src\stream_output\stream_output.c)中的sout_AccessOutWrite函数,最终调用pf_write函数完成数据输出;

14) pf_write函数指向的是(\modules\access_output\udp.c)中的Write函数,完成数据UDP发送,这样数据就转换称TS流输出了;

15) 总结:pf_demux函数为流媒体所有操作的起点,通过该处衍生了很多其他模块的处理,从上面的分析可以看出,系统实质就是PS、ES、PES和TS几种流间的转换,针对应用场合(主要指做服务器或客户端)的不同,转换的方式不同

第一部分 变量及宏定义
1.消息映射宏
vlc_module_begin();
…………………..
vlc_module_end();
2.结构中包含函数
struct input_thread_t
{
VLC_COMMON_MEMBERS

vlc_bool_t b_eof;
vlc_bool_t b_out_pace_control;

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 inti_object_id; int
i_object_type; char *psz_object_type; char *psz_object_name; int

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 *,
char const *,
vlc_value_t,
vlc_value_t,
void * );
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)
{。。。。。。。。。。。}

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 *, constchar *, vlc_bool_t ) );
10.定义函数

#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(

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 则打

开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为运行的对话

框。
接收网络文件的步骤
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 voidRunThread( 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。


网络数据流接收处理分析
1、在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 );
…………………………………………………….

i_count = p_input->pf_demux( p_input );
}
2、在下列函数中:
分离出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 );

p_input->p_access = module_Need( p_input, "access",
p_input->psz_access, VLC_TRUE );
…………………………………………………….
while( !input_FillBuffer( p_input ) )
…………………………………………………….

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 );
…………………………………………………….
}
3、在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 )
{

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);
}
4、在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 );
5、在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 );
}
进行读取数据和分离工作;
6、在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);
7、在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  或者VLCdeveloper documentation


Chapter 5.  The video outputlayer
Data structures and main loop

Important data structures are defined ininclude/video.h and include/video_output.h. The main data structure ispicture_t, which describes

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

picture.

Note also the subpicture_t structure. Infact the VLC SPU decoder only parses the SPU header, and converts the SPUgraphical data to an

internal format which can be renderedmuch faster. So a part of the "real" SPU decoder lies insrc/video_output/video_spu.c.

The vout_thread_t structure is much morecomplex, but you needn't understand everything. Basically the video outputthread manages a heap of

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

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

   1.

      Find the next picture to display inthe heap.
2.

      Find the current subpicture todisplay.
3.

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

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

      Sleep until the specified date.
5.

      Display the picture (plug-infunction). For outputs which display RGB data, it is often accomplished with abuffer switching. p_vout-

>p_bufferis an array of two buffers where the YUV transform takes place, andp_vout->i_buffer_index indicates the currently displayed buffer.
6.

      Manage events.

Methods used by video decoders

The video output exports a bunch offunctions so that decoders can send their decoded data. The most importantfunction is vout_CreatePicture

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

decoded data, and callvout_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 anallocated picture buffer.

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

      If no picture is available in theheap, 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'tget accidently

freed whilethe decoder still needs it. For instance, an I or P picture can still be neededafter displaying to decode interleaved B pictures.
*

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

linkpreviously made.
*

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

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

previous Bpictures (which are indeed placed after - decoding order != presentationorder).
*

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

is ready tobe 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 astream parsing error).
*

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

i_channel isthe 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 subpicturehas been completely

decoded. Itobsoletes 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协议,如是否IPv6、组播地址、组

协议、端口等信息;如果检测出来是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)、MPEG4、H.264、rmvb等等。音视频解码器的作用就是把这些压缩了的数据还

原成原始的音视频数据。VLC解码MPEG2使用了一个独立的库libmpeg2,调用它的源文件是 /modules/codec/libmpeg2.c。VLC关于编解码的模块都放

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

    解码器,例如视频解码器输出的是一张一张的类似位图格式的图像,但是要让人从屏幕看得到,还需要一个视频输出的模块。当然可以像一个Win32窗口程序

那样直接把图像画到窗口DC上——VLC的一个输出模块WinGDI就是这么干的,但是通常这太慢了,而且消耗大量的CPU。在Windows下比较好的办法是用DirectX的接

口,会自动调用显卡的加速功能。

    这样的功能分解使得模块化更容易一点,每个模块住需要专注于自己的事;从整体来说功能强大而且灵活。

    但是事情总是不会那么简单。就拿access来说,媒体的访问是分层的,如RTSP就涉及到IPv4、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中的SourceFIlter,这一类Filter只有输出pin没有输入pin。demux模块对应于splitterfilter,这种filter有一个输入pin,多个输出pin。解码模

块是一类transformfilter,有一个输入pin、一个输出pin,输出模块对应于readeringfilter,有一个输入pin,没有输出pin。当然transformfilter不一定是

解码器,也可能是某种其他的处理。

    回到VLC的话题。每一类的模块数量都很多,那么在打开某一个视频时,VLC如何决定采用哪一个呢?哈哈,这个以后再说 ^_^

    另外给出一个VLC的API Document,有点老了不过挺值得一看的,在VLC wiki上找不到了,就贴出来:http://rogerfd.cn/doc/vlcapi.htm

嵌入式 IP数据报的TS、ES、PS流

IP数据报有首部和数据两部分组成的,首部的前一部分是固定长度20字节,是所有IP数据报必须具有的。首部包括:总长度、标识、MF、DF、片偏移。
 
数字信号实际传送的是数据流,一般数据流包括以下三种:

ES流:也叫基本码流,包含视频、音频或数据的连续码流。
PES流:也叫打包的基本码流,是将基本的码流ES流根据需要分成长度不等的数据包,并加上包头就形成了打包的基本码流PES流。

TS流:也叫传输流,是由固定长度为188字节的包组成,含有独立时基的一个或多个节目,适用于误码较多的环境。 
 
TS流(TransportStream)即在MPEG-2系统中,由视频,音频的ES流和辅助数据复接生成的用于实际传输的标准信息流称为MPEG-2传送流。信息复合/分离的过程称为系统复接/分接,据传输媒体的质量不同,MPEG-2中定义了两种复合信息流:传送流(TS)和节目流(PS:ProgramStream)

MPEG2-PS主要应用于存储的具有固定时长的节目,如DVD电影,而MPEG-TS则主要应用于实时传送的节目,比如实时广播的电视节目


TS流与PS流的区别在于TS流的包结构是固定长度的,而PS流的包结构是可变长度。 PS包与TS包在结构上的这种差异,导致了它们对传输误码具有不同的抵抗能力,因而应用的环境也有所不同。TS码流由于采用了固定长度的包结构,当传输误码破坏了某一TS包的同步信息时,接收机可在固定的位置检测它后面包中的同步信息,从而恢复同步,避免了信息丢失。而PS包由于长度是变化的,一旦某一PS包的同步信息丢失,接收机无法确定下一包的同步位置,就会造成失步,导致严重的信息丢失。因此,在信道环境较为恶劣,传输误码较高时,一般采用TS码流;而在信道环境较好,传输误码较低时,一般采用PS码流由于TS码流具有较强的抵抗传输误码的能力,因此目前在传输媒体中进行传输的MPEG-2码流基本上都采用了TS码流的包格。

 

嵌入式 rtp、rtsp、trcp的区别

RTP(Real-time TransportProtocol)是用于Internet上针对多媒体数据流的一种传输协议。RTP被定义为在一对一或一对多的传输情况下工作。其目的是提供时间信息和实现流同步。但RTP通常使用UDP来传送数据。但RTP也可以在TCPATM等其他协议之上工作。当应用程序开始一个RTP会话时将使用两个端口:一个给RTP一个给 RTCPRTP本身并不能为接顺序传送数据包提供可靠的传送机制。也不提供流量控制或拥塞控制。它依靠RTCP提供这些服务。通常RTP算法并不作为一个独立的网络层来实现。而是作为应用程序代码的一部分。实时传送控制协议RTCP.RTCP(Real-time Transport Control Protocol)RTP提供流量控制和拥塞控制。在RTP会话期间,各参与者周期性地传送RTCP.RTCP包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料.因此,服务器可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。RTPRTCP配合使用,它们能以有效的反馈和最小的开销使传输效率最佳化。因而特别适合传送网上的实时数据。 

实时流协议 RTSP 
实时流协议RTSP(Real-time StreamingProtocol)是由Real NetworksNetscape共同中提出的。该协议定义了一对多应用程序如何有效地通过lP网络传送多媒体数据。RTSP在体系结构上位于RTPRTCP之上。它使用TCPRTP完成数据传输。HTTPRTSP相比。HTTP传送HTML。而RTP传送是多媒体数据。HTTP请求由客户机发出,服务器作出响应;使用RTSP时,客户机和服务器都可以发出请求,即RTSP可以是双向的。

RTP/RTSP/RTCP的区别用一句简单的话总结:RTSP发起/终结流媒体、RTP传输流媒体数据RTCPRTP进行控制,同步。
之所以以前对这几个有点分不清,是因为CTC标准里没有对RTCP进行要求,因此在标准RTSP的代码中没有看到相关的部分。而在私有RTSP的代码中,有关控制、同步等,是在RTP Header中做扩展定义实现的
另外,RFC3550可以看作是RFC1889的升级文档,只看RFC3550即可。

·                RTP:实时传输协议(Real-time Transport Protocol

·                        RTP/RTCP是实际传输数据的协议 

·                        RTP传输音频/视频数据,如果是PLAYServer发送到Client端,如果是RECORD,可以由Client发送到Server 

·                        整个RTP协议由两个密切相关的部分组成:RTP数据协议和RTP控制协议(即RTCP

·                RTSP:实时流协议(Real Time Streaming ProtocolRTSP

·                        RTSP的请求主要有DESCRIBE,SETUP,PLAY,PAUSE,TEARDOWN,OPTIONS等,顾名思义可以知道起对话和控制作用 

·                        RTSP的对话过程中SETUP可以确定RTP/RTCP使用的端口,PLAY/PAUSE/TEARDOWN可以开始或者停止RTP的发送,等等

·                RTCP

·                        RTP/RTCP是实际传输数据的协议 

·                        RTCP包括Sender ReportReceiver Report,用来进行音频/视频的同步以及其他用途,是一种控制协议 

以下是每个协议的概要介绍:
一、RTP数据协议 
RTP
数据协议负责对流媒体数据进行封包并实现媒体流的实时传输,每一个RTP数据报都由头部(Header)和负载(Payload)两个部分组成,其中头部前12个字节的含义是固定的,而负载则可以是音频或者视频数据。RTP数据报的头部格式如图1所示:


其中比较重要的几个域及其意义如下: 

·                CSRC记数(CC):表示CSRC标识的数目。CSRC标识紧跟在RTP固定头部之后,用来表示RTP数据报的来源,RTP协议允许在同一个会话中存在多个数据源,它们可以通过RTP混合器合并为一个数据源。例如,可以产生一个 CSRC列表来表示一个电话会议,该会议通过一个RTP混合器将所有讲话者的语音数据组合为一个RTP数据源。

·                负载类型(PT):标明RTP负载的格式,包括所采用的编码算法、采样频率、承载通道等。例如,类型2表明该RTP数据包中承载的是用ITU G.721算法编码的语音数据,采样频率为8000Hz,并且采用单声道。

·                序列号:用来为接收方提供探测数据丢失的方法,但如何处理丢失的数据则是应用程序自己的事情,RTP协议本身并不负责数据的重传。

·                时间戳:记录了负载中第一个字节的采样时间,接收方能够时间戳能够确定数据的到达是否受到了延迟抖动的影响,但具体如何来补偿延迟抖动则是应用程序自己的事情。

RTP数据报的格式不难看出,它包含了传输媒体的类型、格式、序列号、时间戳以及是否有附加数据等信息,这些都为实时的流媒体传输提供了相应的基础。RTP协议的目的是提供实时数据(如交互式的音频和视频)的端到端传输服务,因此RTP中没有连接的概念,它可以建立在底层的面向连接或面向非连接的传输协议之上;RTP也不依赖于特别的网络地址格式,而仅仅只需要底层传输协议支持组帧(Framing)和分段(Segmentation)就足够了;另外RTP本身还不提供任何可靠性机制,这些都要由传输协议或者应用程序自己来保证。在典型的应用场合下,RTP一般是在传输协议之上作为应用程序的一部分加以实现的,如图2所示:



二、RTCP控制协议 
RTCP控制协议需要与RTP数据协议一起配合使用,当应用程序启动一个RTP会话时将同时占用两个端口,分别供RTPRTCP使用。RTP本身并不能为按序传输数据包提供可靠的保证,也不提供流量控制和拥塞控制,这些都由RTCP来负责完成。通常RTCP会采用与RTP相同的分发机制,向会话中的所有成员周期性地发送控制信息,应用程序通过接收这些数据,从中获取会话参与者的相关资料,以及网络状况、分组丢失概率等反馈信息,从而能够对服务质量进行控制或者对网络状况进行诊断。 

RTCP
协议的功能是通过不同的RTCP数据报来实现的,主要有如下几种类型: 


·                SR:发送端报告,所谓发送端是指发出RTP数据报的应用程序或者终端,发送端同时也可以是接收端。

·                RR:接收端报告,所谓接收端是指仅接收但不发送RTP数据报的应用程序或者终端。

·                SDES:源描述,主要功能是作为会话成员有关标识信息的载体,如用户名、邮件地址、电话号码等,此外还具有向会话成员传达会话控制信息的功能。

·                BYE:通知离开,主要功能是指示某一个或者几个源不再有效,即通知会话中的其他成员自己将退出会话。

·                APP:由应用程序自己定义,解决了RTCP的扩展性问题,并且为协议的实现者提供了很大的灵活性。

RTCP数据报携带有服务质量监控的必要信息,能够对服务质量进行动态的调整,并能够对网络拥塞进行有效的控制。由于RTCP数据报采用的是多播方式,因此会话中的所有成员都可以通过RTCP数据报返回的控制信息,来了解其他参与者的当前情况。
在一个典型的应用场合下,发送媒体流的应用程序将周期性地产生发送端报告SR,该RTCP数据报含有不同媒体流间的同步信息,以及已经发送的数据报和字节的计数,接收端根据这些信息可以估计出实际的数据传输速率。另一方面,接收端会向所有已知的发送端发送接收端报告RR,该RTCP数据报含有已接收数据报的最大序列号、丢失的数据报数目、延时抖动和时间戳等重要信息,发送端应用根据这些信息可以估计出往返时延,并且可以根据数据报丢失概率和时延抖动情况动态调整发送速率,以改善网络拥塞状况,或者根据网络状况平滑地调整应用程序的服务质量。


三、RTSP实时流协议 
作为一个应用层协议,RTSP提供了一个可供扩展的框架,它的意义在于使得实时流媒体数据的受控和点播变得可能。总的说来,RTSP是一个流媒体表示协议,主要用来控制具有实时特性的数据发送,但它本身并不传输数据,而是必须依赖于下层传输协议所提供的某些服务。RTSP可以对流媒体提供诸如播放、暂停、快进等操作,它负责定义具体的控制消息、操作方法、状态码等,此外还描述了与RTP间的交互操作(RFC2326

RTSP
在制定时较多地参考了HTTP/1.1协议,甚至许多描述与HTTP/1.1完全相同RTSP之所以特意使用与HTTP/1.1类似的语法和操作,在很大程度上是为了兼容现有的Web基础结构,正因如此,HTTP/1.1的扩展机制大都可以直接引入到RTSP中。
RTSP控制的媒体流集合可以用表示描述(Presentation Description)来定义,所谓表示是指流媒体服务器提供给客户机的一个或者多个媒体流的集合,而表示描述则包含了一个表示中各个媒体流的相关信息,如数据编码/解码算法、网络地址、媒体流的内容等。
虽然RTSP服务器同样也使用标识符来区别每一流连接会话(Session),但RTSP连接并没有被绑定到传输层连接(如TCP等),也就是说在整个 RTSP连接期间,RTSP用户可打开或者关闭多个对RTSP服务器的可靠传输连接以发出RTSP 请求。此外,RTSP连接也可以基于面向无连接的传输协议(如UDP等)。

RTSP
协议目前支持以下操作: 

·                检索媒体:允许用户通过HTTP或者其它方法向媒体服务器提交一个表示描述。如表示是组播的,则表示描述就包含用于该媒体流的组播地址和端口号;如果表示是单播的,为了安全在表示描述中应该只提供目的地址。

·                邀请加入:媒体服务器可以被邀请参加正在进行的会议,或者在表示中回放媒体,或者在表示中录制全部媒体或其子集,非常适合于分布式教学。

·                添加媒体:通知用户新加入的可利用媒体流,这对现场讲座来讲显得尤其有用。与HTTP/1.1类似,RTSP请求也可以交由代理、通道或者缓存来进行处理。

 

嵌入式 交换机与路由器的广播包

广播分二层广播和三层广播,二层广播是FFFF.FFFF.FFFF,二层交换机遇到这种包就会泛洪到所有同VLAN的端口,不会过滤掉,因为如果二层交换机过滤这种包,arp广播怎么正常工作呢?没有arp,同网段的PC如何通信呢?路由器可以过滤二层广播是因为路由器是三层设备,如果路由器不过滤这种包,任何人发起的二层广播包就会到达Internet的整个范围,这样还要路由器做什么呢?路由器就是为了划分广播域用的阿。

对于三层广播还有本地广播255.255.255.255和特定子网广播比如192.168.1.255/24之分,255.255.255.255这种本地广播是肯定没发跨越路由器的,因为一旦这种广播能跨越路由器的话,同样任何人发起的这种广播就会跑到Internet的任何角落。然而对于特定子网的广播是可以配置为允许跨路由器或者不允许跨路由器的,如果允许的话,我就可以跨越路由器对特定的子网发起三层广播。通常不要允许这类广播,因为你一旦允许了很容易造成icmp sumrf攻击的。

再说一下这种三层广播对二层交换机来说是没有意义的,普通二层交换机只能看到二层头。也只会根据二层头中的目标地址进行处理。

 

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