vlc结构分析和视频播放的基本原理


http://blog.csdn.net/offbye/article/details/7226244


第一部分 变量及宏定义
  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 */ 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 *, /* 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)
  {。。。。。。。。。。。}
  
  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(

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 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。


网络数据流接收处理分析
  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 );
   …………………………………………………….
   /* Read and demultiplex some data. */
   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 );
   /* 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 );
  …………………………………………………….
  }
  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 )
  {
   /* 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 );
  }
  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  或者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协议,如是否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中的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值