VLC简介及使用说明

一、简介
LC的全名是Video Lan Client,是一个开源的、跨平台的视频播放器。VLC支持大量的音视频传输、封装和编码格式,完整的功能特性列表可以在这里获得http://www.videolan.org/vlc/features.html,下面给出一个简要的不完整的列表:
操作系统:Windows、WinCE、Linux、MacOSX、BEOS、BSD
访问形式:文件、DVD/VCD/CD、http、ftp、mms、TCP、UDP、RTP、IP组播、IPv6、rtsp
编码格式:MPEG*、DIVX、WMV、MOV、3GP、FLV、H.263、H.264、FLAC
视频字幕:DVD、DVB、Text、Vobsub
视频输出:DirectX、X11、XVideo、SDL、FrameBuffer、ASCII
控制界面:WxWidgets、QT4、Web、Telnet、Command line
浏览器插件:ActiveX、Mozilla(firefox)
实际上为了更清晰一点,我们可以反过来说说VLC不支持哪些常见的。首先是RealVideo(Real的Audio部分支持),因为Real的Video加码器存在版权的问题。实际上VLC 0.9.0已经加入了RealVideo的支持,但是需要额外的解码器(类似MPlayer)。另外,VLC不支持3GP的音频格式AMR。

VLC原先是几个法国的大学生做的项目,后来他们把VLC作为了一个开源的项目,吸引了来自世界各国的很多优秀程序员来共同编写和维护VLC,才逐渐变成了现在这个样子。
至于为什么叫VideoLan Client,是因为以前还有一个VideoLan Server的项目(简称VLS),而目前VLS的功能已经合并到VLC中来,所以VLC不仅仅是一个视频播放器,它也可以作为小型的视频服务器,更可以一边播放一边转码,把视频流发送到网络上。VLC最为突出的就是网络流的播放功能,例如MPEG2的UDP TS流的播放和转发,几乎是无可替代的。
对普通用户来说,VLC还有一个好处是不影响Windows中的解码器。VLC通常不影响也不依赖于系统中自带的解码器(除了realvideo和quicktime的类型),很绿色很环保;更不用担心流氓软件、广告插件之类的恶心的玩意儿。
从程序结构来看,VLC的可扩展性是相当优秀的。VLC绝大部分用高效的C代码来编写(少量的C++和汇编),但是实现了完全动态的模块化,所有功能包括程序框架本身都是module,可以在运行时载入,这使得VLC可以轻易的扩展多种功能并且容易维护。它的架构有一点类似于DirectShow的技术。
VLC也很注重版权方面的问题,你可以放心的自由的使用而不需要为版权的问题担心——VLC只包括免费的、自由的库。VLC基于GPL,因此也可以用于商业应用,只需要保留GPL,以及公开源代码,如果你修改了VLC的话。
下面是VLC相关的一些链接
VLC官方网站:http://www.videolan.org/
VLC下载页面:http://www.videolan.org/vlc/
VLC下载目录(源码和安装包):http://download.videolan.org/pub/videolan/vlc/
VLC Nightly Builds: http://nightlies.videolan.org/
VLC 开发Wiki:http://wiki.videolan.org/Developers_Corner
VLC Win32第三方库预编译包下载目录:http://download.videolan.org/pub/testing/win32/
VLC 官方论坛:http://forum.videolan.org/
VLC 邮件列表:http://www.videolan.org/developers/lists.html
三、Linux平台编译
先下载源代码,把源代码解压之后也是先运行一下bootstrap,看看缺什么工具没有,例如gcc、make、libtool、automake、autoconfig等是否有合适的版本。如果不合适的话就用你喜欢的方式去升级或者安装。
下面最麻烦的一步来了。VLC在Linux上没有给出和Windows上类似的第三方库的预编译包,你必须去自己获得并编译这些库。这些库的数量取决于你的配置选项。VLC给出了一个自动化的解决方案,关注一下主目录\extras\contrib,里边有一些工具来帮助你自动下载、patch和编译所有可能用到的第三方库。先运行一下那个目录下的bootstrap,如果缺少***.mak的话可以到VLC的代码库中找一下,是一个系统相关的文件,然后简单的一句make就可以了。如果你对这些库比较熟悉的话最好先手动筛选一下,有一些可能不是你所需要的,而下载他们可能需要很长的时间。
在漫长的等待之后,如果一切顺利(我几乎不相信会这样),这一步就算完成了。可能遇到的问题有:下载实在太慢,可以用其他下载工具把库的代码包下下来放到\extras\contrib\src里边;如果下载一半断掉,可以把那个不完整的文件删掉重新再运行make。
这一步做完之后和windows上的编译就几乎一样了。写一个配置脚本然后运行它。例如一个配置脚本:  
 ./configure \
 –enable-release \
 –disable-skins2 \
 –disable-wxwidgets \
 –enable-mozilla \
 –with-mozilla-sdk-path=./gecko-sdk \
 –disable-sout \
 –disable-httpd \
 –enable-live555 \
 –disable-dvdnav \
 –disable-libcdio \
 –disable-libcddb  \
 –disable-cdda  \
 –disable-vcd  \
 –disable-dvdread \
 –disable-smb \
 –disable-cmml \
 –disable-alsa \
 –disable-opengl \
 –disable-png \
 –disable-screen  \
 –disable-mkv  \
 –disable-mod  \
 –disable-mpc  \
 –disable-libtar  \
 –disable-speex  \
 –disable-visual  \
 –disable-daap  \
 –disable-bonjour  \
 –disable-gnutls  \
 –enable-faad \
 –enable-mostly-builtin
配置完之后再运行make就可以了。
参考:http://wiki.videolan.org/UnixCompile

二、Windows平台编译
第一步,要创建编译VLC的环境。VLC在Windows下可以用Msys+MingW 或者Cygwin的方式来编译,二者大同小异,这里主要介绍Cygwin。
Cygwin是一个在Windows下面模拟Linux环境的东西。它提供了很多库和应用程序,让你可以像在Linux上一样来使用Windows。你需要在http://www.cygwin.com/ 下载一个cygwin的安装程序setup.exe。然后选择一个cygwin的源来下载所需要的库,国内的话到 ftp://ftp.sjtu.edu.cn/cygwin/和 http://www.cygwin.cn/pub 相对来说会快一点。第一次安装,除了默认的库以外,我们还需要加入一些库来支持VLC的编译,包括
Archive (目录)
unzip
zip
Devel
autoconf
automake
binutils
cvs
expat
gcc
gcc-core
gcc-g++
gcc-mingw
gcc-mingw-core
gcc-mingw-g++
gdb (如果你需要调试的话)
gettext
gettext-devel
git (不一定需要)
libiconv
libtool
make
mingw-runtime
nasm
patchutils
pkg-config
subversion
Editor
vim (有了它方便点)
Libs
libgcrypt
Web //这两个可以不要,如果你不想编译第三方库
curl (optional: for building extras/contrib)
wget (optional: for building extras/contrib)

然后是下载所需要版本的VLC的源代码。对于Release版本,可以从这里下载:http://download.videolan.org/pub/videolan/vlc/,如果你需要下载实时最新的库,就要从VLC的源码库上取下来了。现在VLC改用git了,使用命令: git clone git://git.videolan.org/vlc.git

VLC还需要很多第三方的库,你可以取得源码来编译,这部分可以参考Linux上的VLC编译的文章。在Windows上VLC做了一个编译好的win32第三方库的包,可以从http://download.videolan.org/pub/testing/win32/ 下载。包需要解压到根目录 ‘/’,事实上这些库都位于’/usr/win32-branch’里边。

在cygwin中进入vlc的主目录,运行bootstrap,’./bootstrap’。在等待它结束之前,我们先来看一下如何配置VLC的编译。运行 ‘./configure -h >cfg_opt.txt’就可以把配置的选项信息都输出到 cfg_opt.txt中,然后慢慢来研究。里面大多是启用、禁用某些特性和功能模块,或者配置某些模块需要的库的路径等信息。为了方便可以写一个简单的脚本来做这件事,当然这个不是必需的。  

PATH=/usr/win32-branch/bin:$PATH
PKG_CONFIG_PATH=/usr/win32-branch/lib/pkgconfig
CPPFLAGS=”-I/usr/win32-branch/include -I/usr/win32-branch/include/ebml”
LDFLAGS=-L/usr/win32-branch/lib
CC=”gcc -mno-cygwin” CXX=”g++ -mno-cygwin”
./configure
–host=i686-pc-mingw32
–disable-gtk
–enable-nls –enable-sdl –with-sdl-config-path=/usr/win32-branch/bin
–enable-ffmpeg –with-ffmpeg-mp3lame –with-ffmpeg-faac
–with-ffmpeg-zlib –enable-faad –enable-flac –enable-theora
–with-wx-config-path=/usr/win32-branch/bin
–with-freetype-config-path=/usr/win32-branch/bin
–with-fribidi-config-path=/usr/win32-branch/bin
–enable-live555 –with-live555-tree=/usr/win32-branch/live.com
–enable-caca –with-caca-config-path=/usr/win32-branch/bin
–with-xml2-config-path=/usr/win32-branch/bin
–with-dvdnav-config-path=/usr/win32-branch/bin
–disable-cddax –disable-vcdx –enable-goom
–enable-twolame –enable-dvdread
–enable-release –enable-dca
–enable-mkv
–enable-quicktime –enable-mozilla
–with-mozilla-sdk-path=/usr/win32-branch/gecko-sdk
–enable-mostly-builtin
&& make

例如把这个文件保存为 ‘mybuild’,等bootstrp运行结束之后,我们只需要运行一下 mybuild就会开始配置和编译了。这通常需要挺长的时间,如果运气好,在漫长的等待之后make运行成功,vlc就编译好了。直接双击主目录里边的vlc.exe应该就可以运行了。
为了方便可以把它打包,最简单的是 ‘make package-win32-base’,它将创建一个子目录来存放所有运行所需的东西,这个目录就是一个绿色版的VLC啦。
当然如果你运气没那么棒,可能会碰到各种各样的错误,例如在boottrap或者configure中出错,通常是因为没安装某个库或者库的版本不合适;也可能碰到编译的错误,尝试去google一下。
三、Linux平台编译
先下载源代码,把源代码解压之后也是先运行一下bootstrap,看看缺什么工具没有,例如gcc、make、libtool、automake、autoconfig等是否有合适的版本。如果不合适的话就用你喜欢的方式去升级或者安装。
下面最麻烦的一步来了。VLC在Linux上没有给出和Windows上类似的第三方库的预编译包,你必须去自己获得并编译这些库。这些库的数量取决于你的配置选项。VLC给出了一个自动化的解决方案,关注一下主目录\extras\contrib,里边有一些工具来帮助你自动下载、patch和编译所有可能用到的第三方库。先运行一下那个目录下的bootstrap,如果缺少***.mak的话可以到VLC的代码库中找一下,是一个系统相关的文件,然后简单的一句make就可以了。如果你对这些库比较熟悉的话最好先手动筛选一下,有一些可能不是你所需要的,而下载他们可能需要很长的时间。
在漫长的等待之后,如果一切顺利(我几乎不相信会这样),这一步就算完成了。可能遇到的问题有:下载实在太慢,可以用其他下载工具把库的代码包下下来放到\extras\contrib\src里边;如果下载一半断掉,可以把那个不完整的文件删掉重新再运行make。
这一步做完之后和windows上的编译就几乎一样了。写一个配置脚本然后运行它。例如一个配置脚本:  
 ./configure \
 –enable-release \
 –disable-skins2 \
 –disable-wxwidgets \
 –enable-mozilla \
 –with-mozilla-sdk-path=./gecko-sdk \
 –disable-sout \
 –disable-httpd \
 –enable-live555 \
 –disable-dvdnav \
 –disable-libcdio \
 –disable-libcddb  \
 –disable-cdda  \
 –disable-vcd  \
 –disable-dvdread \
 –disable-smb \
 –disable-cmml \
 –disable-alsa \
 –disable-opengl \
 –disable-png \
 –disable-screen  \
 –disable-mkv  \
 –disable-mod  \
 –disable-mpc  \
 –disable-libtar  \
 –disable-speex  \
 –disable-visual  \
 –disable-daap  \
 –disable-bonjour  \
 –disable-gnutls  \
 –enable-faad \
 –enable-mostly-builtin
配置完之后再运行make就可以了。
参考:http://wiki.videolan.org/UnixCompile

四、使用
VLC的功能很强大,它不仅仅是一个视频播放器,也可作为小型的视频服务器,更可以一边播放一边转码,把视频流发送到网络上。
最简单的,从界面打开一个文件播放,也可以在命令行下使用,如C:\Program Files\VideoLAN\VLC>vlc.exe test.ts
获取内置的帮助,会写到vlc-help.txt:C:\Program Files\VideoLAN\VLC>vlc.exe -h
获取更详细的帮助,有大量的选项介绍:C:\Program Files\VideoLAN\VLC>vlc.exe -H

在线使用帮助
http://www.videolan.org/doc/play-howto/en/play-howto-en.html
http://www.videolan.org/doc/streaming-howto/en/streaming-howto-en.html

搜索并显示可用的模块列表:C:\Program Files\VideoLAN\VLC>vlc.exe -l

Windows下,默认情况,VLC的配置文件在 %APPDATA%\vlc\vlcrc,%APPDATA%在XP下通常是 C:\Documents and Settings\用户名\Application Data
Linux下,在用户home目录的/.vlc/中

vlc的选项完全可以通过修改vlcrc来设置,界面只是略微方便一点
重置所有选项到默认状态:C:\Program Files\VideoLAN\VLC>vlc.exe –reset-config
VLC从vlcrc中指定的plugin-path,以及当前目录的modules和plugins目录来递归查找plugin,VLC的大部分功能都是通过plugin来实现的。VLC默认有大量的动态插件,为了加快启动速度,vlc会在%APPDATA%\vlc\cache中缓存plugin的列表,选项plugins-cache=0可以禁止缓存plugin

打开一个UDP组播流,组播组 239.255.1.1,端口 4567,默认端口1234,对于rtp协议,VLC会自动识别,写udp还是rtp都没问题
C:\Program Files\VideoLAN\VLC>vlc.exe udp://@239.255.1.1:4567

在本地UDP端口 888 接收流, “@”表示绑定一个地址,而不是连接该地址
C:\Program Files\VideoLAN\VLC>vlc.exe udp://@888

串流输出,就是在播放的时候,以某种方式向外传送视频,在打开界面的串流/保存 中设置会比较方便。
例如,循环播放test.ts, 以rtp方式传送到224.1.1.1端口1234, 同时显示视频
vlc.exe test.ts –loop :sout=#duplicate{dst=std{access=rtp,mux=ts,dst=224.1.1.1:1234},dst=display}

例如,接收UDP端口888,数据全部保存到C:\dump.ts
vlc.exe udp://@888 :demux=dump :demuxdump-file=”C:\dump.ts”

关于Windows下视频输出模块
Direct3D :效果比DirectX差一点,但是方便截图,也可以在图像上实现alpha
DirectX:效果最好,利用DirectX的显示加速
OpenGL:在不同的硬件上表现不太一样
WinGDI:最慢的一种,不需要显卡加速
caca:用彩色的Assic字符来显示

临时启用某个视频输出,可以这样:C:\Program Files\VideoLAN\VLC-0.8.6e>vlc test.ts –vout=caca

ActiveX控件
官方发布的VLC自带ActiveX控件 axvlc.dll,注册之后可以方便的在应用程序和网页中使用VLC,注册的办法是
C:\Program Files\VideoLAN\VLC-0.8.6e>regsvr32 axvlc.dll
ActiveX VLC的使用方法可以参考源代码中ActiveX目录的README.TXT和test.html,ActiveX控件的接口有第一版和第二版,第一版简单,功能少,已经不再维护建议用第二版本,功能多一点

Mozilla Plugin
你还可以在Windows和Linux的Firefox中使用VLC。Windows下可以在安装VLC的时候选上Mozilla plugin,事实上它做的就是在HKLM_Software_MozillaPlugin键下添加一个VLC的子键。Linux下可以把libvlcplugin.so(或者叫npvlc.so)和插件目录放到Firefox的plugins目录,来使之生效。
同样这适用于基于Xulrunner的应用程序,事实上像 Miro、SongBird等xul应用都是用的这个插件。与ActiveX类似的,VLC的Mozilla Plugin也有两套接口,建议用新的第二版。

注册表
在Windows下VLC只使用很少量的注册表的信息,最重要的一条是HKLM_Software_VLC键下的InstallDir项,VLC的Mozilla Plugin和ActiveX控件通过这一项来定位其插件的目录。Telnet、Http控制对VLC来说,控制界面都是Interface类的模块,你可以使用各种控制模块。Windows下默认使用的是wxwidgets图形界面,还可以使用http、telnet等界面,来远程控制VLC,如果用VLC在服务器上专门作视频流转发一类的事情,这种远程界面可以帮上大忙。

视频过滤器
0.8系列中有多达13种视频过滤器,为视频添加各种效果和特殊处理。

Skin2界面
一个漂亮一点的,可以换肤的界面。

其他的
播放DVD、VCD光盘,打开DirectShow设备,播放当前屏幕…

一些问题
目前的VLC无法播放Real视频,如rm、rmvb。其实从0.9版本的VLC开始可以依赖其他解码器播放。但就目前测试的情况来看,顺序播放没问题,但是拖动则效果很差。RTSP的播放,拖动有问题,在获取播放位置时有缺陷。例如和达尔文服务器的配合。

五、视频播放的基本原理
几乎所有的视频播放器,如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模块对应于splitter filter,这种filter有一个输入pin,多个输出pin。解码模块是一类transform filter,有一个输入pin、一个输出pin,输出模块对应于readering filter,有一个输入pin,没有输出pin。当然transform filter不一定是解码器,也可能是某种其他的处理。
另外给出一个VLC的API Document,参见:http://rogerfd.cn/doc/vlcapi.htm

六、精简
VLC默认包含了大量的功能,超过2百个插件;Windows下安装包大小接近10MB,安装之后超过35MB。有时候如果我们只需要部分的功能,应该如何精简它呢?
精简VLC的第一步是重新配置编译选项,将不需要的功能去掉。一个精简配置脚本如下:
PATH=/usr/win32-branch/bin:$PATH
PKG_CONFIG_PATH=/usr/win32-branch/lib/pkgconfig
CPPFLAGS=”-I/usr/win32-branch/include -I/usr/win32-branch/include/ebml”
LDFLAGS=-L/usr/win32-branch/lib
CC=”gcc -mno-cygwin” CXX=”g++ -mno-cygwin”
./configure
–with-freetype-config-path=/usr/win32-branch/bin
–with-fribidi-config-path=/usr/win32-branch/bin
–disable-ffmpeg
–disable-live555
–disable-cddax
–disable-vcdx
–disable-skins2
–disable-wxwidgets
–disable-mozilla
–disable-httpd
–disable-dvdnav
–disable-libcdio
–disable-libcddb
–disable-cdda
–disable-sdl
–disable-sdl-image
–disable-vcd
–disable-dvdread
–disable-smb
–disable-cmml
–disable-alsa
–disable-opengl
–disable-png
–disable-screen
–disable-mkv
–disable-mod
–disable-mpc
–disable-libtar
–disable-speex
–disable-visual
–disable-daap
–disable-bonjour
–disable-gnutls
–disable-vorbis
–disable-real
–disable-xml
–disable-x264
–enable-sout
–enable-activex
–enable-release

 从这个脚本可见,主要是禁用了大量的功能。运行 ./configure -h 可以显示每一个配置项的意义;如果还不清楚可以再去搜一下选项对应的库的位置。ffmpeg是一个庞大的编解码库,但是MPEG2的解码可以利用另一个库libmpeg2,所以在这里不需要它。
 注意make完之后要用make package-win32-base来打一下包,打包时会用strip工具来压缩每一个可执行文件。
 但是这样还没有结束。配置项并不是很全面的,而且还会有一些小问题,例如xml禁用了还会有。。。于是还需要手动的清理一下plugins目录里面的插件,挑出没有用处的,这也需要对VLC模块的熟悉。或者使用Roger的笨方法,一个一个试,对功能没有影响的就拿掉。SImpleTV0.4所使用的plugin如以下所示:   
liba52sys_plugin.dll                  liba52 用于AC3格式的音频解码,CCTV高清频道需要
liba52tofloat32_plugin.dll
liba52tospdif_plugin.dll
liba52_plugin.dll
libaccess_file_plugin.dll             文件访问,播放文件需要
libaccess_http_plugin.dll             HTTP访问模块
libaccess_output_file_plugin.dll      sout的文件模块,用于录制
libaccess_udp_plugin.dll              UDP访问模块
libaout_directx_plugin.dll            DirectX的声音输出,默认的声音输出模块
libdeinterlace_plugin.dll               解交错的vout-filter模块
libdirect3d_plugin.dll                  Direct3D视频输出模块,在上面可以半透明。。。
libfixed32tofloat32_plugin.dll        一些数据转换模块
libfixed32tos16_plugin.dll
libfloat32tos16_plugin.dll
libfloat32tos8_plugin.dll
libfloat32tou16_plugin.dll
libfloat32tou8_plugin.dll
libfloat32_mixer_plugin.dll
libglwin32_plugin.dll                  OpenGL视频输出模块
libi420_ymga_plugin.dll
libi422_yuy2_plugin.dll
libipv4_plugin.dll                    IPv4网络模块
libipv6_plugin.dll                    IPv6网络模块
liblibmpeg2_plugin.dll                libmpeg2,解码全靠它
liblogger_plugin.dll                  日志模块,调试必备
libmemcpy_plugin.dll                  好像是利用各种CPU指令加速内存拷贝的
libmpeg_audio_plugin.dll              MPEG音频的解码模块
libmpgatofixed32_plugin.dll
libmpga_plugin.dll
libmux_dummy_plugin.dll
libpacketizer_mpegvideo_plugin.dll
libs16tofixed32_plugin.dll
libs16tofloat32swab_plugin.dll
libs16tofloat32_plugin.dll
libs8tofloat32_plugin.dll
libsimple_channel_mixer_plugin.dll
libstream_out_display_plugin.dll      sout的模块 。。。
libstream_out_duplicate_plugin.dll
libstream_out_standard_plugin.dll
libtrivial_channel_mixer_plugin.dll
libtrivial_mixer_plugin.dll
libtrivial_resampler_plugin.dll
libts_plugin.dll                         TS流的解复用模块
libu8tofixed32_plugin.dll
libu8tofloat32_plugin.dll
libugly_resampler_plugin.dll
libvout_directx_plugin.dll            DirectX视频输出模块
libwingdi_plugin.dll                     GDI视频输出模块  
作为ActiveX控件的VLC,只需要这些模块和axvlc.dll就足够了。当然如果使用了libvlc.dll还要带上。

VLC 0.9.2 正式发布 && VLC播放Rm Rmvb的插件
新的VLC加入了RealVideo的demux和codec module,已经可以播放rm、rmvb格式的real视频,但是需要下载以下两个附件:
pncrt.dll 拷贝到 C:\Program Files\VideoLAN\VLC
drv43260.dll 拷贝到 C:\Program Files\VideoLAN\VLC\plugins
这两个文件也可以从MPlayer的win32 codec中拷贝出来。从测试的情况来看,顺序播放没有什么问题,但是跳转的时候比较慢,图像不太稳定
这里有对VLC新特性的介绍 http://wiki.videolan.org/What_is_cool_in_0.9
Release Note:http://www.videolan.org/developers/vlc/NEWS
下载目录:http://download.videolan.org/pub/vlc/0.9.2/win32/
下载链接(exe):http://download.videolan.org/pub/vlc/0.9.2/win32/vlc-0.9.2-win32.exe

七、将VLC嵌入自己的应用
总的来说把VLC内嵌入自己的应用有4种途径:
•直接调用VLC进程
•VLC的plugin for Mozilla
•VLC的ActiveX插件
•调用libvlc
当然,理论上也可以把VLC的源码直接扣出一部分来放到自己的程序中,不过这种方法难度太大没多大意义。
先说第一种办法,别怀疑,这是非常有用的一种解决方案,特别是对于转码、流转发等应用,通常直接调用VLC就可以了。VLC有HTTP和Telnet的Interface模块(就是和用户交互的界面),也很方便和远程交互。而且跨平台应用也非常方便,只需要安装不同平台的VLC就行了。直接调用VLC的话最重要的就是参数,可以从 vlc.exe -H 中获取比较详细的命令行参数介绍。对于转码、转发的,命令行参数会比较复杂,可以用GUI先试一下,然后把界面上的Generated stream output string拷下来。
VLC的Mozilla plugin除了用于Firefox的web页之外,还可以用于任何XUL-base的应用,这种应用通常使用xulrunner来启动。XUL是一种用XML来写界面的语言,有点像HTML而且也兼容大部分HTML标记,Firefox就是用XUL来写的。VLC的Mozilla插件通常用Javascript来控制,详细的接口文档可以参考这里: http://www.videolan.org/doc/play-howto/en/ch04.html#id310965 这个接口有比较老的第一版和比较新的第二版,建议用新的,功能多一些。用VLC的Mozilla插件的应用并不少,例如比较出名的Miro、Songbird。因为这个好处是显而易见的:跨平台、可控性强、使用简单;限制也很明显:专为Mozilla设计,应用的范围很窄。
VLC的ActiveX插件只能用于Windows平台,当然这是因为ActiveX只是微软的东西。但是不得不说这个还是很好用的。无论是C++、C#、VB还是网页,都可以轻松地将这个ActiveX利用起来。关于这个的VLC官方文档在这里http://wiki.videolan.org/ActiveX_Controls 开发、使用之前别忘了先要注册ActiveX控件,方法是运行 regsvr32 axvlc.dll。Roger 之前写的 SimpleTV 就是调用了VLC的ActiveX控件,所以这也可以作为一个示例的代码,可以主要看看怎么调用这个控件来做串流的代码:
//Create Option for the plugin
bool bIPv6 = false;
if (strstr (szMRL, “::”))
{
bIPv6 = true;
}

tagVARIANT opt,var,ip6;
CString sout, ipv6 = “ipv6=1″;
SAFEARRAY *psa;
long index;
SAFEARRAYBOUND bound[1] = {0};
bound[0].cElements = bIPv6 ? 2 : 1;

if (m_cfg.bDisplayWhenRecording)
{
sout.Format(”sout=#duplicate{dst=display,dst=std{access=file,mux=ts,dst=\”%s\”}}”,
strName);
}
else
{
sout.Format(”sout=#duplicate{dst=std{access=file,mux=ts,dst=\”%s\”}}”, strName);
}

var.vt = VT_BSTR;
var.bstrVal = sout.AllocSysString();
psa = SafeArrayCreate (VT_VARIANT, 1, bound);
index = 0;
SafeArrayPutElement (psa, &index, &var);
if (bIPv6)
{
ip6.vt = VT_BSTR;
ip6.bstrVal = ipv6.AllocSysString();
index = 1;
SafeArrayPutElement (psa, &index, &ip6);
}
opt.vt = VT_ARRAY | VT_BSTR;
opt.parray = psa;

tagVARIANT nul;
nul.vt = VT_NULL;
m_vlc.GetPlaylist ().add (szMRL, nul, opt);
m_vlc.GetPlaylist ().next ();
m_vlc.GetPlaylist ().play ();

 很多人调用VLC的ActiveX控件都会遇到一个问提就是,播放没有反应。大部分的原因都是因为plugins的问题。VLC作为exe运行时会递归搜索当前目录下的modules和plugins目录来寻找plugin,但是当以ActiveX控件运行时,默认只会检查注册表的HKLM_Software_VideoLan_VLC_InstallDir,并添上plugins作为模块搜索路径,如果这个路径没有设置或者设置不对,VLC将找不到大部分的模块,导致大部分功能缺失而无法运行。如果需要修改这一特性可以修改 \activex\plugin.cpp (0.8.6i)中的 VLCPlugin::getVLC函数。在SimpleTV中,Roger修改的一个片段是

char p_pluginpath[MAX_PATH+1];
int iTmp = 0;
/* 得到SimpleTV.exe的全路径 /
DWORD dwLen = GetModuleFileNameA (NULL, p_pluginpath, MAX_PATH);
for (iTmp = strlen(p_pluginpath) – 1; iTmp >= 0; — iTmp)
{
if (p_pluginpath[iTmp] == ‘\’)
{
p_pluginpath[iTmp] = 0;
break;
}
}
/
将模块路径设为SimpleTV下的 player\modules */
strcat (p_pluginpath, “\player\modules“);
ppsz_argv[ppsz_argc++] = “–plugin-path”;
ppsz_argv[ppsz_argc++] = p_pluginpath;

还有一点是配置文件vlcrc的路径,如果需要修改的话,可以修改 src/misc/configuration.c 中的 GetDir 函数, 在Windows下这个目录默认返回 %APPDATA% ,以上两步做好的话其实就可以做出一个Portable的绿色版VLC了。
和其他ActiveX的播放器,例如WMP、QuickTime、RealPlayer相比,VLC的优点也很明显:自带所需解码器、开源、功能强大。
当然ActiveX方式和Mozilla 插件的方式也是有局限性的,这样的方式功能还不够强大,例如没有截屏的接口…如果需要更多完整的功能,可以考虑直接调用libvlc的接口,

VLC官方提供了一些文档和例子 http://wiki.videolan.org/Libvlc。这里还有一个用C#调用libvlc的例子http://jeremiah.blog.51cto.com/539865/116981
在Windows下libvlc可能默认是不编译的(0.8.6i),可以在configure中开启这一选项 –enable-shared-libvlc
完整的VLC体积还是不小的,如果只需要VLC一部分功能的话,可以最后做一下精简。
功能部份:

VLC媒体播放器的核心是libvlc ,它提供了界面,应用处理功能,如播放列表管理,音频和视频解码和输出,线程系统。所有libvlc源文件设在的/src目录及其子目录:

config/ :从命令行和配置文件加载配置,提供功能模块的读取和写入配置

control/: 提供动作控制功能,如播放/暂停,音量管理,全屏,日志等。

extras/: 大多是平台的特殊代码

modules/: 模块管理

network/: 提供网络接口(socket管理,网络错误等)

osd/: 显示屏幕上的操作

test/: libvlc测试模块

text/: 字符集

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

playlist/: 管理播放功能,如停止,播放,下一首,随机播放等

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

video_output/ : 初始化视频播放器,把从解码器得到的视频画面转化格式(从YUV 转为 RGB)然后播放它们

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

stream_output/: 输出音频流和视频流到网络

misc/: libvlc使用的其他部分功能 ,如线程系统,消息队列, CPU的检测,对象查找系统,或平台的特定代码。

模块部份:

VLC媒体播放器的模块部份,在/modules的子目录下(详细说明可以参考其下的List文件),这些模块只在程序载入它们时有效.每一个模块,可提供不同的功能,它们会适合的特定文件或某一特定的环境.此外,audio_output/video_output/interface 模块都写成了可跨平台的代码,方便支持新的平台(如beos或服务Mac OS X ) 。

插件模块可以在 src/modules.c 和 include/vlc_modules*.h 提供函数中,动态加载和卸载

LibVLC可以将模块直接插入到应用程序中,例如不支持动态加载代码的操作系统.模块静态插入到应用程序叫内建.
1.vlc.c 只是入口程序

2.Libvlc.c 是各个模块的结合点,这要是对接口编程

Vlc_Create(): 两个重要的数据结构:libvlc_t & vlc_t , 所有的参数传递都在这里面
Vlc_Init(): 初始化参数, module_bank
Vlc_AddInf(): 添加module
3./src/misc/configure.c 命令行参数和参数文件分析
参数文件是~/.vnc/vlcrc。其中可以设置log文件的位置

4./include/ 所有头文件的集合

5./src/interface/Interface.h 所有module的集合

6./src/misc/Modules.c
其中module_t * __module_Need( vlc_object_t *p_this, const char *psz_capability,
const char *psz_name, vlc_bool_t b_strict ) 方法是寻找合适的interface
如果找到合适的,就调用AllocatePlugin()动态的分配一个。

7.how to link to different modules without OOP

对VLC源代码阅读的计划是从其程序的框架开始,先对其主要的文件进行整理:
1.include/main.h 文件: access to all program variables,主要定义了2个结构体:libvlc_t,vlc_t。
a. struct libvlc_t 根据程序注释:该结构体只有一个实例,在main函数中被分配,而且只能在main中访问。它用来存储一些只能初始化一次的数据,比如说cpu容量或者global lock.
b. struct vlc_t 注释称:This structure is a LibVLC instance
libvlc_t,vlc_t在VLC_COMMON_MEMBERS宏中出现,分别定义了 libvlc_t * p_libvlc; vlc_t * p_vlc; 对象,注释称为 root of the evil,可见其结构体的重要性.所有的参数传递都在这里面(具体尚不清楚)。
2.include/Vlc_common.h 文件:common definitions,Collection of useful common types and macros definitions,通用类型和宏定义的集合
主要作用是为了将不同的操作系统中的变量定义统一起来,比如说根据将unit_8来统一代表unsiged char类型.
该文件中还定义了VLC_COMMON_MEMBERS宏,该宏中包括了所有VLC基本对象的通用成员变量:these members are common for all vlc objects。
定义导出函数
#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
定义回调函数
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 */
3.include/vlc_objects.h 文件:vlc_object_t definition and manipulation methods,vlc_object_t的定义和处理函数
struct vlc_object_t
{
VLC_COMMON_MEMBERS
}; //定义一个结构来使用宏定义的公共成员

VLC中vlm介绍
代码从两大部分入手,一个telnet 的deamon。还有就是rtsp的实现部分 。结果发现,他们通过了一个桥梁vlm的media进行沟通。

当受到new MEDIANAME vod enabled 就建立一个media。
如果受到setup MEDIANAME input filename.mpg 就读入流准备分析,建立input流
当受到rtsp的请求后,就建立这个output流
这样,vod就和别的模块一致了。rtsp只是一种output流的module。

代码分析

  1. /modules/control/telnet.c
    /*****************************************************************************
  • Run: main loop
    *****************************************************************************/
    static void Run( intf_thread_t *p_intf ){
    vlm_ExecuteCommand( p_sys->mediatheque, cl->buffer_read,
    &message );
    }
  1. /src/Misc/vlm.c
    /*****************************************************************************
  • vlm_ExecuteCommand:
    *****************************************************************************/
    int vlm_ExecuteCommand( vlm_t *p_vlm, const char *psz_command,
    vlm_message_t **pp_message)
    {
    }
    vlm_MediaNew( vlm_t *vlm, const char *psz_name, int i_type ){
    vlm_media_t *media = malloc( sizeof( vlm_media_t ) );
    }

struct vlm_t
{
VLC_COMMON_MEMBERS
vlc_mutex_t lock;
int i_media;
vlm_media_t **media;
int i_vod;
vod_t *vod;
int i_schedule;
vlm_schedule_t **schedule;
};


int vlm_MediaSetup( vlm_t *vlm, vlm_media_t *media, const char *psz_cmd,
const char *psz_value ){

if( (p_input = input_CreateThread2( vlm, &media->item, psz_header
) ) )
{
while( !p_input->b_eof && !p_input->b_error ) msleep( 100000 );

input_StopThread( p_input );
input_DestroyThread( p_input );
vlc_object_detach( p_input );
vlc_object_destroy( p_input );
}
}
3. /src/Input/input.c
input_thread_t *__input_CreateThread2( vlc_object_t *p_parent,
input_item_t *p_item,
char *psz_header )
{
input_thread_t p_input = NULL; / thread descriptor /
p_input = Create( p_parent, p_item, psz_header, VLC_FALSE );
/
Now we can attach our new input */
vlc_object_attach( p_input, p_parent );

/* Create thread and wait for its readiness. */
if( vlc_thread_create( p_input, “input”, Run,
VLC_THREAD_PRIORITY_INPUT, VLC_TRUE ) )
{
msg_Err( p_input, “cannot create input thread” );
vlc_object_detach( p_input );
vlc_object_destroy( p_input );
return NULL;
}
}
static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item,
char *psz_header, vlc_bool_t b_quick )
{
}

/*****************************************************************************

  • Run: main thread loop
  • This is the “normal” thread that spawns the input processing chain,
  • reads the stream, cleans up and waits
    *****************************************************************************/
    static int Run( input_thread_t *p_input )
    {
    }

=====================================================================================
4. \modules\misc\rtsp.c
static vod_media_t *MediaNew( vod_t p_vod, const char psz_name,
input_item_t p_item )
{
vod_sys_t p_sys = p_vod->p_sys;
vod_media_t p_media = malloc( sizeof(vod_media_t) );
int i;
if( !p_media )
{
msg_Err( p_vod, “not enough memory” );
return NULL;
}
memset( p_media, 0, sizeof(vod_media_t) );
p_media->es = 0;
p_media->psz_mux = 0;
p_media->rtsp = 0;
p_media->b_raw = VLC_FALSE;
asprintf( &p_media->psz_rtsp_path, “%s%s”, p_sys->psz_path, psz_name );
p_media->p_rtsp_url =
httpd_UrlNewUnique( p_sys->p_rtsp_host, p_media->psz_rtsp_path, NULL,
NULL, NULL );
if( !p_media->p_rtsp_url )
{
msg_Err( p_vod, “cannot create RTSP url (%s)”, p_media->psz_rtsp_path);
free( p_media->psz_rtsp_path );
free( p_media );
return NULL;
}
msg_Dbg( p_vod, “created RTSP url: %s”, p_media->psz_rtsp_path );
asprintf( &p_media->psz_rtsp_control_v4,
“a=control:rtsp://%%s:%d%s/trackID=%%d\r\n”,
p_sys->i_port, p_media->psz_rtsp_path );
asprintf( &p_media->psz_rtsp_control_v6,
“a=control:rtsp://[%%s]:%d%s/trackID=%%d\r\n”,
p_sys->i_port, p_media->psz_rtsp_path );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_SETUP,
RtspCallback, (void
)p_media );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_DESCRIBE,
RtspCallback, (void
)p_media );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_PLAY,
RtspCallback, (void
)p_media );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_PAUSE,
RtspCallback, (void
)p_media );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_TEARDOWN,
RtspCallback, (void
)p_media );
}
static int RtspCallback( httpd_callback_sys_t *p_args, httpd_client_t *cl,
httpd_message_t *answer, httpd_message_t *query )
{
switch( query->i_type )
{
case HTTPD_MSG_SETUP:
{
}
case HTTPD_MSG_PLAY:
{
p_rtsp = RtspClientGet( p_media, psz_session );
vod_MediaControl( p_vod, p_media, psz_session, VOD_MEDIA_PLAY,
psz_output );

}
}

}
5. vlc_vod.h
static inline int vod_MediaControl( vod_t *p_vod, vod_media_t *p_media,
char *psz_id, int i_query, … )
{
i_result = p_vod->pf_media_control( p_vod->p_data, p_media, psz_id,
i_query, args );
}

libvlc外部api的简单整理

libvlc.h
定义了libvlc的外部api,引用这个头文件就可以把VLC嵌入到我们的程序里面了。

libvlc的对象必须先被初始化之后才能被使用。

libvlc core
libvlc_new() 用于初始化一个libvlc的实例,argc表示参数的个数,argv表示参数,返回创建的实例若当发生错误时返回NULL
libvlc_release()用于销毁一个libvlc的实例
  libvlc error handling
libvlc_errmsg()返回的是在idaoyong线程中产生的最新的libvlc错误,这个错误信息至少在另外一个错误发生之前(至少再调用一次libvlc)都是有效的,当没有任何错误的时候返回的是NULL
libvlc_clearerr()用于清除当前线程的libvlc的错误状态.此操作是可选的,默认情况下,错误状态是会在新的错误发生时被覆盖.
libvlc_vprinterr()用于设置当前线程的libvlc的错误状态和消息.无论何时都返回一个nul字符
libvlc_printerr() /源码里的注释和上一个一模一样
libvlc_retain()增加libvlc的引用计数,任何新的libvlc实例的引用计数为1
libvlc_add_intf():尝试启动libvlc实例的用户接口,p_instance表示要启动的实例,name为接口名,NULL表示默认,返回0表示成功-1表示发生错误
libvlc_set_exits_handler():此函数用于为一个已存在的libvlc事件注册一个回调.此方法在你用libvlc_add_intf()开启了至少一个接口时非常有用.典型的,这个函数将唤醒你的程序主循环(从其他线程).参数p_instance 表示libvlc实例,cb表示当libvlc要退出时要调用的回调.opaque表示回调的数据指针.警告:此函数不能同libvlc_wait()同时调用.
libvlc_wait():等待到有一个接口引发实例的推出动作.必须先用libvlc_add_intf()开启至少一个接口.
libvlc_set_user_agent(): 设置应用程序名,当有协议要求的时候,libvlc将把这个名字作为用户代理串传递给它.参数name应该是一个可读的应用程序名,例如"FooBar player 1.2.3",http参数为HTTP User Agent。例如"FooBar/1.2.3 Python/2.6.0。
libvlc_get_Version():返回libvlc的版本号
libvlc_get_compiler():返回编译libvlc的编译器的版本。
libvlc_get_changeset() :返回libvlc的 changeset?
libvlc asynchronous events
libvlc 发出不同步事件

许多libvlc对象,如libvlc_instance_t libvlc_media_player_t不同步的产生时间,它们中的每一个都提供了libvlc_event_manager_t事件管理器。你可以通过libvlc_event_attach()来订阅这些事件以及用libvlc_event_detach()来退订事件。

libvlc_event_manager_t是属于libvlc对象的事件管理器
libvlc_event_type_t:表示libvlc的事件
libvlc_callback_t():回调函数通知(call back function notification翻译不准确),参数p_event为触发回调的时间.
libvlc_event_attach():注册一个event notification。参数p_event_manager 想要绑定的事件管理器.通常来说它是由vlc_my_object_event_manager()处获得的,此处的my_object是你想要监听的对象,i_event_type是想要监听的事件,f_callback是当i_event_type发生时要调用的函数。user_data是用户提供的伴随事件而传递的数据。成功时此函数返回0,发生错误时返回ENOMEM
libvlc_event_detach()退订一个event notification
libvlc_event_type_name():获得一个事件的类型名

libvlc_log LibVLC logging
libvlc_log系列函数提供了访问libvlc消息日志的方法.这些函数仅用于高级用户或调试之用.

libvlc_get_log_verbosity():获得VLC消息的详细级别
libvlc_set_log_verbosity():设置VLC消息的详细级别
libvlc_log_open():开启VLC消息日志实例(从一个libvlc实例中获得其消息日志实例)
libvlc_log_close():关闭VLC消息日志实例
libvlc_log_count():返回日志中消息条数
libvlc_log_clear():清除日志实例.将把实例中的所有消息删除,为了防止消息阻塞,应该经常清除.
libvlc_log_get_iterator():定位并返回一个日志中记录的iterator
libvlc_log_iterator_free():释放一个先前定位好的iterator
libvlc_log_iterator_next():返回下一条日志消息,当接下来为空的时候返回NULL,否则返回下一个消息对象
libvlc_media.h
libvlc_media_t是一个可播放的媒体的抽象表达.它包含了这个媒体的位置以及各种可选的元数据.

libvlc_state_t:此枚举类型的循序必须严格保证和源码一致,同时可参考mediacontrol_PlayerStatus,input_state_e枚举类型以及VideoLan.LibVLC.State(在bindings/cil/src/media.cs)
libvlc_media_stats_t:Libvlc的媒体统计信息
libvlc_media_track_info_t:没有注释,主要是fourcc和docec的其他信息。
libvlc_media_new_location():使用一个给定的媒体资源路径来建立一个libvlc_media对象.参数psz_mrl为要读取的MRL(Media Resource Location).此函数返回新建的对象或NULL.
libvlc_media_new_path():从本地文件系统路径新建,其他参照上一条
libvlc_media_new_as_node():使用给定的名称创建一个libvlc_media_t并将其作为一个空的节点
libvlc_media_add_option():添加一个选项到已有的libvlc_media_t,这个选项将被用于决定media_player如何读取媒体。这样一来就可以在每个媒体上指定各自的VLC的高级reading/streaming选项。
libvlc_media_add_option_flag():减价一个带有可配置标记的选贤到已有的libvlc_media_t.其他同上一条.
libvlc_media_retain():保留一个引用到一个媒体描述对象(libvlc_media_t.使用libvlc_media_release()来减少一个媒体描述对象的引用计数
libvlc_media_release():减少一个libvlc_media_t的引用计数,如果减少到0时,此此函数将释放此对象(销毁).它将发送一个libvlc_MediaFreed事件到所有的监听者那里。如果一个libvlc_media_t被释放了,它就再也不能使用了。
libvlc_media_get_mrl():从一个媒体描述对象处获得它的mrl
libvlc_media_duplicate():镜像一份媒体描述对象
libvlc_media_get_meta():读取媒体的元数据。如果媒体还没被解析,则返回NULL,这个方法会自动调用libvlc_media_parse_async(),因此,在调用此方法以后,你可以接收到一个libvlc_MediaMetaChanged事件。如果你希望使用一个同步的版本,请确保你在调用get_meta()之前调用了libvlc_media_parse();
libvlc_media_set_meta():设置媒体的元数据,此方法不会保存数据,还需要调用libvlc_media_save_meta()来保存.
libvlc_media_get_state():获取当前媒体描述对象的状态.可能的状态被定义在livblc_structures.c中.
libvlc_media_subitems():获得一个媒体描述对象的子项目.此方法将增加媒体描述对象的引用计数,使用libvlc_media_list_release()减少引用计数.
libvlc_media_event_manager():获得一个媒体描述对象的事件管理器.
libvlc_media_get_duration():获得一个媒体描述对象的持续时间.发生错误时返回-1.
libvlc_media_parse():解析一个本地媒体的元数据和轨道信息,此方法是同步的.
libvlc_media_parse_async():同上,此方法不同步,你可以监听libvlc_MediaParsedChanged事件来追踪他,如果已经被解析过了则此事件不会被触发。
libvlc_media_is_parsed():获得一个媒体描述对象的分析状态。当分析过了返回true。
libvlc_media_set_user_data():设置媒体描述符的用户数据,此数据仅被host程序访问,VLC.framework将它作为一个指向一个引用了一个libvlc_media_t指针的本地对象的指针来使用
libvle_media_get_tracks_info():获得媒体描述符的基本流信息.注意你必须使用–sout="#description"播放媒体恰好一次,否则将得到一个空的数组。而多次播放则会导致多个重复数据。

VLC API集合:http://www.videolan.org/developers/vlc/doc/doxygen/html/group__libvlc.html#_details

H264分层能够分成两层,一层是VCL层(视频编码层),另外一层是NAL层(网络提取层)。

NAL网络提取层:负责以网络所要求的恰当的方式对数据进行打包和传送
VCL视频编码层:包括核心压缩引擎和块,宏块和片的语法级别定义,设计目标是尽可能地独立于网络进行高效的编码
以下重点讲解的是NALU(Network Abstract Layer Unit)的结构

01 NALU结构分析
码流解析的角度
可以理解为有一个一个的NALU单元组成.

一个NALU单元分成两部分: NAL头和RBSP(Raw ByteSequence Payload)原始字节序列载荷.

前面提到的VCL层, 或者说VCL数据,是指视频编码生成的压缩比特流片段,被称为SODB(String of Data Bits)

SODB是RBSP的原始帧, 即RBSP包含了SODB数据

NALU结构图
一个NALU由 固定长度的Header和RBSP组成

02 NALU的头部解析

通过上面我们也可以看到,NALU Header由三个句法元素组成,分别为:

forbidden_zero_bit
nal_ref_idc
nal_unit_type
它们总共占据一个字节,也就 是说,NALU Header,在整个NALU中,占据一个字节。

而且forbidden_zero_bit的值对应1个bit,nal_ref_idc的值对应2个bit,nal_unit_type的值对应5个bit,加起来刚好一个字节。

forbidden_zero_bit(禁止位):h264文档规定,这个值应该为0,当它不为0时,表示网络传输过程中,当前NALU中可能存在错误,解码器可以考虑 不对这个NALU进行解码。
nal_ref_idc:取值0~3,代表当前这个NALU的重要性,取值越大,代表当前NALU越重要,就需要优先被保护。尤其是当前NALU为图像参数集、序列 参数集或IDR图像时,或者为参考图像条带(片/Slice),或者为参考图像的条带数据分割时,nal_ref_idc值肯定不为0。IDR帧,即:及时解码刷新图像,它是一个序列的第一个图像,H.264引入IDR图像是为了解码的重新同步。当解码器解码到IDR图像时,立即将参考帧队列清空,将已解码的数据 全部输出或抛弃,重新查找参数集,开始一个新的序列。这样一来,如果前一个序列发生重大错误,在这里就可以获得重新同步。所以IDR图像之后 的图像,永远不会引用IDR图像之前的图像来解码。并且IDR图像一定是I图像,而I图像不一定是IDR图像(H264里没有图像层,图像可以理解为 帧、片或宏块)。
nal_unit_type:顾名思义,这个应该是最好理解的了,它表示NALU Header后面的RBSP的数据结构的类型 pps NALU(图像参数集): sps NALU(序列参数集): IDR NALU(及时解码刷新图像): 其中,SPS 主要包含的是图像的宽、高、YUV 格式和位深等基本信息;PPS 则主要包含熵编码类型、基础 QP 和最大参考帧数量等基本编码信息。
nal_unit_type解析
nal_unit_type在整个码流中是很重要的一环

我们来仔细看看里面都包含了什么内容

1-4:I/P/B帧,如果nal_ref_idc 为0 则表示I帧,不为0则为P/B帧
5:IDR帧,I帧的一种,告诉解码器,之前依赖的解码参数集合(接下来要出现的SPS\PPS等)可以被刷新了。
6:SEI,英文全称Supplemental Enhancement Information,翻译为“补充增强信息”,提供了向视频码流中加入额外信息的方法。
7:SPS,全称Sequence Paramater Set,翻译为“序列参数集”。SPS中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。
8: PPS,全称Picture Paramater Set,翻译为“图像参数集”。该类型保存了整体图像相关的参数。
9:AU分隔符,AU全称Access Unit,它是一个或者多个NALU的集合,代表了一个完整的帧。

其中SPS,PPS 需要在I帧前出现,不然解码器没法解码

SPS 主要包含的是图像的宽、高、YUV 格式和位深等基本信息;
PPS 则主要包含熵编码类型、基础 QP 和最大参考帧数量等基本编码信息。
如果 没有 SPS、PPS 里面的基础信息,之后的 I 帧、P 帧、B 帧就都没办法进行解码。因此 SPS 和 PPS 是至关重要的。

03 NALU类型区分

00 00 00 01 06 05 SEI数据:是视频的附加增强信息,它包含了一些用户自定义的数据,如时间戳,字幕,弹幕等信息。SEI信息一般放在编码图像之 前,很多时候SEI可以被忽略。

00 00 00 01 67 SPS数据:指的是序列参数集,它保存了一组编码视频序列的全局参数。编码视频序列指的是原始数据经过编码后组成的一系列序号 集。

00 00 00 01 68 PPS数据:指的是图像参数集,主要用于保存图像序列集中一个或者多个独立的图像。一般情况下,配合SPS和PPS都是H264开头的两个 NALU头。

00 00 00 01 65 IDR数据:IDR指的是H264的一帧完整的图像数据,也就是我们经常说的关键帧。

H265的预测

###帧内预测
如第一节所说,由于同一张图片中各个块之间有较强的关联性,且一个CTU块内部的相似度也很高,因此提出了一种帧内预测压缩算法,比如一个图像为

a=[0 2 2 3;
	0 2 1 3;
	0 2 2 2;
	0 1 2 3;]

我们只保留最上面一行的数据【0,2,2,3】,解压缩时,下面几行都直接复制这一行就可以恢复图像

a=[0 2 2 3;
	0 2 2 3;
	0 2 2 3;
	0 2 2 3;]

如图可知,恢复后的图像与原始图像依然有差异,这个差异的成为残差,因此我们不仅要保存帧内压缩的压缩模式(本文只提到了一种),还需要保存残差。DCT变换和量化的意义将在下节介绍,我们对残差也是保存通过DCT变换和量化后的残差。

##帧间估计
###前面也说到了,视频里连续的图片相似度很高,因此H.265/HEVC引入了帧间编码。这个CTU块跟其他哪张图片相似(ref_idx),跟相似图片的具体哪个CTU块相似(mvd),只需要保存ref_idx,和mvd即可,同样的,相似的这个CTU跟当前CTU的差距,依然按残差系数输出## ###预测方式的选择: H265将各种预测模式所造成的图片的失真(ΔD)和保存这些压缩后的信息所消耗的资源(R)进行计算代价函数(ΔJ),最终选择代价函数最小的模式进行预测,并输出其残差;

##DCT变换和量化
###DCT变换
当一个视频,也就是一组图片的一个CTU输入时,预测图像和原始图像存在误差,我们会将这个误差进行dct变换
)

该图是某个图片经过DCT变换后的结果,由图可以看出(图片来源【2】),DCT变换后得到频域矩阵,低频部分幅度很大(左上角低频,右下角高频),而高频部分幅度较低。

3.2、 量化
为了减少存储数据所需要的内存资源。CTU经过DCT变换后,我们再将其进行量化。由于量化步长选取的不一样,造成的精度损失也不一样(参考【3】)。举个例子,如果我们选最小步长是1,向下取整,那么0.6,0.2都将被量化为0,412.6就会被量化为412。可以看到,高频信号由于幅度较小,因此量化后的损失很大,而低频信号由于幅度较大,因此影响较小。毕竟普通人丢了100块钱和富豪丢了100块钱损失是不一样的。

四、环路滤波
🔯由于CTU的处理方式,和高频信号损失的原因,因此我们恢复信号时,还需要增加一个去方块滤波和SAO滤波【4】,来减小预测后的图像和原始图像的差距(即,进一步减小残差)。

五、总结
🅰️H265先选择一种代价函数最小的预测方式(帧内预测,或帧间预测),对图像进行压缩;
🅱️H265通过DCT变换和量化对预测图像和原始图像的差异(残差)进行处理,减小信息量
🆎压缩后的图像,直接恢复的话,和原始图像差距过大,因此需要进行环路滤波缩小这段差距
🅾️环路滤波后,依然存在误差,误差也需要被保留(DCT变换和量化后保留)
简单来说,H265就是通过一系列预测算法对视频进行压缩,再将因此产生的和原始图像的差异(残差,失真)保存。解压缩时,就可以通过反预测,加残差的方式恢复图像。

解码图像 = (预测模式 + 对残差进行量化变换) 后的图片进行环路滤波

H265
1、H265一个图像序列的组成:VPS+SPS+PPS+SEI+一个I帧+若干个P帧。VPS、SPS、PPS、SEI、一个I帧、一个P帧都可以称
为一个NALU。
2、H265的NALU结构:开始码+NALU头+NALU数据
(1)、开始码大小为四个字节,是一个固定值00 00 00 01(十六进制),标识一个NALU的开始。
(2)、NALU头大小为两个字节,共16位,第1位值为0,第2-7位为NALU的type位(共6位),标识当前NALU的类型
,第8-15位值为0,第16位值为1。
(3)、NALU数据为编码器编出来的图像信息或图像数据。
3、六种类型的NALU
(1)、VPS(视频参数集):NALU头值为0x40 01(十六进制),NALU头type位值为32(十进制)。
(2)、SPS(序列参数集):NALU头值为0x42 01(十六进制),NALU头type位值为33(十进制)。
(3)、PPS(图像参数集):NALU头值为0x44 01(十六进制),NALU头type位值为34(十进制)。
(4)、SEI(补充增强信息):NALU头值为0x4e 01(十六进制),NALU头type位值为39(十进制)。
(5)、I帧:NALU头值为0x26 01(十六进制),NALU头type位值为19(十进制)。
(6)、P帧:NALU头值为0x02 01(十六进制),NALU头type位值为1(十进制)。
4、H265的NALU打包成RTP包的模式(下面是用到的两种模式)
(1)、一个NALU打包成一个RTP包,只需要在一个12字节的RTP包头后添加去掉开始码的NALU即可
(这种模式在一个NALU的大小小于MTU时使用)。
(2)、一个NALU打包成几个RTP包(FUs模式),在12个字节的RTP头后面有两个字节的PayloadHdr和一个字节的FU
header。PayloadHdr的值等于NALU头的type位改为49(十进制)后的值,FU header第1位标记RTP包是否为NALU的第一片,第2位标
记RTP包是否为NALU的最后一片。后6位是NALU头的type位。

SDP参考:
一、h265的VPS/SPS/PPS

与分辨率有关
与帧率无关(与帧率是否有关主要看sps中某个字段的值,当前海思编出的sps中该字段值标识与帧率无关)
与N/P制无关

1、4M
Payload: 40010c01ffff016000000300b00000030000030099aa0240
Payload: 420101016000000300b00000030000030099a001402005a1636aa4932f90
Payload: 4401c0f2f03c90

2、4M_4x3
Payload: 40010c01ffff016000000300b00000030000030099aa0240
Payload: 420101016000000300b00000030000030099a001202006c1636aa4932f90
Payload: 4401c0f2f03c90

3、3M
Payload: 40010c01ffff016000000300b00000030000030099aa0240
Payload: 420101016000000300b00000030000030099a00100200601636aa4932f90
Payload: 4401c0f2f03c90

4、1080P
Payload: 40010c01ffff016000000300b0000003000003007baa0240
Payload: 420101016000000300b0000003000003007ba003c08010e58daa924cbe40
Payload: 4401c0f2f03c90

5、960P
Payload: 40010c01ffff016000000300b0000003000003007baa0240
Payload: 420101016000000300b0000003000003007ba00280803c1636aa4932f9
Payload: 4401c0f2f03c90

6、720P
Payload: 40010c01ffff016000000300b0000003000003005daa0240
Payload: 420101016000000300b0000003000003005da00280802d1636aa4932f9
Payload: 4401c0f2f03c90

7、D1
Payload: 40010c01ffff016000000300b0000003000003005aaa0240
Payload: 420101016000000300b0000003000003005aa00582009058daa924cbe4
Payload: 4401c0f2f03c90

8、CIF
Payload: 40010c01ffff016000000300b0000003000003003caa0240
Payload: 420101016000000300b0000003000003003ca00b0804858daa924cbe40
Payload: 4401c0f2f03c90

H265,正式名称为高效视频编码,英文全称为High Efficiency Video Coding(HEVC),是国际电信联盟视频编码专家组和国际标准化组织/国际电工委员会动态图像专家组共同开发的下一代视频编码标准。作为H264/MPEG-4 AVC的继任者,H265旨在提供更高的视频压缩效率,能够在保持相同视频质量的前提下大幅度减少视频文件的大小,或者在相同的比特率下提供显著提升的图像质量。当使用RTP传输H265视频流时,也需要遵循一定的打包和传输规则。

H265 NALU
H265 NALU是H265编码视频流的基本数据单元,用于承载编码后的视频数据,并提供网络传输的抽象。NALU的设计旨在使视频编码与底层网络传输协议分离,使得H265编码的视频内容能够适应各种网络环境和应用需求。

    每个NALU都以一个固定长度的NAL Unit Header开始,NAL Unit Header占用两个字节,通常包含以下几个字段。

    Forbidden Zero Bit (F): 占1位,这一位必须为0。如果为1,则表示语法错误,整个NALU将被丢弃。

    NALU Type (Type): 占6位,定义了NALU所携带数据的类型。总共有64种可能的类型(范围是0-63),其中0-31是VCL(视频编码层)NAL单元,用于携带编码的视频数据;而32-63是非VCL NAL单元,用于携带控制信息或元数据。不同的NALU Type对应着不同的编码数据或控制信息,比如:P帧和B帧为1,IDR帧为19,VPS(Video Parameter Set)为32,SPS(Sequence Parameter Set)为33,PPS(Picture Parameter Set)为34,SEI(Supplemental Enhancement Information)为39等。

    LayerId: 占6位,用于表示NAL所在的Access Unit所属的层,是为了HEVC的继续扩展而设置的。在当前的HEVC标准中,这个字段通常被设置为0,但在未来的扩展中可能会用到。

    TID: 占3位,用于指定NAL单元的时间标识符,一般取值为1。它帮助解码器确定NAL单元在视频流中的时间位置,从而正确解码和播放视频。

    紧跟在NAL Unit Header之后的是NAL Unit Payload,包含了编码视频流的核心数据和辅助信息,是视频解码和播放的基础。在实际的网络传输和存储中,NALU通常还需要进一步封装成以下格式中的一种。

    Annex B格式:在Annex B格式中,每个NALU之前添加一个Start Code Prefix,可以是0x000001或0x00000001,用于标识NALU的起始位置。相邻NALU之间,以此方式明确分隔。

    AVCC (Advanced Video Coding Container) 格式:AVCC格式常见于MP4容器中,NALU不再使用Start Code Prefix,而是通过Length字段来标识每个NALU的长度。SPS和PPS等参数以NALU形式封装,并在MP4文件的hvcC盒(Box)中以字节串的形式存储。

封装方法
H265 NALU在封装到 RTP包中时,需要遵循一定的规则和流程,以确保数据能够被正确地传输、接收和解码。根据NALU的大小和传输需求,可以选择以下三种常见的封装方法。

    1、单NALU封装。对于小型的NALU,(比如:P帧、B帧),可以直接将整个NALU放入一个RTP包的Payload中,无需额外处理。此时,RTP包的结构如下。

±----------------------------+
| RTP Header (12 Byte) |
| NALU Header (2 Byte) |
| NALU Data … |
±----------------------------+
2、FU-A分包。对于大型NALU(比如:某些关键帧),如果其大小超过了RTP包的最大有效载荷MTU,可以使用Fragmentation Unit A方式进行分片。原始NALU会被拆分成多个片段,每个片段作为一个独立的RTP包发送。此时,RTP包的结构如下。

±----------------------------+
| RTP Header (12 Byte) |
| FU Indicator (2 Byte) |
| FU Header (1 Byte) |
| Fragmented NALU Data … |
±----------------------------+
可以看到,FU-A分包在12个字节的RTP Header后,有三个字节的分包头,分别为:FU Indicator和FU Header。

    FU Indicator占用两个字节,由以下部分组成。

    F (1 bit): 禁止位,与NALU Header的F位一致。

    Type (6 bits): 分包类型,二进制固定为110001(对应十进制的49),表示FU-A类型。

    LayerId (6 bits): 与NALU Header的LayerId一致。

    TID (3 bits): 与NALU Header的TID一致。

    FU Header占用一个字节,由以下部分组成。

    S (1 bit): 分包起始位。如果该FU是原始NALU的第一个片段,S设为1。否则,设为0。

    E (1 bit): 分包结束位。如果该FU是原始NALU的最后一个片段,E设为1。否则,设为0。

    Type (6 bits): 原始NALU类型,与NALU Header的Type一致,用于在重组时恢复原始NALU Header。

    3、STAP-A聚合

    对于多个小尺寸NALU,如果它们具有相近的解码时间戳,且合并后总尺寸仍小于MTU,可以使用Single-Time Aggregation Packet A方式将多个NALU合并到一个RTP包中。此时,RTP包的结构如下。

±----------------------------+
| RTP Header (12 Byte) |
| STAP-A Header (2 Byte) |
| NALU Payload1 Size (2 Byte) |
| NALU Payload1 |
| NALU Payload2 Size (2 Byte) |
| NALU Payload2 |
| … |
±----------------------------+
STAP-A Header紧跟在RTP Header之后,占用两个字节(与NALU Header结构类似),用于标识这是一个STAP-A包,其Type值固定为48。在每个聚合的NALU前,会有一个长度字段(通常为2个字节),表明后续NALU数据的长度。所有聚合在STAP-A包中的NALU都共享相同的时间戳,这是STAP-A包的一个重要特征。

    注意:无论采用上面的哪种封装方法,NALU Data或NALU Payload中都不包括Annex B格式中的起始码(比如:0x000001或0x00000001),因为RTP包已经提供了足够的信息来标识NALU的边界。

FU-A分包及重组
在服务端,FU-A分包的大致步骤如下。

    1、原NALU切割: 大型NALU被拆分成多个连续的片段。切割位置通常选择在NALU内部的编码块边界,以避免破坏编码结构。

    2、片段标识: 每个片段(FU)在RTP Payload中添加一个FU Header,用于标识该片段属于哪个原始NALU,以及其在原始NALU中的位置。

    3、独立传输: 每个FU作为一个独立的RTP包发送,每个RTP包的Payload仅包含一个FU。

    客户端接收到FU-A分包的RTP包后,根据RTP Header解析出Payload Type,确认为H265 FU-A数据后,按照以下步骤处理。

    1、FU分包头解析: 提取FU Indicator和FU Header中的信息。

    2、片段重组: 将收到的FU片段按照RTP包的Sequence Number顺序重新组合,将所有片段的Fragmented NALU Data拼接在一起。

    3、NALU还原: 在重组后的NALU数据前添加原始NALU Header(根据FU分包头中的信息恢复),形成完整的NALU结构。

    4、解码处理: 将还原后的完整NALU提交给H265解码器进行解码。

    使用FU-A封装方法进行分包和重组时,有以下几点需要特别注意。

    1、顺序传输: FU-A分包的RTP包必须严格按照分包顺序发送和接收,以确保正确重组。

    2、丢包处理: 如果中间某个FU片段丢失,可能导致原始NALU无法正确重组。接收端可以根据RTP包的序列号和确认机制检测丢包,并尝试通过重传请求(比如:RTCP的NACK)恢复丢失片段。

    3、时间戳同步: 所有FU片段共享同一个解码时间戳,确保解码时的正确同步
    H265 nalu head格式 000000 01 nalu type 01 content

例如:(具体nalu type值对应的类型可以上网查一下)

00 00 00 01 40 01 的nuh_unit_type的值为 32, 语义为视频参数集 VPS
00 00 00 01 42 01 的nuh_unit_type的值为 33, 语义为序列参数集 SPS
00 00 00 01 44 01 的nuh_unit_type的值为 34, 语义为图像参数集 PPS
00 00 00 01 4E 01 的nuh_unit_type的值为 39, 语义为补充增强信息 SEI
00 00 00 01 26 01 的nuh_unit_type的值为 19, 语义为可能有RADL图像的IDR图像的SS编码数据 IDR
00 00 00 01 02 01 的nuh_unit_type的值为1, 语义为被参考的后置图像,且非TSA、非STSA的SS编码数据

哥伦布编码:

1.指数哥伦布编码定义。
指数哥伦布码(指数Golomb码),一种压缩编码方法。
用来表示非负整数的k阶指数哥伦布码可用如下步骤生成:
将数字以二进制形式写出,去掉最低的k个比特位,之后加1
计算留下的比特数,将此数减一,即是需要增加的前导零个数
将第一步中去掉的最低k个比特位补回比特串尾部
0阶指数哥伦布码如下所示:
0 => 1 => 1
1 => 10 => 010
2 => 11 => 011
3 => 100 => 00100
4 => 101 => 00101
5 => 110 => 00110
6 => 111 => 00111
7 => 1000 => 0001000
8 => 1001 => 0001001

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
VLC 3.0.16是一款功能强大的、开源的多媒体播放器。下面是关于如何使用VLC 3.0.16的简要说明: 1. 下载和安装VLC 3.0.16:可以从VLC官方网站(https://www.videolan.org/vlc/index.zh.html)下载相应的安装程序,并按照提示进行安装。 2. 打开VLC 3.0.16:安装完成后,在计算机上找到VLC的图标,并双击打开它。 3. 添加媒体文件:点击界面上方的"媒体",然后选择"打开文件"或"打开文件夹",浏览并选择你想播放的媒体文件。 4. 观看视频或听歌曲:一旦你选择了媒体文件,它将开始播放。使用控制栏上的按钮来控制播放进程,如播放、暂停、快进、快退等。你还可以使用键盘快捷键来进行操作,比如空格键(暂停/播放)和左右箭头键(快进/快退)。 5. 调整音频和视频设置:点击"工具",然后选择"偏好设置",你可以在“音频”和“视频”标签下调整相关设置,如音量、音轨、字幕、屏幕比例等。 6. 添加和管理播放列表:点击"视图",然后选择"播放列表",你可以在这里添加和管理播放列表。只需点击右上角的"+"按钮来添加媒体文件或文件夹。 7. 使用其他特性:VLC还有许多其他强大的功能,比如截图、循环播放、屏幕录制等。你可以在"工具"菜单中查找并使用它们。 总之,使用VLC 3.0.16是非常简单的。只需下载、安装,然后添加并播放你喜欢的媒体文件即可。同时,VLC还提供了众多的高级功能,如调整音频和视频设置、管理播放列表等,使你能够根据个人喜好进行个性化设置和定制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_44245323

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值