最新日志

发表于:2008-7-24 17:00:03
标签:无标签

1

视频解码优化[转]

视频解码优化

以下通过剖析一些经验来了解视频解码优化
1  在嵌入式系统中实现MPEG4的视频解码
有两种方法可行
(1)采用ffmpeg(mplayer 的核心就是采用ffmpeg),然后对ffmpeg mp4解码优化

1)对IDCT汇编化,并优化VLD的实现   ->inline&汇编化
2)根据ARM9 cache & cache line的大小做MB的分组,使得每次可以同时处理多个MB
  即 对多个MB在一个循环内做VLD--->IDCT-->MC--.......  ->耦合
3)优化关键代码段的内存访问(MC)    ->inline&汇编化
4)不要使用FFmpeg内置的img_convert()做yuv2rgb转换  ->inline&汇编化
5)对解码库做ARM指令集优化    ->体系结构优化
  configured ffmpeg with cpu = ARMV4L would give you a better performance
  If you have IPP,you can enable it, you can obtain huge enhancement
  IPP="Intel"? Integrated Performance Primitives intel高性能构件库 (only for xScale)
 
(2)用xvid来做,ffmpeg包含的解码库太多,如果你只做MPPEG-4解码,何必用这么复杂的库?
btw,在嵌入式系统中最好用0.9.2版的xvid。
因为1.1.0的版本包含了很多AS的特性,通常在嵌入式系统中都不需要用,并且也不容易实现。
要自己做编码算法的话,不能总想依赖别人,最好还是需要自己花
功夫去实现和优化。因此我觉得从实际出发的话,XVID0.9.2版的比1.1.0的好。
实际上,通常在主频400MHz的平台上,要优化XVID的算法达到CIF实时解码也还是很容易的,最多就一个多月
2  视频解码流程对解码带来的影响
视频解码优化一般 代码量大,而且源代码往往是从其他地方获取得到的,所以阅读比较困难,更别说优化了,最近在优化realVideo,有几点心得:
1)阅读代码前必须先熟悉流程,抓住关键的点,比如视频解码不外乎熵解码,反量化,反变换,插值,重建,滤波,参考帧插入等。
把握住这几个点,可以将代码很快分离出来。
2)分析解码流程,了解解码需要的最小buffer是多大,各个buffer 的位宽多大。
3)根据已经知道的流程,跟踪代码buffer流向,是否存在多余的内存拷贝。想办法将buffer减少,经验说明,减少buffer带来的速度上的提升远大于局部算法的优化。 ->耦合
4)观察程序结构顺序是否合理,不合理的程序结构会导致buffer增大。
   这两天研究视频解码顺序,发现先插值后做反变换要比先做反变换再做插值效率要高许多,原因是插值后的位宽是8bits,而往往反变换后是9bits,所以在重建之前要保存插值后的值要比保存反变换后的值要省一半的空间,这样在重建时访问的内存就少很多了。据我了解,大部分高效率的解码器都是先插值再反变换,而且变换后马上做重建,这样既减少内存使用,也避免内存访问抖动太厉害,最终减少缓存不命中。 ->修改解码流程 耦合
3   cache机制对解码带来的影响
先看 http://www.hongen.com/pc/diy/know/mantan/cache0.htm
写透(直写式)和写回(回写式)有着截然不同的操作,在不同的场合,不同的内存块使用不同的回写策略(如果你的系统可以实现的话)要比使用一种策略要高效得多。具体一点,对于反复存取的内存块置成写回,而把一次写入而很长时间以后再使用的内存置为写透,可以大大提高cache的效率。
第一点很容易理解,第二点就需要琢磨一下了,由于写透的操作是,当缓存有该地址的数据时同时更新缓存和主存,当缓存没有该地址数据直接写主存,忽略缓存。当该地址的数据很长时间后才被使用到,那么在使用的时候该数据肯定不在cache中(被替换了),所以不如直接写入主存来得直接;
相反,如果使用写回操作,当 cache中有该地址数据,需要更新该数据,设置dirty位,很长时间后再使用该数据或被替换的时候才将其刷进主存,这有占了茅坑不拉屎的嫌疑;而当 cache没有该地址数据时,情况更糟糕,首先需要将相应的主存数据(一个cache line)导入cache,再更新数据,设置dirty位,再等待被刷回内存,这种情况不仅占用了cache的空间,还多一次从主存中导入数据的过程,同样占据总线,开销很大。至于为什么要先从主存中导入数据,是因为cache往主存回写数据时是按照一个cache line 单位来写的,但被更新的数据可能没有一个cache line这么多,所以为了保证数据一致性,必须先把数据导入cache,更新后再刷回来。
对于很多视频解码来说,帧写入过程是一个一次性的动作,只有在下一次作为参考帧时才会被使用到,所以帧缓冲内存可以设置为写透操作,而下一次使用它的时候很可能是作为参考帧来使用,而作为参考帧不需要反复的存取,只需一次读操作就可以了,所以效率并不会因为不经过cache而降低。实验证明该方法可以使 mpeg4 sp解码提高20-30%的效率。
相似的内容cache操作的小技巧还有prefetch操作,prefetch操作是将主存的数据导入cache而期间cpu不需要等待,继续下一条指令的执行,如果下一条指令也是总线的操作,那么就必须等待prefetch完成以后再开始。所以,在使用该指令时,在prefetch指令后面插入尽可能大于一次缓存不命中所需要的clock数对应的指令,那么prefetch与其后面的指令可以并行执行,从而省去了等待的过程,相当于抵消缓存不命中的损失。当然,如果插入的指令太多而cache太小,有可能prefetch的数据进入cache 后又被替换掉了,所以,这需要自己去评估。 ->cache优化
4  总结
IDCT是视频解码中关键步骤中的第一步,目前一般采用快速算法来做,如chen-wang 算法,c语言和汇编的效果差别还是比较大的。
对一个8x8的block做idct做变换,如   
for (i = 0; i < 8; i++)
 idct_row (block + 8 * i);
for (i = 0; i < 8; i++)
 idct_col (block + i);
把他汇编后,主要是可以减少存储器带宽,提高存储效率,避免无谓的内存读写。
mplayer 在此方面做了很多努力,针对armv4(s3c2440属于armv4l架构)的相关文件放在dsputil_arm_s.S文件中。但遗憾的是,它里面有一条指令PLD,cache预取指令2440是不支持的。PLD指令属于enhanced DSP指令,在armv4E(E 既代表enhanced DSP)才被支持,因此在我们orchid上跑的代码必须注释掉这条指令,否则编译不过
再把话题转回来,在IDCT之前,视频压缩流通过VLD(variable lenght decode)变长解码得到DCT数据。
这部分工作一般是通过查表来加速性能,所有的编码表会预先存起来。而取视频比特流的代码通常是宏,
通过宏的扩展来达到和汇编同样的效果。
在IDCT后还有关键的运动补偿和色彩空间转换两个步骤。对运动补偿的加速也是通过汇编化,其代码也同样放在
dsputil_arm_s.S 有必要一提的是在这部分,如果有SIMD指令将会极大的提高它的速度。
color space转换是解码输出后的最重要的一步。在嵌入式系统中,一般都是采用rgb565既16bit来表示一个像素的色彩。
一个8x8的block,它的yuv(420格式)表示如下,
YYYYYYYY
YYYYYYYY
YYYYYYYY
YYYYYYYY
UUUUUUUU
VVVVVVVV
注意它的值是8bit的,通过装换方程计算,可以得到像素值。在实现中通常采用查表来加速计算,对于每一个Y,U,V都有
一个对应表。对于1个320x240的video,共76800像素。如果每个像素在这个转换中节省10个cycle,那save下来的cpu还是相当可观的。
当色彩空间转换完后,就是通过把这个picture copy到framebuffer的内存里,这里存在一大片的copy时间。有两方面可以注意,
一是有人实现过把转换后的内存直接往framebuffer送,减少最后所需的copy过程,这个idea确实不错,但是需要一些技巧去实现
二是copy这个过程本省也是可以加速的,在armv5以上的体系结构里,cpu----cache---memory,其中cache和memory的宽度是32位,
但cpu和cache的bus width确是64位,用32位的成本实现了64位的存储器。如果这个能被使用,那么理论上,copy速度可以加倍。
在PC机上,一般我们的应用程序会有fastmemorycopy这个函数,它们是用simd等特殊指令来实现,在armv5上则是通过它的总线宽度来加速
在s3c2440上不可用:( 它是v4架构。
总的来说,
(1)算法级的优化基本用无可用,ffmpeg/mplayer已经实现的相当不错,除非自己实现一个新的decoder;
(2)在代码级,主要是通过关键代码的inline(宏,inline函数)和汇编来加速。这部分在arm平台还是有一些潜力可挖
(3)硬件级,在这一层,cpu的体系结构决定指令集、cache的形式和大小等。如指令集是否有enhanced DSP指令、SIMD指令
,cache是否可配置、cache line大小,这些都会影响代码级和算法级的优化
(4)系统层优化,之所以把它放在最后一层,是由于它建立在整个系统之上,只有对整个系统包括硬件和软件有深刻的理解才能做到。
纵观优化,其实质是尽可能的去除冗余计算,最大化的利用系统硬件资源。
对于RISC架构的cpu来讲,先天不足的就是需要比较大的存储器带宽(因为RISC的指令都是基于寄存器的,必须把操作数都load到内存才能计算),
cpu资源被过多的使用在内存的read和write。
以以下代码为例,它是解码输出后,把yuv空间装换成rgb空间的一个片断
000111c :       
    111c:       e92d4ff0        stmdb   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    1120:       e1a0a000        mov     sl, r0
    1124:       e5900038        ldr     r0, [r0, #56]
    1128:       e1a0c001        mov     ip, r1
    112c:       e3500004        cmp     r0, #4  ; 0x4
    1130:       e24dd034        sub     sp, sp, #52     ; 0x34
    1134:       e1a00002        mov     r0, r2
    1138:       e1a01003        mov     r1, r3
    113c:       0a00055d        beq     157c
    1140:       e59d2058        ldr     r2, [sp, #88]
    1144:       e3520000        cmp     r2, #0  ; 0x0
    1148:       d1a00002        movle   r0, r2
    114c:       da00055b        ble     1574
    1150:       e59d3060        ldr     r3, [sp, #96]
    1154:       e58d1030        str     r1, [sp, #48]
    1158:       e5933000        ldr     r3, [r3]
    115c:       e59f2434        ldr     r2, [pc, #1076] ; 1598 <.text+0x1598>
    1160:       e0213193        mla     r1, r3, r1, r3
    1164:       e58d3018        str     r3, [sp, #24]
    1168:       e59d305c        ldr     r3, [sp, #92]
    116c:       e58d1000        str     r1, [sp]
    1170:       e5933000        ldr     r3, [r3]
    1174:       e79a1002        ldr     r1, [sl, r2]
    1178:       e58d301c        str     r3, [sp, #28]
    117c:       e5903008        ldr     r3, [r0, #8]
    1158:       e5933000        ldr     r3, [r3]
    115c:       e59f2434        ldr     r2, [pc, #1076] ; 1598 <.text+0x1598>
    1160:       e0213193        mla     r1, r3, r1, r3
    1164:       e58d3018        str     r3, [sp, #24]
    1168:       e59d305c        ldr     r3, [sp, #92]
    116c:       e58d1000        str     r1, [sp]
    1170:       e5933000        ldr     r3, [r3]
    1174:       e79a1002        ldr     r1, [sl, r2]
    1178:       e58d301c        str     r3, [sp, #28]
    117c:       e5903008        ldr     r3, [r0, #8]
    1180:       e59c4008        ldr     r4, [ip, #8]
    1184:       e590e000        ldr     lr, [r0]
    1188:       e59c2000        ldr     r2, [ip]
    118c:       e5900004        ldr     r0, [r0, #4]
    1190:       e59cc004        ldr     ip, [ip, #4]
    1194:       e58d3014        str     r3, [sp, #20]
    1198:       e1a011c1        mov     r1, r1, asr #3  ;h_size
    119c:       e3a03000        mov     r3, #0  ; 0x0
    11a0:       e58d4010        str     r4, [sp, #16]
    11a4:       e58d2004        str     r2, [sp, #4]
    11a8:       e58de020        str     lr, [sp, #32]
    11ac:       e58d000c        str     r0, [sp, #12]
    11b0:       e58dc008        str     ip, [sp, #8]
    11b4:       e58d1028        str     r1, [sp, #40]
    11b8:       e58d3024        str     r3, [sp, #36]
    11bc:       e1a08003        mov     r8, r3
    .................................................
    .................................................
我们可以发现在这个片断中有太多的ldr(load, read from memory)和str(store, wirte to memory)
而且过多的load和str还影响了cpu和memory之间的cache的效率,形成cache抖动。当发生cache miss时,
cahce控制器花了大力气把内容从memory搬到cache,但是没怎么用这个entry马上又被替换掉。如果运气不好,
cache就一直这样"抖动"。
在解码过程中,各个模块都各自为战,都各自去占比较大的memory带宽
如何减少这种无用的行为呢?必须让关键代码适应硬件体系结构,把数据流相关的代码耦合在一起。
很多代码通过模块化得到了优秀的可读性和可扩展性。鱼与熊掌不可兼得,耦合在一起的代码会显得比较晦涩难懂。
ffmpeg/mplayer在这方面作了一个比较好的tradeoff。
1、2、3的知识摘自网上,要比较好的理解以上内容需要一些视频编、解码的知识。

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(157)
发表于:2008-7-24 9:20:11
标签:无标签

2

xvid的学习

xvid有两种编码方式:single pass和twopass 
single pass模式编码简单,速度也快,但最终效果不如twopass。 
twopass就是视频压制需要经过两次编码,分别为twopass-1st pass(简称1pass)和twopass-2nd pass(简称2pass) 
1pass时,编码器会用最高质量编码采集可供第2次运算参考的画面信息,而在2 pass时。编码器会根据第一次压缩获得的信息和用户指定的文件大小,自动分配比特率,使需要高流量的运动画面分配到更多的空间,更高的比特率来保证画面质量。相对的,对于那些不包含太多运动信息的静态画面则用较低的比特率。追求画质的朋友当然会选择这种方式,但运算比single pass更费时。 

接下来介绍一些基本概念: 
Q值——量化值,它被用来描述1帧的质量,每帧都有一个Q值,取值范围在1-31之间。Q值越小,画质越好,比特率越大 
I-frame——关键帧,常被缩写为IF。关键帧是构成一个帧组的第一帧。IF保留了一个场景的所有信息 
P-frame——未来单项预测帧,缩写为PF,只储存与之前一个已解压画面的差值 
B-frame——双向预测帧,缩写为BF,除了参考之前解压的画面以外,也会参考后一帧的画面信息 

 

编码流程: 

 

各变量的设置:创建xvid_enc_frame_t和xvid_enc_stats_t,分别用于传入参数和统计编码结果。 

具体过程: 

设置传入图像数据和图像色彩空间 

设置传出的码流 

设置vol的标志 

设置帧的编码类型 

设置量化因子 

设置运动估计算法集合 

设置vop的标志 

 

编码器提供的函数 

1, xvid_global(NULL, XVID_GBL_INIT, &xvid_gbl_init, NULL); 

含义:根据cpu的特性使用相应汇编优化的函数 

 

2, xvid_encore(NULL, XVID_ENC_CREATE, &xvid_enc_create, NULL); 

含义:初始化编码器。 

具体过程: 

创建编码器句柄,并根据传入的参数设置各变量的值,并且分配要使用的内存,用于存放重建帧,参考帧(1/2像素精度)。以及各种临时变量。并且做好码率控制的初始化。 

 

3, xvid_encore(enc_handle, XVID_ENC_ENCODE, &xvid_enc_frame, &xvid_enc_stats); 

目的:编码一帧 

具体过程: 

初始化写码流。 

如果有必要,转换色彩空间,并且把原始图像拷贝到有边框的图像空间,但是没有扩展边框。 

将重建帧交换成参考帧 

从帧队列中获取当前帧 

设置Encoder结构体的current结构体的vol_flags,vop_flags,motion_flags,fcode,bcode和quant字段。 

调用call_plugins,在里面调用rc_single_before做码率控制的初始化,以及对current结构体的其他变量进一步设置 

通过帧号或者MEanalysis函数分析来确定编码类型,并且根据用户的设置作修正。 

MEanalysis的原理是,如果某个宏块的残差的sad大于该宏块的平均值的偏离,那么使用intra方式,否则使用inter方式,然后对这些宏块进行统计,得到整帧的编码方式。 

 

如果编码类型是I_VOP 

设置Encoder->mbParam->vol_flags 

设置Encoder->mbParam.par 

根据vol_flags设置vop_flags 

调用FrameCodeI以I帧的方式编码 

调用call_plugins,在里面调用rc_single_after,进行码率控制。 

 

如果编码类型是P_VOP 

用mbParam.vol_flags固定住pEnc->current->vol_flags 

调用FrameCodeP以P帧的方式编码 

调用call_plugins,在里面调用rc_single_after,进行码率控制。 

}// xvid_encore 

 

4, static int FrameCodeI(Encoder * pEnc, Bitstream * bs) 

目的:将一帧图像编码成一个I帧 

具体过程: 

以XVID_PLG_FRAME参数调用call_plugins,该函数目前的作用是设置dquant,可以在该函数中设置最好质量。 

调用SetMacroblockQuants,为每个宏块设置量化因子,所以也可以在这里设置最好质量 

调用BitstreamWriteVolHeader,写vol 

调用set_timecodes,设置时间编码。 

调用BitstreamPad,填充bit至字节对齐 

调用BitstreamWriteVopHeader,填写vop头 

 

依次读取每一个宏块,进行编码 

调用CodeIntraMB设置编码模式为intra,将所有和运动有关的变量设为0 

调用MBTransQuantIntra进行变换编码 

调用MBTrans8to16将像素的表示方法从8bit扩大到16bit 

调用MBfDCT对像素进行变换编码 

调用MBQuantIntra对dct系数进行intra方式的量化 

调用MBDeQuantIntra对dct系数进行intra方式的反量化 

调用MBiDCT将恢复的dct系数进行反变换 

调用MBTrans16to8将恢复的16bit像素饱和到8bit,组成重建宏块 

}//MBTransQuantIntra 

 

调用MBPrediction作acdc预测 

调用get_dc_scaler函数得到量化系数 

调用predict_acdc得到预测方向以及在该预测方向上的和当前块的同一量化水平的预测值 

调用calc_acdc_bits以确定是只使用DC预测,还是DCAC预测。原理是分别作DC预测和DCAC预测,分别计算在这2种情况下需要的码流长度,以确定哪种方式更节约码流。 

调用CodeCoeffIntra_CalcBits,用于确定各种方式下的码流长度 

根据预测模式的不同,恢复成相应的系数 

最后计算该宏块的cbp 

}//MBPrediction 

 

调用MBCoding将宏块编制成码流 

调用CodeBlockIntra将intra宏块编制成码流 

编码mcbpc 

编码ac预测标记 

编码cbpy 

对于6个块里的每个块 

首先编码DC系数 

调用CodeCoeffIntra对剩下的63个系数进行编码 

}//CodeBlockIntra 

}//MBCoding 

 

}//依次读取每一个宏块,进行编码 

 

填充bit,直到字节对齐 

 

 

5, static int FrameCodeP(Encoder * pEnc, Bitstream * bs) 

含义:将一帧图片编码成P帧 

具体过程: 

如果参考帧还没有设置边框,那么就调用image_setedges设置边框 

如果需要半像素运动估计,那么就调用image_interpolate进行插值 

将一帧填充边框后的参考帧,分成8*8的小块,对于每个小块进行插值,如下: 

调用interpolate8x8_halfpel_h进行水平插值 

调用interpolate8x8_halfpel_v进行垂直插值 

调用interpolate8x8_halfpel_hv进行对角线插值 

用参数XVID_PLG_FRAME调用call_plugins,该函数目前的作用是设置dquant,可以在该函数中设置最好质量。 

调用SetMacroblockQuants,为每个宏块设置量化因子,所以也可以在这里设置最好质量 

调用MotionEstimation做运动估计 

使用MotionFlags变量保存要使用的运动算法集合 

使用skip_thresh保存要达到skip模式的阀值 

使用Data保存运动估计要用到的相应变量 

对于每个宏块,依次执行如下操作 

调用sad16v计算本宏块与参考帧对应位置宏块的亮度的残差,将其保存在pMB->sad16中,并按照4个块的方式分别存放pMB->sad8[0-3]中 

用sad00记录最大亮度块残差的4倍 

如果还需要考虑色差块的因素 

调用sad8两次,分别计算u分量和v分量的残差,都加入pMB->sad16中,并且也加入sad00中 

如果该宏块的量化差值为0,并且sad00又没有超过skip模式的阀值 

如果已经考虑了色差因素,或者使用xvid_me_SkipDecisionP确认符合skip模式。 

调用ZeroMacroblockP将其编码为skip模式,并置标记pMB->mode = MODE_NOT_CODED 

根据采用的运动估计算法不同,做相应的设置 

调用SearchP做该宏块的运动估计 

确定是否使用inter4v模式,并记录之 

调用get_range确定运动搜索的范围,并记录在Data中 

调用get_pmvdata2,以获得左,上,右上的运动向量,以及它们对应的sad,存入pmv[1-3]和Data->temp[1-3]。然后计算它们的中值,并且存放于pmv[0],并且把最小的sad存放于Data->temp[0] 

设置Data的当前宏块的yuv字段。设置Data->RefP[0-5]为参考帧的同一宏块的整像素y,水平半象素y,垂直半象素y,对角线y,u,v。 

设置Data->lambda16和Data->lambda8,其含义可能是运动向量对带宽的占用折合到sad的值 

设置qpel和方向 

如果采用qpel,调用get_qpmv2计算用qple方式下的估计中值,存入ata->predMV;否则,Data->predMV为0。 

调用d_mv_bits计算mv需要的编码bit,用于修正pMB->sad16和pMB->sad8[0],并将Data->iMinSAD[0-4]设置为pMB->sad16和pMB->sad8[0-3],也就是0向量对应的各SAD。 

如果不采用率失真决策模型,并且不是当前帧的第一宏块,那么使用一种方法设置阀值threshA,否则阀值threshA为512。 

 

调用PreparePredictionsP,对pmv作进一步的设置,做运算前的准备。 

设置pmv[0]为0向量 

设置pmv[1]为中值向量的偶数值 

设置pmv[2]为参考帧相同位置宏块的第0块运动向量的偶数值 

如果该宏块有左边宏块,设置pmv[3]为左边宏块的第1块的运动向量的偶数值,否则为0 

如果该宏块有上面宏块,设置pmv[4]为上面宏块的第2块的运动向量的偶数值,否则为0 

如果该宏块有右上宏块,设置pmv[5]为右上宏块的第2块的运动向量的偶数值,否则为0 

如果该宏块有右下宏块,设置pmv[6]为参考帧的相同宏块的右下宏块的第0块的运动向量的偶数值,否则为0。 

}//PreparePredictionsP 

 

如果使用inter4v,设置CheckCandidate为CheckCandidate16,否则设置为CheckCandidate16no4v 

 

逐一检查mpv[1-6]这六个最可能运动向量,如果发现他们与以前的运动不同,就调用CheckCandidate做运动估计,过程如下: 

检查要做运动估计的运动向量是否越界 

通过该运动向量获得所指向数据块的指针 

调用sad16v,记录下4个8*8块的SAD值,存入data->temp[0-3]中,并将他们的和存入临时变量sad中。 

对sad和data->temp[0]做基于运动向量的修正。 

如果要考虑色差因素,调用xvid_me_ChromaSAD计算额外的SAD,累加至sad中。 

如果sad小于data->iMinSAD[0],那么设置data->iMinSAD[0],data->currentMV[0],和data->dir。注意,此时的data->dir记录的不是钻石搜索的方向,而是当前向量是pmv数组的第几个元素。 

逐一检查data->temp[0-3],如果他们小于data->iMinSAD[1-4],那么修改data->iMinSAD[1-4]和data->currentMV[1-4] 

}//CheckCandidate 

 

如果当前最优运动向量,即Data->iMinSAD[0],小于threshA?或者当前最优运动向量等于参考帧相同位置宏块的运动向量,并且对应的SAD值又比他的小? 

就不再做inter4v的搜索 

否则,就做inter4v的搜索 

使用make_mask逐一检查存放于pmv的所有运动向量,察看是否位于欲搜索的钻石形的顶点。如果是,则在mask变量中标记之。 

根据MotionFlags确定使用的搜索函数,根据当前设置,MainSearchPtr = xvid_me_AdvDiamondSearch 

调用xvid_me_AdvDiamondSearch进行搜索,过程如下: 

bDirection既表明了上次尝试的方向,又表明本次可以尝试的方向 

x,y为钻石搜索的位置的中心点坐标 

for(;;) 

如果可以尝试左边,那么调用CheckCandidate尝试左边 

如果可以尝试右边,那么调用CheckCandidate尝试右边 

如果可以尝试上边,那么调用CheckCandidate尝试上边 

如果可以尝试下边,那么调用CheckCandidate尝试下边 

如果有更好的方向 

bDirection = 更好的方向 

如果更好的方向是左右方向,那么测试该位置的上下方向 

否则,那么测试该位置的左右方向 

如果这次又找到了更好的方向 

将更好的方向累加到bDirection 

将更好的位置存入x,y 

否则 

根据去搜索临近未搜索的点,具体规则如下: 

如果bDirection = = 2,表明搜索方向是趋向右边的,那么搜索当前中心点的右上点和右下点。 

如果bDirection = = 1,表明搜索方向是趋向左边的,那么搜索当前中心点的左上点和左下点。 

如果bDirection = = 2+4,表明搜索方向是趋向右上的,那么再搜索当前中心点的左上点,右上点和右下点。 

如果bDirection = = 4,表明搜索方向是趋向上边的,那么搜索当前中心点的左上点和右上点。 

如果bDirection = = 8,表明搜索方向是趋向下边的,那么搜索当前中心点的左下点和右下点。 

如果bDirection = = 1+4,表明搜索方向是趋向左上的,那么再搜索当前中心点的左下点,左上点和右上点。 

如果bDirection = = 2+8,表明搜索方向是趋向右下的,那么再搜索当前中心点的左下点,左上点和右上点。 

如果bDirection = = 1+8,表明搜索方向是趋向左下的,那么再搜索当前中心点的左上点,左下点和右下点。 

否则的话,则认为本轮搜索没有找到更好的点,那么再搜索当前中心点的左上点,左下点,右上点,右下点。 

如果没有找到更好的方向,从函数中返回 

更新bDirection为更好的方向 

更新x,y为更好的位置 

}//for(;;) 

 

}//xvid_me_AdvDiamondSearch 

 

如果运动估计算法使用了XVID_ME_EXTSEARCH16,那么 

设置startMV = Data->predMV 

设置backupMV为当前最佳运动向量 

如果startMV和backupMV不相等 

调用CheckCandidate计算位置为startMV的SAD 

调用xvid_me_DiamondSearch做以startMV为起点的搜索,过程如下: 

for(;;) 

如果可以尝试左边,那么调用CheckCandidate尝试左边 

如果可以尝试右边,那么调用CheckCandidate尝试右边 

如果可以尝试上边,那么调用CheckCandidate尝试上边 

如果可以尝试下边,那么调用CheckCandidate尝试下边 

如果没有更好的方向,退出 

bDirection = 更好的方向 

x,y = 更好的位置 

如果更好的方向是左右方向,那么测试该位置的上下方向 

否则,那么测试该位置的左右方向 

如果这次又找到了更好的方向 

bDirection += 更好的方向 

x,y = 更好的位置 

}//xvid_me_DiamondSearch 

 

将这次搜索结果和上次搜索结果比较,记录最佳的SAD和位置。 

}//如果startMV和backupMV不相等 

 

设置startMV = {1,1} 

设置backupMV为当前最佳运动向量 

如果startMV和backupMV不相等 

调用CheckCandidate计算位置为startMV的SAD 

调用xvid_me_DiamondSearch做以startMV为起点的搜索,过程如下: 

将这次搜索结果和上次搜索结果比较,记录最佳的SAD和位置。 

}//如果运动估计算法使用了XVID_ME_EXTSEARCH16 

}//否则,就做inter4v的搜索 

 

如果没有采用1/4像素运动估计算法 

如果采用了XVID_ME_HALFPELREFINE16算法 

调用xvid_me_SubpelRefine 

按顺时针方向8次调用CheckCandidate16,得到最好的1/2像素位置 

否则 

略 

 

如果当前SAD足够小,那么inter4v = 0 

如果采用inter4v 

4次调用Search8来搜索4个8*8块的最佳运动向量,每一次搜索的规则如下: 

如果采用1/4像素运动估计,略。否则 

调用get_pmv2取得本块的中值 

计算第一块以外快的d_mv_bits 

用Data->lambda8修正该块当前的SAD,但是第0块是不用修正的。 

如果使用了XVID_ME_EXTSEARCH8 | XVID_ME_HALFPELREFINE8 | XVID_ME_QUARTERPELREFINE8,那么 

Data->RefP[0-3] = 参考帧的整像素,水平半象素,垂直半象素,对角线半象素的对应宏块的对应块的起始地址。 

Data->Cur = 当前帧的当前宏块的当前块的起始地址 

利用get_range得到运动搜索的范围 

根据MotionFlags的指示,设定运动估计MainSearchPtr的算法,当前设置为MainSearchPtr = xvid_me_AdvDiamondSearch。 

调用xvid_me_AdvDiamondSearch做运动估计,其中做SAD的函数是CheckCandidate8,该函数类似于CheckCandidate16 

如果不采用1/4像素运动估计,并且又采用了XVID_ME_HALFPELREFINE8,那么调用xvid_me_SubpelRefine 

按顺时针方向8次调用CheckCandidate8,得到最好的1/2像素位置 

如果采用了1/4像素运动估计,略 

}// XVID_ME_EXTSEARCH8 | XVID_ME_HALFPELREFINE8 | XVID_ME_QUARTERPELREFINE8 

 

如果采用1/4运动估计 

略 

否则 

记录pMB->pmvs[block] = 当前找到的最佳位置与预测位置的差值 

 

将这次的搜索存入相应OldData的字段,以及pMB的相应字段 

}// Search8 

如果考虑色差的因素,并且又不考虑率失真算法 

根据是否采用1/4像素运动估计算出色差的运动向量 

计算u,v的SAD,将其作为Data->iMinSAD[1]的修正 

} //如果采用inter4v 

否则,Data->iMinSAD[1]为足够大的值 

}//SearchP 

 

调用ModeDecision_SAD确定该宏块的类型 

判断该宏块要采取的编码方式,MODE_INTER,MODE_INTER4V,MODE_NOT_CODED,MODE_INTRA 

调用motionStatsPVOP做一些统计工作 

具体过程略 

}//对于每个宏块,依次执行如下操作 

做一些最后的设置 

}//MotionEstimation 

 

调用set_timecodes设置时间戳 

调用BitstreamWriteVopHeader写VOP头 

具体过程略 

 

对于每一个宏块,依次执行如下操作 

如果该宏块的编码模式是MODE_INTRA或者MODE_INTRA_Q 

调用CodeIntraMB设置编码模式为intra,将所有和运动有关的变量设为0 

调用MBTransQuantIntra进行变换编码 

调用MBCoding将该宏块编制成码流 

Continue 

 

调用MBMotionCompensation做运动补偿 

如果编码模式是MODE_NOT_CODED 

用参考帧的相应宏块替代当前帧的当前宏块 

Return 

 

如果编码模式是MODE_NOT_CODED或者MODE_INTER或者MODE_INTER_Q 

如果mb->mcsel不为0 

做GMC的处理 

Return 

计算运动向量dx,dy 

调用compensate16x16_interpolate进行运动补偿 

如果采用1/4像素运动估计 

略 

否则,调用get_ref计算用于运动补偿的参考宏块的指针 

调用4次transfer_8to16sub做亮度块的运动补偿,使得临时数组里存放的是残差,而原始图像里存放的是参考快的数据。 

}//compensate16x16_interpolate 

计算出用于色差运动补偿的dx,dy 

}//MODE_NOT_CODED或者MODE_INTER或者MODE_INTER_Q 

 

否则,那就是MODE_INTER4V 

根据是否使用1/4像素运动估计,计算出4个色度块的运动向量 

以这4个运动向量为参数,调用4次compensate8x8_interpolate ,该操作类似于compensate16x16_interpolate,不同在于一次只计算一个块。 

计算出用于色差运动补偿的dx,dy 

 

调用CompensateChroma计算色差块的运动补偿 

调用interpolate8x8_switch2计算出u的插值 

调用interpolate8x8_halfpel_v或者interpolate8x8_halfpel_h或者interpolate8x8_halfpel_hv做实际的插值操作,或者直接返回 

调用transfer_8to16sub_c做u份量的运动补偿 

 

调用interpolate8x8_switch2计算出v的插值 

调用interpolate8x8_halfpel_v或者interpolate8x8_halfpel_h或者interpolate8x8_halfpel_hv做实际的插值操作,或者直接返回 

调用transfer_8to16sub_c做v份量的运动补偿 

}//CompensateChroma 

 

}//MBMotionCompensation 

 

如果需要编码,那么用MBTransQuantInter进行编码,并把结果返回给pMB->cbp 

调用MBfDCT进行宏块变换编码 

调用6次fdct 

 

调用MBQuantInter进行量化 

对于宏块里的每一块 

调用quant_h263_inter进行量化 

如果在量化后,前三个系数为0,并且系数的绝对值之和小于阀值,那么标记该块为全0块,将标记存入cbp。否则,标记为非全0块,也将标记存入cbp 

}//MBQuantInter 

 

调用MBDeQuantInter反量化 

确定要使用的反量化函数 

对于六个块里的每个块,如果cbp表示许可,都调用dequant_h263_inter反量化 

}//MBDeQuantInter 

 

调用MBiDCT做反离散余弦变换 

对于六个块里的每个块,如果cbp表示许可,都调用idct_int32反量化 

 

调用MBTrans16to8将恢复出的残差构成重建图像 

确定具体执行的函数,分为transfer_16to8copy和transfer_16to8add 

找到该宏块的y,u,v分量起始地址 

对于六个块里的每个块,如果cbp表示许可,调用相应得函数执行重建。 

}// MBTrans16to8 

}//MBTransQuantInter 

 

如果无残差,并且编码方式为MODE_INTER,并且帧方式是P帧,并且向量2分量都为0,那么可以考虑skip模式 

如果可以考虑skip模式,则做进一步检验,如果检验通过,那么 

编码模式为MODE_NOT_CODED,并且在码流里做标记 

Continue 

 

调用MBCoding将这个宏块写入码流 

写入非NOT_CODED标记 

调用CodeBlockInter写入码流 

编码mcbpc 

编码cbpy 

调用CodeVector编码运动向量 

对六个块,如果cbp只是需要编码,调用CodeCoeffInter进行编码 

}//CodeBlockInter 

}// MBCoding 

 

}//对于每一个宏块,依次执行如下操作 

 

更新fcode 

为下一帧的编码做简单的更新设置 

统计该帧编码长度 

}// FrameCodeP

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类: 无分类    |    来源: 转贴

评论(0) | 阅读(196)
发表于:2008-7-3 20:07:56
标签:无标签

1

linux下面编程的几个数据结构

1、获取磁盘信息

                              struct   statfs   {  
                                    long         f_type;           /*   type   of   filesystem   (see   below)   */  
                                    long         f_bsize;         /*   optimal   transfer   block   size   */  
                                    long         f_blocks;       /*   total   data   blocks   in   file   system   */  
                                    long         f_bfree;         /*   free   blocks   in   fs   */  
                                    long         f_bavail;       /*   free   blocks   avail   to   non-superuser   */  
                                    long         f_files;         /*   total   file   nodes   in   file   system   */  
                                    long         f_ffree;         /*   free   file   nodes   in   fs   */  
                                    fsid_t     f_fsid;           /*   file   system   id   */  
                                    long         f_namelen;     /*   maximum   length   of   filenames   */  
                              };  
   
  f_bfree   *   f_bsize   应该就是剩余空间

2、获得终端信息的

termios, tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, cfgetispeed, cfsetispeed, cfsetospeed - 获取和设置终端属性,行控制,获取和设置波特率

 

它是指向一个 termios 结构的指针。这个结构包含了至少下列成员:


tcflag_t c_iflag;      /* 输入模式 */
tcflag_t c_oflag;      /* 输出模式 */
tcflag_t c_cflag;      /* 控制模式 */
tcflag_t c_lflag;      /* 本地模式 */
cc_t c_cc[NCCS];       /* 控制字符 */

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类: 无分类    |    来源: 整理

评论(0) | 阅读(181)
发表于:2008-6-20 10:40:13
标签:无标签

1

Linux 下串口编程入门

Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍。

串口简介

串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺。

Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的 《Serial Programming Guide for POSIX Operating Systems》

计算机串口的引脚说明

序号 信号名称 符号 流向 功能
2 发送数据 TXD DTE→DCE DTE发送串行数据
3 接收数据 RXD DTE←DCE DTE 接收串行数据
4 请求发送 RTS DTE→DCE DTE 请求 DCE 将线路切换到发送方式
5 允许发送 CTS DTE←DCE DCE 告诉 DTE 线路已接通可以发送数据
6 数据设备准备好 DSR DTE←DCE DCE 准备好
7 信号地        信号公共地
8 载波检测 DCD DTE←DCE 表示 DCE 接收到远程载波
20 数据终端准备好 DTR DTE→DCE DTE 准备好
22 振铃指示 RI DTE←DCE 表示 DCE 与线路接通,出现振铃



串口操作

串口操作需要的头文件

#include     <stdio.h>      /*标准输入输出定义*/
#include     <stdlib.h>     /*标准函数库定义*/
#include     <unistd.h>     /*Unix 标准函数定义*/
#include     <sys/types.h>  
#include     <sys/stat.h>   
#include     <fcntl.h>      /*文件控制定义*/
#include     <termios.h>    /*PPSIX 终端控制定义*/
#include     <errno.h>      /*错误号定义*/




打开串口

在 Linux 下串口文件是位于 /dev 下的

串口一 为 /dev/ttyS0

串口二 为 /dev/ttyS1

打开串口是通过使用标准的文件打开函数操作:

int fd;
/*以读写方式打开串口*/
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd){ 
/* 不能打开串口一*/ 
perror(" 提示错误!");
}




设置串口

最基本的设置串口包括波特率设置,效验位和停止位设置。

串口的设置主要是设置 struct termios 结构体的各成员值。

struct termio
{ unsigned short  c_iflag; /* 输入模式标志 */ 
 unsigned short  c_oflag;  /* 输出模式标志 */ 
 unsigned short  c_cflag;  /* 控制模式标志*/ 
 unsigned short  c_lflag;  /* local mode flags */ 
 unsigned char  c_line;      /* line discipline */ 
 unsigned char  c_cc[NCC];    /* control characters */
};

设置这个结构体很复杂,我这里就只说说常见的一些设置:

波特率设置

下面是修改波特率的代码:

struct  termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200);     /*设置为19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);

设置波特率的例子函数:

/**
*@brief  设置串口通信速率
*@param  fd     类型 int  打开串口的文件句柄
*@param  speed  类型 int  串口速度
*@return  void
*/
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
     B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400,  19200,  9600,  4800,  2400,  1200,  300, 38400,  
     19200,  9600, 4800, 2400, 1200,  300, };
void set_speed(int fd, int speed){
 int   i; 
 int   status; 
 struct termios   Opt;
 tcgetattr(fd, &Opt); 
 for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++) { 
  if  (speed == name_arr[i]) {     
   tcflush(fd, TCIOFLUSH);     
   cfsetispeed(&Opt, speed_arr[i]);  
   cfsetospeed(&Opt, speed_arr[i]);   
   status = tcsetattr(fd1, TCSANOW, &Opt);  
   if  (status != 0) {        
    perror("tcsetattr fd1");  
    return;     
   }    
   tcflush(fd,TCIOFLUSH);   
  }  
 }
}

效验位和停止位的设置:

无效验 8位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS8;
奇效验(Odd) 7位 Option.c_cflag |= ~PARENB;
Option.c_cflag &= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
偶效验(Even) 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag |= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
Space效验 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= &~CSIZE;
Option.c_cflag |= CS8;

设置效验的函数:

/**
*@brief   设置串口数据位,停止位和效验位
*@param  fd     类型  int  打开的串口文件句柄
*@param  databits 类型  int 数据位   取值 为 7 或者8
*@param  stopbits 类型  int 停止位   取值为 1 或者2
*@param  parity  类型  int  效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{ 
 struct termios options; 
 if  ( tcgetattr( fd,&options)  !=  0) { 
  perror("SetupSerial 1");     
  return(FALSE);  
 }
 options.c_cflag &= ~CSIZE; 
 switch (databits) /*设置数据位数*/
 {   
 case 7:  
  options.c_cflag |= CS7; 
  break;
 case 8:     
  options.c_cflag |= CS8;
  break;   
 default:    
  fprintf(stderr,"Unsupported data size\n"); return (FALSE);  
 }
switch (parity) 
{   
 case 'n':
 case 'N':    
  options.c_cflag &= ~PARENB;   /* Clear parity enable */
  options.c_iflag &= ~INPCK;     /* Enable parity checking */ 
  break;  
 case 'o':   
 case 'O':     
  options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/  
  options.c_iflag |= INPCK;             /* Disnable parity checking */ 
  break;  
 case 'e':  
 case 'E':   
  options.c_cflag |= PARENB;     /* Enable parity */    
  options.c_cflag &= ~PARODD;   /* 转换为偶效验*/     
  options.c_iflag |= INPCK;       /* Disnable parity checking */
  break;
 case 'S': 
 case 's':  /*as no parity*/   
     options.c_cflag &= ~PARENB;
  options.c_cflag &= ~CSTOPB;break;  
 default:   
  fprintf(stderr,"Unsupported parity\n");    
  return (FALSE);  
 }  
/* 设置停止位*/  
switch (stopbits)
{   
 case 1:    
  options.c_cflag &= ~CSTOPB;  
  break;  
 case 2:    
  options.c_cflag |= CSTOPB;  
    break;
 default:    
   fprintf(stderr,"Unsupported stop bits\n");  
   return (FALSE); 
} 
/* Set input parity option */ 
if (parity != 'n')   
 options.c_iflag |= INPCK; 
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/   
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)   
{ 
 perror("SetupSerial 3");   
 return (FALSE);  
} 
return (TRUE);  
}

需要注意的是:

如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:

options.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
options.c_oflag  &= ~OPOST;   /*Output*/




读写串口

设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。

  • 发送数据
    char  buffer[1024];int    Length;int    nByte;nByte = write(fd, buffer ,Length)
    

  • 读取串口数据

    使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。

    可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。

    char  buff[1024];int    Len;int  readByte = read(fd,buff,Len);
    

关闭串口

关闭串口就是关闭文件。

close(fd);


例子

下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件

/**********************************************************************代码说明:使用串口二测试的,发送的数据是字符,
但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。
**********************************************************************/
#define FALSE  -1
#define TRUE   0
/*********************************************************************/
int OpenDev(char *Dev)
{
 int fd = open( Dev, O_RDWR );         //| O_NOCTTY | O_NDELAY 
 if (-1 == fd) 
 {    
  perror("Can't Open Serial Port");
  return -1;  
 } 
 else 
  return fd;
}
int main(int argc, char **argv){
 int fd;
 int nread;
 char buff[512];
 char *dev  = "/dev/ttyS1"; //串口二
 fd = OpenDev(dev);
 set_speed(fd,19200);
 if (set_Parity(fd,8,1,'N') == FALSE)  {
  printf("Set Parity Error\n");
  exit (0);
 }
while (1) //循环读取数据
{   
 while((nread = read(fd, buff, 512))>0)
 { 
  printf("\nLen %d\n",nread); 
  buff[nread+1] = '\0';   
  printf( "\n%s", buff);   
 }
}
 //close(fd);  
 // exit (0);
}

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(202)
发表于:2008-6-20 10:06:09
标签:无标签

1

va_list、va_start、va_arg、va_end的原理与使用

 

  1. 概述
    由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦;即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,有些人采用指针参数来解决问题.下面就c语言中处理不定参数数目的问题进行讨论.
  2. 定义
    大家先看几宏.
    在VC++6.0的include有一个stdarg.h头文件,有如下几个宏定义:
    #define _INTSIZEOF(n)   ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
    #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )           //第一个可选参数地址
    #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
    #define va_end(ap)    ( ap = (va_list)0 )                            // 将指针置为无效
    如果对以上几个宏定义不理解,可以略过,接这看后面的内容.
  3. 参数在堆栈中分布,位置
    在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段的目的.
    总之,函数在堆栈中的分布情况是:地址从高到低,依次是:函数参数列表,函数返回地址,函数执行代码段.
    堆栈中,各个函数的分布情况是倒序的.即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分.参数在堆栈中的分布情况如下:
    最后一个参数
    倒数第二个参数
    ...
    第一个参数
    函数返回地址
    函数代码段
  4. 示例代码
    void arg_test(int i, ...);
    int main(int argc,char *argv[])
    {
     int int_size = _INTSIZEOF(int);
     printf("int_size=%d\n", int_size);
     arg_test(0, 4);
     
     arg_cnt(4,1,2,3,4);
     return 0;
    }
    void arg_test(int i, ...)
    {
     int j="0";
     va_list arg_ptr; 
     
     va_start(arg_ptr, i); 
     printf("&i = %p\n", &i);//打印参数i在堆栈中的地址
     printf("arg_ptr = %p\n", arg_ptr);
     //打印va_start之后arg_ptr地址,
     //应该比参数i的地址高sizeof(int)个字节
     //这时arg_ptr指向下一个参数的地址
     
     j=*((int *)arg_ptr);
     printf("%d %d\n", i, j); 
     j=va_arg(arg_ptr, int);
     printf("arg_ptr = %p\n", arg_ptr);
     //打印va_arg后arg_ptr的地址
     //应该比调用va_arg前高sizeof(int)个字节
     //这时arg_ptr指向下一个参数的地址
     va_end(arg_ptr);
     printf("%d %d\n", i, j);
    }

  5. 代码说明:
    int int_size = _INTSIZEOF(int);得到int类型所占字节数
     va_start(arg_ptr, i); 得到第一个可变参数地址,

    根据定义(va_list)&v得到起始参数的地址, 再加上_INTSIZEOF(v) ,就是其实参数下一个参数的地址,即第一个可变参数地址.
    j=va_arg(arg_ptr, int); 得到第一个参参数的值,并且arg_ptr指针上移一个_INTSIZEOF(int),即指向下一个可变参数的地址.
    va_end(arg_ptr);置空arg_ptr,即arg_ptr=0;
    总结:读取可变参数的过程其实就是堆栈中,使用指针,遍历堆栈段中的参数列表,从低地址到高地址一个一个地把参数内容读出来的过程.

  6. 在编程中应该注意的问题和解决办法
    虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数,但是由于不知道可变参数有多少个,什么时候应该结束遍历,如果在堆栈中遍历太多,那么很可能读取一些无效的数据.
    解决办法:a.可以在第一个起始参数中指定参数个数,那么就可以在循环还中读取所有的可变参数;b.定义一个结束标记,在调用函数的时候,在最后一个参数中传递这个标记,这样在遍历可变参数的时候,可以根据这个标记结束可变参数的遍历;
    下面是一段示例代码:
    //第一个参数定义可选参数个数,用于循环取初参数内容
    void arg_cnt(int cnt, ...);
    int main(int argc,char *argv[])
    {
     int int_size = _INTSIZEOF(int);
     printf("int_size=%d\n", int_size);
     arg_cnt(4,1,2,3,4);
     return 0;
    }
    void arg_cnt(int cnt, ...)
    {
     int value="0";
     int i="0";
     int arg_cnt=cnt;
     va_list arg_ptr; 
     va_start(arg_ptr, cnt); 
     for(i = 0; i < cnt; i++)
     {
      value = va_arg(arg_ptr,int);
      printf("value%d=%d\n", i+1, value);
     }
    }

    虽然可以根据上面两个办法解决读取参数个数的问题,但是如果参数类型都是不定的,该怎么办,如果不知道参数的类型,即使读到了参数也没有办法进行处理.解决办法:可以自定义一些可能出现的参数类型,这样在可变参数列表中,可以可变参数列表中的那类型,然后根据类型,读取可变参数值,并进行准确地转换.传递参数的时候可以这样传递:参数数目,可变参数类型1,可变参数值1,可变参数类型2,可变参数值2,....
    这里给出一个完整的例子:
    #include
    #include
    const int INT_TYPE  = 100000;
    const int STR_TYPE  = 100001;
    const int CHAR_TYPE  = 100002;
    const int LONG_TYPE  = 100003;
    const int FLOAT_TYPE = 100004;
    const int DOUBLE_TYPE = 100005;
    //第一个参数定义可选参数个数,用于循环取初参数内容
    //可变参数采用arg_type,arg_value...的形式传递,以处理不同的可变参数类型
    void arg_type(int cnt, ...);
    //第一个参数定义可选参数个数,用于循环取初参数内容
    void arg_cnt(int cnt, ...);
    //测试va_start,va_arg的使用方法,函数参数在堆栈中的地址分布情况
    void arg_test(int i, ...);
    int main(int argc,char *argv[])
    {
     int int_size = _INTSIZEOF(int);
     printf("int_size=%d\n", int_size);
     arg_test(0, 4);
     
     arg_cnt(4,1,2,3,4);
     arg_type(2, INT_TYPE, 222, STR_TYPE, "ok,hello world!");
     return 0;
    }

void arg_test(int i, ...)
{
 int j="0";
 va_list arg_ptr; 
 
 va_start(arg_ptr, i); 
 printf("&i = %p\n", &i);//打印参数i在堆栈中的地址
 printf("arg_ptr = %p\n", arg_ptr);
 //打印va_start之后arg_ptr地址,
 //应该比参数i的地址高sizeof(int)个字节
 //这时arg_ptr指向下一个参数的地址
 
 j=*((int *)arg_ptr);
 printf("%d %d\n", i, j); 
 j=va_arg(arg_ptr, int);
 printf("arg_ptr = %p\n", arg_ptr);
 //打印va_arg后arg_ptr的地址
 //应该比调用va_arg前高sizeof(int)个字节
 //这时arg_ptr指向下一个参数的地址
 va_end(arg_ptr);
 printf("%d %d\n", i, j);
}
void arg_cnt(int cnt, ...)
{
 int value="0";
 int i="0";
 int arg_cnt=cnt;
 va_list arg_ptr; 
 va_start(arg_ptr, cnt); 
 for(i = 0; i < cnt; i++)
 {
  value = va_arg(arg_ptr,int);
  printf("value%d=%d\n", i+1, value);
 }
}
void arg_type(int cnt, ...)
{
 int arg_type = 0;
 int int_value=0;
 int i="0";
 int arg_cnt=cnt; 
 char *str_value = NULL;
 va_list arg_ptr; 
 va_start(arg_ptr, cnt);
 for(i = 0; i < cnt; i++)
 {
  arg_type = va_arg(arg_ptr,int);
  switch(arg_type)
  {
  case INT_TYPE:
   int_value = va_arg(arg_ptr,int);
   printf("value%d=%d\n", i+1, int_value);
   break;
  case STR_TYPE:
   str_value = va_arg(arg_ptr,char*);
   printf("value%d=%d\n", i+1, str_value);
   break;
  default:
   break;
  }
 }
}
以上是我个人的见解,不对的地方希望大家指正,发表看法,我不胜感谢!!!

 

va_start() va_end()函数应用

1:当无法列出传递函数的所有实参的类型和数目时,可用省略号指定参数表
void foo(...);
void foo(parm_list,...);

2:函数参数的传递原理
函数参数是以数据结构:栈的形式存取,从右至左入栈.eg:
#include
void fun(int a, ...)
{
int *temp = &a;
temp++;
for (int i = 0; i < a; ++i)
{
cout << *temp << endl;
temp++;
}
}

int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
fun(4, a, b, c, d);
system("pause");
return 0;
}
Output::
1
2
3
4

3:获取省略号指定的参数
在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。像这段代码:
void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...)
{
va_list args;
va_start(args, pszFormat);
_vsnprintf(pszDest, DestLen, pszFormat, args);
va_end(args);
}

4.va_start使argp指向第一个可选参数。va_arg返回参数列表中的当前参数并使argp指向参数列表中的下一个参数。va_end把argp指针清为NULL。函数体内可以多次遍历这些参数,但是都必须以va_start开始,并以va_end结尾。

  1).演示如何使用参数个数可变的函数,采用ANSI标准形式
  #include 〈stdio.h〉
  #include 〈string.h〉
  #include 〈stdarg.h〉
  /*函数原型声明,至少需要一个确定的参数,注意括号内的省略号*/
  int demo( char, ... );
  void main( void )
  {
   demo("DEMO", "This", "is", "a", "demo!", "");
  }
  /*ANSI标准形式的声明方式,括号内的省略号表示可选参数*/
  int demo( char msg, ... )
  {
/*定义保存函数参数的结构*/
   va_list argp;
   int argno = 0;
   char para;

   /*argp指向传入的第一个可选参数,msg是最后一个确定的参数*/
   va_start( argp, msg );
   while (1)
{
   para = va_arg( argp, char);
   if ( strcmp( para, "") == 0 )
break;
   printf("Parameter #%d is: %s\n", argno, para);
   argno++;
   }
   va_end( argp );
   /*将argp置为NULL*/
   return 0;
  }

2)//示例代码1:可变参数函数的使用
#include "stdio.h"
#include "stdarg.h"
void simple_va_fun(int start, ...)
{
va_list arg_ptr;
int nArgValue =start;
int nArgCout="0"; //可变参数的数目
va_start(arg_ptr,start); //以固定参数的地址为起点确定变参的内存起始地址。
do
{
++nArgCout;
printf("the %d th arg: %d\n",nArgCout,nArgValue); //输出各参数的值
nArgValue = va_arg(arg_ptr,int); //得到下一个可变参数的值
} while(nArgValue != -1);
return;
}
int main(int argc, char* argv[])
{
simple_va_fun(100,-1);
simple_va_fun(100,200,-1);
return 0;
}

3)//示例代码2:扩展——自己实现简单的可变参数的函数。
下面是一个简单的printf函数的实现,参考了中的例子
#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ...) //一个简单的类似于printf的实现,//参数必须都是int 类型
{
char* pArg="NULL"; //等价于原来的va_list
char c;

pArg = (char*) &fmt; //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值
pArg += sizeof(fmt); //等价于原来的va_start

do
{
c =*fmt;
if (c != '%')
{
putchar(c); //照原样输出字符
}
else
{
//按格式字符输出数据
switch(*++fmt)
{
case'd':
printf("%d",*((int*)pArg));
break;
case'x':
printf("%#x",*((int*)pArg));
break;
default:
break;
}
pArg += sizeof(int); //等价于原来的va_arg
}
++fmt;
}while (*fmt != '\0');
pArg = NULL; //等价于va_end
return;
}
int main(int argc, char* argv[])
{
int i = 1234;
int j = 5678;

myprintf("the first test:i=%d\n",i,j);
myprintf("the secend test:i=%d; %x;j=%d;\n",i,0xabcd,j);
system("pause");
return 0;
}

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(186)
发表于:2008-6-14 16:16:28
标签:无标签

2

Linux編程中遇到的幾個函數

1.atoi函數

This function converts a given string to integer

int atoi(const char *sz);

returns the string's integer value

2.memset函數

set buffers to a specified character

memset{

      void *dest,//pointer to destination

     int c,// character to set

    size_t count // number of character

     }

return the value of dest

3. htonl /htons

 convert a u-long/u-short from host to TCP/IP network byte order,which is big-endian

u_long htonl{  u_long hostlong}

u_short htonl{  u_short hostlong}

4.fcntl 整理中

点击此处查看原文 >>

系统分类: 自由话题   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(169)
发表于:2008-5-30 22:07:30
标签:无标签

1

终于搞定在skyeye里面运行uclinux

很早就成功安装了skyeye,但是一直没有正确的编译uclinux,今天终于把它搞定了

贴出来庆祝下:

编译uclinux的步骤:

make menuconfig (选择GDB,linux-2.4.x uclibc)

make dep   //进行编译链接

make lib_only  //编译uClibc。以后我们编译用户程序的时候需要这个运行库

make user_only  //编译用户的应用程序,包括初始化进程init,和用户交互的bash,以及集成了很多程序的busybox,还有一些服务,如boa(一个在嵌入式领域用的很多的Web 服务器)和telnetd(telnet 服务器,我们可以通过网络来登录我们的uClinux 而不一定使用串口)。

make romfs

make image//报错了这里,说没有linux的东西,先不管

make linux //出现error:cannot open romfs.o

解决办法:cp ./linux-2.4.x/fs/romfs/romfs.o ./linux-2.4.x/

再编译下make linux

发现还有错误    arm-elf-ld -p -X -T arch/armnommu/vmlinux.lds arch/armnommu/kernel/head-armv.o arch/armnommu/kernel/init_task.o init/main.o init/version.o init/do_mounts.o
    --start-group
    arch/armnommu/kernel/kernel.o arch/armnommu/mm/mm.o arch/armnommu/mach-S3C44B0X/S3C44B0X.o kernel/kernel.o mmnommu/mmnommu.o fs/fs.o ipc/ipc.o
    drivers/serial/serial.o drivers/char/char.o drivers/block/block.o drivers/misc/misc.o drivers/net/net.o drivers/net/appletalk/appletalk.o drivers/media/media.o
    net/network.o
    arch/armnommu/lib/lib.a /root/uClinux-dist/linux-2.4.x/lib/lib.a /usr/local/lib/gcc-lib/arm-elf/2.95.3/libgcc.a
    --end-group
    -o linux
    fs/fs.o(.data+0x1378): multiple definition of `romfs_file_operations'
    romfs.o(.data+0x80): first defined here
    make[1]: *** [linux] Error 1
    make[1]: Leaving directory `/root/uClinux-dist/linux-2.4.x'
    make: *** [linux] Error 1

解决办法:vi ~/uClinux-dist/linux-2.4.x/fs/romfs/inode.c
    /* Mapping from our types to the kernel */

    static struct address_space_operations romfs_aops = {
    readpage: romfs_readpage
    };

    static struct file_operations romfs_dir_operations = {
    read: generic_read_dir,
    readdir: romfs_readdir,
    };

    static struct file_operations romfs_file_operations = {
    read: generic_file_read,
    mmap: generic_file_mmap,
    #ifdef MAGIC_ROM_PTR
    romptr: romfs_romptr,
    #endif
再进行make linux

这一次ok了

再来make image下,这下是ok的了

这下是ok的

在uc-linux-dist目录下新建一个skyeye.conf

cpu: arm7tdmi
 mach: at91
 mem_bank: map="M", type="RW", addr="0x00000000", size="0x00004000"
 mem_bank: map="M", type="RW", addr="0x01000000", size="0x00400000"
 mem_bank: map="M", type="R", addr="0x01400000", size="0x00400000", file="images/romfs".img
 mem_bank: map="M", type="RW", addr="0x02000000", size="0x00400000"
 mem_bank: map="M", type="RW", addr="0x02400000", size="0x00008000"
 mem_bank: map="M", type="RW", addr="0x04000000", size="0x00400000"
 mem_bank: map="I", type="RW", addr="0xf0000000", size="0x10000000"

然后在uclinux目录下运行

skyeye linux-2.4.x/linux

(skyeye)target sim

(sky