视频的显示同步
八月 31st, 2008
视频的同步通过视频流的包里的dts(decoding time stamp)和pts ( presentation time stamp )
信息来实现。
视频有三种帧。I(intra)帧为静态图像可以直接解码。P(predicted)帧基于之前的帧通过运动估计形成的图像和实际图像的残差,解码需依赖之前的帧。B(bidirectional)帧基于之前和之后的帧运动估计得到的残差,解码依赖之前和之后的帧。
如果有一段电影由I B P P帧的顺序组成。第一帧I帧可以直接解码,但第二和第三帧是B帧依赖于第四帧P帧的信息才能解码,为了方便解码在编码的时候就直接以I P B B的顺序存储了。所以我们获取的AVStream的帧顺序是I P B B,这是DTS即解码的顺序,但这样用这个顺序来显示显然是不行的,好在每个帧都有PTS来表明显示顺序,它会告诉我们先缓存几帧用I B B P的顺序来显示。
Zoundry Raven
八月 31st, 2008
Zoundry新版本换了一个名字,有portable模式,支持代理,界面也更人性化了。
通过HTTP代理使用SVN
八月 31st, 2008
修改%APPDATA%\Subversion目录下的servers文件。
command line版的svn http://www.sliksvn.com/en/download
DPKG就是Debian
八月 24th, 2008
Linux众多发行版的本质区别是什么?文件系统,内存管理,进程调度这些操作系统最核心的功能都在内核包里了,所有的发行版都是一样的。区别都在应用层面了,主要就是提供的二进制软件包的丰富程度,和安装便利性,如此来说,dpkg这个Debian系列特有的软件安装方式就是Debian的核心了,不知道这样理解是不是正确。
Linux确实需要统一一下,虽说参差多态才是生活的本源,但程序在二进制的层面的不兼容的确让人很困惑,就算提供源码一般人也是不会愿意configure ,make这样安装程序吧,而且大一点的程序动辄几个小时的编译时间也超过了方便与否的范畴。
Mplayer笔记
八月 24th, 2008
AVI是微软提出的Container,因为在文件大小和对先进编码器的支持上存在缺陷,慢慢开始被遗弃,但目前仍是一种非常普遍的格式。微软准备用ASF/WMV来替代他。但更被看好的是另一种开放的Container标准MKV,http://en.wikipedia.org/wiki/Matroska。当然rmvb是Real Media公司的Container。有一个叫MediaInfo的软件可以查看Container中的具体信息。
因此要播放视频文件的第一步就是把文件中包含的视频音频分离出来(demux),然后才能解码。Matroska Muxer等工具可以用来将多个音频轨道、视频轨道、字幕等合并成一个单独的Container文件。与之对应的分离出音频视频的工作是由分离器 Splitter来做,如Haali Media Splitter ,而Mplayer则由一堆Demux_*文件来处理各种不同的Container。Mencoder.exe用Muxer*文件来生成不同的 Container。
http://www.ogg.cn/
http://www.matroska.org/downloads/windows.html
Mplayer的内置解码器一般在vd_*文件中定义,只能被Mplayer使用,外置解码器是通过在Windows注册表登记,而可被所有需要视频解码功能的程序使用的解码器,如用regsvr32 /s ffdshow.ax注册后,系统内置的Media Player也可以通过调用ffdshow支持大部分视频格式了。DirectX SDK中的GraphEdit可用来查看系统中注册的解码器。
解码后的视频通过VO输出,Windows推荐directx,Linux推荐xv。可用mplayer -vo help查看支持的vo。
在vo输出之前,可以设置各种滤镜(filter)来操作视频,如scale滤镜可用来放大缩小视频,screenshot用来截屏。在mplayer的源码中实现这种功能的文件用vf_开头。mplayer甚至可以直接把解码后的yuv文件写到磁盘。
MP另外一个比较有用的功能是Software Volume adjustment,即用软件的方式增大音量,这用来处理那些音量调到最大还不能听清的影片比较有用。
mplayer quiet.avi -softvol -softvol-max 300 3倍音量。
Windows下编译MPlayer可用mingw。
http://oss.netfarm.it/mplayer-win32.php
http://forum.doom9.org/
写一个使用ffmpeg动态链接库的vc程序
八月 24th, 2008
在MingG下使用./configure –enable-shared –enable-memalign-hack选项编译ffmpeg,并make install。
新建一个vc控制台项目,取消 “Precompiled headers” 选项。
ALT+F7呼出项目属性面板,把c:\msys\1.0\local\include以下的目录加入到include路径。
下载http://code.google.com/p/msinttypes/downloads/list放到vc的include目录,使vc兼容c99语法。
同样因为VC和GCC的兼容问题会一些出现错误。
在合适的地方加上
#define inline _inline
#define snprintf _snprintf_s
把c:\msys\1.0\local\bin加到lib路径。
并把avcodec.lib avformat.lib avutil.lib avdevice.lib加到Additional Dependencise.
删了main文件,用ffmpeg里的apiexample.c或output_example.c代替。 c->time_base= (AVRational){1,25};把这句改为下面的形式
c->time_base.num = 1;
c->time_base.den = 25;
F5生成程序,运行的时候把提示缺少的dll从c:\msys\1.0\local\bin目录拷过来。
VC++2008下编译通过。
http://ffmpeg.mplayerhq.hu/general.html#SEC11
http://arrozcru.no-ip.org/ffmpeg_wiki/tiki-index.php?page=Static
Mplayer代码阅读
八月 21st, 2008
从Mplayer.c的main开始
处理参数
mconfig = m_config_new();
m_config_register_options(mconfig,mplayer_opts);
// TODO : add something to let modules register their options
mp_input_register_options(mconfig);
parse_cfgfiles(mconfig);
初始化mpctx结构体,mpctx应该是mplayer context的意思,顾名思义是一个统筹全局的变量。
static MPContext *mpctx = &mpctx_s;
// Not all functions in mplayer.c take the context as an argument yet
static MPContext mpctx_s = {
.osd_function = OSD_PLAY,
.begin_skip = MP_NOPTS_VALUE,
.play_tree_step = 1,
.global_sub_pos = -1,
.set_of_sub_pos = -1,
.file_format = DEMUXER_TYPE_UNKNOWN,
.loop_times = -1,
#ifdef HAS_DVBIN_SUPPORT
.last_dvb_step = 1,
#endif
};
原型
typedef struct MPContext {
int osd_show_percentage;
int osd_function;
ao_functions_t *audio_out;
play_tree_t *playtree;
play_tree_iter_t *playtree_iter;
int eof;
int play_tree_step;
int loop_times;
stream_t *stream;
demuxer_t *demuxer;
sh_audio_t *sh_audio;
sh_video_t *sh_video;
demux_stream_t *d_audio;
demux_stream_t *d_video;
demux_stream_t *d_sub;
mixer_t mixer;
vo_functions_t *video_out;
// Frames buffered in the vo ready to flip. Currently always 0 or 1.
// This is really a vo variable but currently there’s no suitable vo
// struct.
int num_buffered_frames;
// AV sync: the next frame should be shown when the audio out has this
// much (in seconds) buffered data left. Increased when more data is
// written to the ao, decreased when moving to the next frame.
// In the audio-only case used as a timer since the last seek
// by the audio CPU usage meter.
double delay;
float begin_skip; ///< start time of the current skip while on edlout mode
// audio is muted if either EDL or user activates mute
short edl_muted; ///< Stores whether EDL is currently in muted mode.
short user_muted; ///< Stores whether user wanted muted mode.
int global_sub_size; // this encompasses all subtitle sources
int global_sub_pos; // this encompasses all subtitle sources
int set_of_sub_pos;
int set_of_sub_size;
int global_sub_indices[SUB_SOURCES];
一些GUI相关的操作
打开字幕流
打开音视频流
mpctx->stream=open_stream(filename,0,&mpctx->file_format);
fileformat文件还是TV流DEMUXER_TYPE_PLAYLIST或DEMUXER_TYPE_UNKNOWN DEMUXER_TYPE_TV
current_module记录状态vobsub open_stream handle_playlist dumpstream
stream_reset(mpctx->stream);
stream_seek(mpctx->stream,mpctx->stream->start_pos);
f=fopen(stream_dump_name,”wb”); dump文件流
stream->type==STREAMTYPE_DVD
//============ Open DEMUXERS — DETECT file type ======================
Demux。分离视频流和音频流
mpctx->demuxer=demux_open(mpctx->stream,mpctx->file_format,audio_id,video_id,dvdsub_id,filename);
Demux过程
demux_open
get_demuxer_type_from_name
……
mpctx->d_audio=mpctx->demuxer->audio;
mpctx->d_video=mpctx->demuxer->video;
mpctx->d_sub=mpctx->demuxer->sub;
mpctx->sh_audio=mpctx->d_audio->sh;
mpctx->sh_video=mpctx->d_video->sh;
分离了之后就开始分别Play audio和video
这里只关心play video
/*========================== PLAY VIDEO ============================*/
vo_pts=mpctx->sh_video->timer*90000.0;
vo_fps=mpctx->sh_video->fps;
if (!mpctx->num_buffered_frames) {
double frame_time = update_video(&blit_frame);
mp_dbg(MSGT_AVSYNC,MSGL_DBG2,”*** ftime=%5.3f ***\n”,frame_time);
if (mpctx->sh_video->vf_inited < 0) {
mp_msg(MSGT_CPLAYER,MSGL_FATAL, MSGTR_NotInitializeVOPorVO);
mpctx->eof = 1; goto goto_next_file;
}
if (frame_time < 0)
mpctx->eof = 1;
else {
// might return with !eof && !blit_frame if !correct_pts
mpctx->num_buffered_frames += blit_frame;
time_frame += frame_time / playback_speed; // for nosound
}
}
关键的函数是update_video
根据pts是否正确调整一下同步并在必要的时候丢帧处理。
最终调用decode_video开始解码(包括generate_video_frame里)。
mpi = mpvdec->decode(sh_video, start, in_size, drop_frame);
mpvdec是在main里通过reinit_video_chain的一系列调用动态选定的解码程序。
其实就一结构体。它的原型是
typedef struct vd_functions_s
{
vd_info_t *info;
int (*init)(sh_video_t *sh);
void (*uninit)(sh_video_t *sh);
int (*control)(sh_video_t *sh,int cmd,void* arg, …);
mp_image_t* (*decode)(sh_video_t *sh,void* data,int len,int flags);
} vd_functions_t;
这是所有解码器必须实现的接口。
int (*init)(sh_video_t *sh);是一个名为init的指针,指向一个接受sh_video_t *类型参数,并返回int类型值的函数地址。
那些vd_开头的文件都是解码相关的。随便打开一个vd文件以上几个函数和info变量肯定都包含了。
mpi被mplayer用来存储解码后的图像。在mp_image.h里定义。
typedef struct mp_image_s {
unsigned short flags;
unsigned char type;
unsigned char bpp; // bits/pixel. NOT depth! for RGB it will be n*8
unsigned int imgfmt;
int width,height; // stored dimensions
int x,y,w,h; // visible dimensions
unsigned char* planes[MP_MAX_PLANES];
int stride[MP_MAX_PLANES];
char * qscale;
int qstride;
int pict_type; // 0->unknown, 1->I, 2->P, 3->B
int fields;
int qscale_type; // 0->mpeg1/4/h263, 1->mpeg2
int num_planes;
/* these are only used by planar formats Y,U(Cb),V(Cr) */
int chroma_width;
int chroma_height;
int chroma_x_shift; // horizontal
int chroma_y_shift; // vertical
/* for private use by filter or vo driver (to store buffer id or dmpi) */
void* priv;
} mp_image_t;
图像在解码以后会输出到显示器,mplayer本来就是一个视频播放器么。但也有可能作为输入提供给编码器进行二次编码,MP附带的mencoder.exe就是专门用来编码的。在这之前可以定义filter对图像进行处理,以实现各种效果。所有以vf_开头的文件,都是这样的filter。
图像的显示是通过vo,即video out来实现的。解码器只负责把解码完成的帧传给vo,怎样显示就不用管了。这也是平台相关性最大的部分,单独分出来的好处是不言而喻的,像在Windows下有通过direcx实现的vo,Linux下有输出到X的vo。vo_*文件是各种不同的vo实现,只是他们不都是以显示为目的,像vo_md5sum.c只是计算一下图像的md5值。
在解码完成以后,即得到mpi以后,filter_video被调用,其结果是整个filter链上的所有filter都被调用了一遍,包括最后的VO,在vo的put_image里把图像输出到显示器。这个时候需要考虑的是图像存储的方法即用哪种色彩空间。
[MPlayer core]
| (1)
_____V______ (2) /~~~~~~~~~~\ (3,4) |~~~~~~|
| | —–> | vd_XXX.c | ——-> | vd.c |
| decvideo | \__________/ <-(3a)– |______|
| | —–, ,………….(3a,4a)…..:
~~~~~~~~~~~~ (6) V V
/~~~~~~~~\ /~~~~~~~~\ (8)
| vf_X.c | –> | vf_Y.c | —-> vf_vo.c / ve_XXX.c
\________/ \________/
| ^
(7) | |~~~~~~| : (7a)
`-> | vf.c |…:
|______|
感觉Mplayer的开发人员们都是无比的牛,硬是用原始的C实现了很多OO语言才支持的特性,带来不好的结果是代码看起来比较费劲,记下来慢慢看先。
gvim as IDE
八月 16th, 2008
Linux下大部分项目代码是用makefile组织的,一些用MingW或Cygwin等工具移植到Windows的项目也保持了这种一致,只有一个configure后自动生成的MakeFile文件来组织整个项目的结构,这种方式虽然简洁高效并且通用性好,但显然没有VS提供的sln管理方式来的方便。不管怎么样,既然不能用vs干脆就用vim了。
早听说vim+Cscope很强大,正好试一下。
首先理清一下概念。Cscope是一个独立的代码分析工具,除了vim也可以被emace和其他软件调用。Vim的-enable-cscope编译选项,只是内置提供了调用cscope的接口,cscope程序还是要单独下载的。
阅读源码需要的最基本功能有快速跳转到函数定义的位置,查看函数调用了哪些函数,并被哪些函数调用等,cscope通过预分析代码,建立一个存储这些关系的数据库来实现需要的功能。
下载http://cscope.sourceforge.net/cscope_maps.vim放到vim到plugin目录,这个文件定义了一些快捷键,根据需要自己修改。
下载Cscope的win32版http://iamphet.nm.ru/cscope/index.html,放到gvim.exe所在的文件夹,方便起见把gvim的目录加到系统Path路径里,然后开一个cmd切换到要阅读的源码目录,运行cscope -Rbq它会在源码目录下生成cscope.out等几个文件。然后用vim打开代码文件,就可以用cscope了。
在Vim命令模式下用cs add d:\src\cscope.out连接之前生成的数据库,vim会自动加载当前目录下的cscope.out文件(cscope_maps.vim里定义的),而且之后搜索定位文件都是通过相对路径,所以直接在源码顶层目录打开一个文件开始浏览代码会方便一点。
生成数据库时用的参数
- -R: 在生成索引文件时,搜索子目录树中的代码
- -b: 只生成索引文件,不进入cscope的界面
- -q: 生成cscope.in.out和cscope.po.out文件,加快cscope的索引速度
- -k: 在生成索引文件时,不搜索/usr/include目录
- -i: 如果保存文件列表的文件名不是cscope.files时,需要加此选项告诉cscope到哪儿去找源文件列表。可以使用“-”,表示由标准输入获得文件列表。
- -I dir: 在-I选项指出的目录中查找头文件
- -u: 扫描所有文件,重新生成交叉索引文件
- -C: 在搜索时忽略大小写
- -P path: 在以相对路径表示的文件前加上的path,这样,你不用切换到你数据库文件所在的目录也可以使用它了。
浏览代码时用的参数
- s: 查找C语言符号,即查找函数名、宏、枚举值等出现的地方
- g: 查找函数、宏、枚举等定义的位置,类似ctags所提供的功能
- d: 查找本函数调用的函数
- c: 查找调用本函数的函数
- t: 查找指定的字符串
- e: 查找egrep模式,相当于egrep功能,但查找速度快多了
- f: 查找并打开文件,类似vim的find功能
- i: 查找包含本文件的文件
浏览代码时先用cs show看一下有没有连上cscope数据库。
想查找foo2函数在那里定义,可以在命令模式下,输入:cs f s foo2,也可以在Normal模式下,把光标停在函数名上,输入Ctrl+\,再按s键,这也是在cscope_maps.vim里定义的,果然要方便一点,但要是能弄成一种CScope模式就好了,每次都要先输入Ctrl+\也很麻烦。
官网http://cscope.sourceforge.net/
另一种组合是Ctags+taglist。
ctags在功能上比cscope要弱一点,但taglist只支持ctags生成的数据库,taglist是Vim上下载量最大的插件,它提供一个额外的窗口显示文件,变量,函数等信息,这样Vim看起来就更像一个IDE了。
ctags http://ctags.sourceforge.net/
taglist http://www.vim.org/scripts/script.php?script_id=273
按照说明把下载的文件放到相应目录后重启gVim,没有出现问题的话这时在命令模式下输入Tlist就可以激活taglist的窗口,然后用TlistAddFile或TlistAddFilesRecursivei打开其他文件,浏览时ctrl+]查看函数的定义, ctrl+o返回原来位置。
下面是我的gvim截图
windows XP/2003下的UAC (续)
八月 1st, 2008
在sf找到两个致力于低权限帐号增强安全性开源的项目,
http://sourceforge.net/projects/sudowin
http://sudown.sourceforge.net/
虽然在我的windows 2003 SD上装了都不能使用。
简单的看了一下代码,sudown是VC2005写的,一共三个项目,主要的工作的sudown做的,其他两个都是在调用sudown提供的功能。它的原理是,在普通用户登录系统以后,再把它加入到管理员组中,这个时候explorer等程序已经在运行了,而系统进程winlogon在创建他们的时候,用户还没有管理员权限,所以他们都是在以低权限运行,而你在explorer中双击运行的程序,也会继承这个低权限的,虽然你的帐号有管理员权限。然后当你logoff到时候,在把你的帐号从管理员组中移除。也就是说,你只在登录以后是管理员,其他时候都是一个普通帐号,比较tricky。具体是用到了Winlogon Notify来截获系统登录退出的消息。
sudowin是用C#写的,需要.net 2.0,用vs打开一看居然有14个项目,一个个看显然不太现实,想了一下C#做这种API密集型事情肯定会大量用到DllImport,就把它当关键字搜索整个Solution,很快定位到一个叫Server项目下的Win32.cs文件。这里对大量的API进行了包装,其中比较引人注目的是LogonUser和CreateProcessAsUse这两个函数。MSDN一下就可以发现这两个函数正是实现runas需要的。LogonUser用指定的帐号密码登录,获取一个代表用户的token,再用这个token调用CreateProcessAsUse创建对应权限的进程。
事实上反汇编一下ruanas.exe可以发现,在它的Imports表里面可以看到一个叫CreateProcessAsUserW的函数,MSDN对其的说明是:
The CreateProcessWithLogonW and CreateProcessWithTokenW functions are similar to the CreateProcessAsUser function, except that the caller does not need to call the LogonUser function to authenticate the user and get a token.
http://msdn.microsoft.com/en-us/library/ms682431(VS.85).aspx
猜测sudowin就是runas的另一个实现,因为没有用过也不知道它做了哪些改善。
因为这两个程序都没有在我的系统上正常工作,所以还是觉得我之前的那个想法更好一点。只是很沮丧的发现runas有一个/env选项,可以保留当前用户环境进行操作,让我关于重用用户profile的探索显得很多余。
附上一个关于Least-privileged User Account 的网站。
http://nonadmin.editme.com/