Linux C/C++ 意志坚定是我信念

----勤奋的李文勰

   :: 首页 :: 新随笔 :: 联系 ::  :: 管理 ::
  37 随笔 :: 7 文章 :: 8 评论 :: 0 引用

2009年10月23日 #

  1.6.2是目前飞漫公司在GPL许可下发行的miniGUI最新版本,2.0.3 和1.6.9是商业增值版,分别适用于有MMU和没有MMU的处理器,用这两个是要花¥的。

一.编译安装miniGUI 1.6.2

   首先要做好准备工作,去http://www.minigui.org/res.shtml下载一些压缩包:
    开发库:libminigui-str-1.6.2.tar.gz
    资源文件:minigui-res-str-1.6.tar.gz
    示例程序:mg-samples-str-1.6.2.tar.gz
    建议在qvfb中运行miniGUI程序,这样比较方便,qvfb下载地址:
 http://www.minigui.com/downloads/dep-libs/qvfb-1.0.tar.gz

我使用的是fedora 7,所以在其他系统中和这里可能有差异,比如编译出错等问题,这个和gcc,glibc的版本有关系。遇到问题解决问题,我也是经过种种困难才编译成功的,多搜索多思考。


(1)安装minigui-res-str-1.6.tar.gz:MiniGUI 所使用的资源, 包括基本字体、图标、位图和鼠标光标。
安装命令:
[root@fedora7]#tar -zxf minigui-res-str-1.6.tar.gz
[root@fedora7]#cd minigui-res-str-1.6
[root@fedora7]#make install

(2)安装libminigui-str-1.6.2.tar.gz:MiniGUI 函数库源代码。
解压命令:
[root@fedora7]#tar -zxf libminigui-str-1.6.2.tar.gz
这里有一个bug,解压缩后进入libminigui-str-1.6.2/src/kernel打开init.c文件,
删除或注释掉下面这几行
#ifdef __LINUX__
else {
pthread_kill_other_threads_np ();
}
#endif
保存退出,然后回到/libminigui-str-1.6.2目录接着安装:
[root@fedora7]#cd libminigui-str-1.6.2
[root@fedora7]#./configure
[root@fedora7]#make
[root@fedora7]#make install

(3)把libminigui加入库搜索路径:
进入/etc/ld.so.conf.d建一个minigui.conf,写上/usr/local/lib
可以使用命令完成:
[root@fedora7]#echo /usr/local/lib > /etc/ld.so.conf.d/minigui.conf
(如果你使用的不是fedora7可能没有/etc/ld.so.conf.d这个目录,只有一个/etc/ld.so.conf,不过作用是一样的,在这个文件末尾加上/usr/local/lib)


(4)最后要把系统共享库缓存刷新,将libminigui加载上,可以使用命令
[root@fedora7]#ldconfig
这个命令执行时要花十多秒,耐心等待。当然不使用刷新命令重启系统也可以。


(5)编译示例程序
解压并编译mg-smaples-str-1.6.2.tar.gz:
----------------------------------------------------
[root@fedora7]#tar –zxf mg-smaples-str-1.6.2.tar.gz
[root@fedora7]#./configure
[root@fedora7]#make
----------------------------------------------------

二.配置minigui 

minigui在linux中有两种运行模式:fbcon和qvfb
fbcon:Frame Buffer Console
qvfb: Qt Virtual Frame Buffer
看名字就知道fbcon在控制台下运行,这种模式下你不能开linux的X图形界面,使用不方便。fbcon则是带帧缓冲的虚拟控制台,minigui程序在qvfb中运行就像我们在图形界面下的Terminal中运行命令一样。两种模式我都试过了,详细配置如下:

(1)fbcon模式
打开MiniGUI.cfg
[root@fedora7]# gedit /usr/local/etc/MiniGUI.cfg
注意这一段:
-------------------------------------------------
[system]
# GAL engine
gal_engine=fbcon

# IAL engine
ial_engine=fbcon

mdev=/dev/mouse
mtype=IMPS2
-------------------------------------------------
gal_engine和ial_engine的值应该设置为fbcon,minigui安装后默认设置就是这样,所以你不用改。要注意的就是鼠标的设置,我的fedora7中鼠标设备文件不是这里的/dev/mouse,而是/dev/input/mice,所以我建了一个符号链接到/dev/mouse

配置文件MiniGUI.cfg搞定后需要开启linux内核的帧缓冲支持,这个是在引导器中修改,我使用的是现在最流行的grub,修改方法:
[root@fedora7]# vim /boot/grub/menu.lst
在menu.lst中加入一个段落:
----------------------------------------------------------
title MiniGUI 1.6.2
    root(hd0,0)
    kernel /vmlinuz-2.6.21-1.3194.fc7 ro root=/dev/VolGroup00/LogVol00 vga=0x0317 fb:on
    initrd /initrd-2.6.21-1.3194.fc7.img
----------------------------------------------------------
这里的root(hd0,0)以及kernel,initrd段需要结合你自己的系统和分区情况填写,最简单的办法是把你以前的一个linux启动项找过来对照着改,kernel项后面的vga=0x0317 fb:on是设置显示器分辨率,打开帧缓冲。

显示模式对照表:
-------------------------------------------------------
       640x480    800x600    1024x768    1280x1024
 8位色   0x301      0x303       0x305       0x307
15位色   0x310      0x313       0x316       0x319
16位色   0x311      0x314       0x317       0x31A
24位色   0x312      0x315       0x318       0x31B
-------------------------------------------------------

重启系统在grub中选择title MiniGUI 1.6.2
登录后就可以运行mg-smaples-str-1.6.2/src中的例子了,运行一个helloworld试试效果,如果没有问题正常显示了窗口就说明一切OK了。

(2)qvfb模式
把下载回来的qvfb-1.0.tar.gz解压安装:
[root@fedora7]# tar zxf qvfb-1.0.tar.gz
[root@fedora7]# cd qvfb-1.0
[root@fedora7]# ./configure
[root@fedora7]# make
[root@fedora7]# make install
打开MiniGUI.cfg
[root@fedora7]# gedit /usr/local/etc/MiniGUI.cfg
注意一下段落:
---------------------------------------------------
[system]
# GAL engine
gal_engine=qvfb 

# IAL engine
ial_engine=qvfb 

mdev=/dev/mouse
mtype=IMPS2

[fbcon]
defaultmode=1024x768-16bpp

[qvfb]
defaultmode=640x480-16bpp
display=0
---------------------------------------------------
gal_engine和ial_engine的值应该设置为qvfb,minigui安装后默认设置就是fbcon,所以你要改过来。另外要注意[qvfb]段中的defaultmode,这个是你需要使用的显示器分辨率和色深,后面设置qvfb要和这里一致,否则minigui运行时或报初始化失败的错误。

然后就可以使用qvfb体验miniGUI了:

[root@fedora7]# qvfb &
加上&符号使qvfb进程开启后shell立即返回,否则会阻塞shell进程,需要再开一个shell才可以运行minigui程序。qvfb运行起来后在qvfb中设置虚拟显示器分辨率:File-->Configure,注意Size和Depth要设置的同MiniGUI.cfg中的分辨率和色深一致。
qvfb设置搞定后就可以运行mg-smaples-str-1.6.2/src中的例子了,运行一个helloworld试试效果,Good luck!

三.安装中文字体
可能是涉及版权问题新的开源版Minigui1.6.2中没有提供种中文字体库,可以从minigui-res-1.3.3.tar.gz中借用过来。
解压minigui-res-1.3.3.tar.gz复制font文件夹到/usr/local/lib/minigui/res/覆盖原来的font文件(最好还是把之前的备份,改名为font-bak以便以后恢复)
修改MiniGUI.cfg中部分段落如下:
[systemfont]
font_number=6
font0=rbf-fixed-rrncnn-8-16-ISO8859-1
font1=*-fixed-rrncnn-*-16-GB2312
font2=*-Courier-rrncnn-*-16-GB2312
font3=*-SansSerif-rrncnn-*-16-GB2312
font4=*-Times-rrncnn-*-16-GB2312
font5=*-Helvetica-rrncnn-*-16-GB2312

default=0
wchar_def=1
fixed=1
caption=2
menu=3
control=3

[rawbitmapfonts]
font_number=4
name0=rbf-fixed-rrncnn-8-16-ISO8859-1
fontfile0=/usr/local/lib/minigui/res/font/8x16-iso8859-1.bin
name1=rbf-fixed-rrncnn-16-16-GB2312-0
fontfile1=/usr/local/lib/minigui/res/font/song-16-gb2312.bin
name2=rbf-fixed-rrncnn-6-12-ISO8859-1
fontfile2=/usr/local/lib/minigui/res/font/6x12-iso8859-1.bin
name3=rbf-fixed-rrncnn-12-12-GB2312-0
fontfile3=/usr/local/lib/minigui/res/font/song-12-gb2312.bin

[varbitmapfonts]
font_number=3
name0=vbf-Courier-rrncnn-10-15-ISO8859-1
fontfile0=/usr/local/lib/minigui/res/font/Courier-rr-10-15.vbf
name1=vbf-Helvetica-rrncnn-15-16-ISO8859-1
fontfile1=/usr/local/lib/minigui/res/font/Helvetica-rr-15-16.vbf
name2=vbf-Times-rrncnn-13-15-ISO8859-1
fontfile2=/usr/local/lib/minigui/res/font/Times-rr-13-15.vbf

配置文件的解释:
fontNO=<type>-<facename>-<style>-<width>-<height>-<charset>
type       字体类型,*表示缺省
facename   字体样式
style      字体风格
width      字体宽度
height     字体高度
charset    字符集

 

然后就可以在程序中使用中文字体了.
如果中文显示乱码,检查源程序文件的编码,必须以GB2312编码保存。
建议将系统环境变量设置为中文:export LANG = zh_CN.UTF-8
使用vim编辑源程序,注意Vim中以下三个环境变量:
fileencodings
fileencoding
encoding
查看fileencoding是否为euc-cn,如果不是编译后肯定会是乱码,因为中文字体文件使用GB2312编码。
我的设置Vim设置:
fileencodings = utf-8,chinese,latin1
fileencoding = euc-cn
encoding = utf-8
通过se和set命令查看和设置这些变量。

 

如果出现如下问题提示:

GAL qvfb engine: No driver for bpp 32
GAL: Init GAL engine failure: qvfb.
GDI: Can not initialize graphics engine!

说明显示引擎加载失败.请检查颜色深度!
 

posted @ 2009-10-23 15:25 shelvenn's blog 阅读(275) 评论(0) 编辑

2009年8月3日 #

在RTP会话中,RTCP周期性地给所有参与者发送控制包,应用程序或第三方监控者接受RTCP控制包,从中获取控制信息,估计当前QoS,以便进行传输控制、拥塞处理、错误诊断等。
RTCP报文头部参数首先要区别携带不同控制信息的RTCP报文的类型,RTCP报文的类型主要有以下几种:(1)SR:发送报告,当前活动发送者发送、接收统计;(2)RR:接收报告,非活动发送者接收统计;(3)SDES:源描述项,包括CNAME;(4)BYB:表示结束;(5)APP:应用特定函数。其中最主要的RTCP报文是SR和RR。通常SR报文占总RTCP包数量的25%,RR报文占75%。
通过这5种控制包,RTCP协议实现了以下4个主要功能:
(1)提供数据发布的质量反馈,这是RTCP最主要的功能。作为RTP传输协议的一部分,与其他传输协议的流和阻塞控制有关。反馈对自适应编码控制直接起作用。反馈功能由RTCP发送者和接收者报告执行。
(2)送带有称作规范名字(CNAME)的RTP源持久传输层标识。如发现冲突,或程序重新启动,SSRC标识可改变,接收者需要CNAME跟踪参加者。接收者也需要CNAME与相关RTP连接中给定的几个数据流联系。
(3)根据参与RTP会话的数量调整RTCP的发送速率。
(4)传送最小连接控制信息,如参加者辨识。最可能用在“松散控制”连接,那里参加者自由进入或离开,没有成员控制或参数协调,RTCP充当通往所有参加者的方便通道,但不必支持应用的所有控制通信要求。
3 由RTP包分析影响多媒体数据流实时传输的因素
随着VoIP领域不断发展,满足网络QoS检测需求的应用也成为引人注目的焦点。IP QoS是指IP的服务质量,也是指IP数据流通过网络时的性能。目的就是向用户提供端到端的服务质量保证。有一套度量指标,包括业务可用性、延迟、可变延迟、吞吐量和丢包率等,现就项目中在上海阿尔卡特网络支援系统有限公司NGN实验室中所得到的RTP和RTCP包进行分析,主要研究其中3个因素,从而达到对实时流媒体数据进行监控的目的。
3.1 抖动
抖动会引起端到端时延的增加,引起语音质量的降低。在音频数据的传输过程中,由于传输延迟的不稳定而造成相邻数据包接收时刻间隔不稳定,从而产生抖动。消除抖动的主要依据就是RTP包的首部中包含的时间戳字段。时间戳标志着该段音频数据中第一个采样点的采样时间。每两个RTP包的抖动可以用其RTP包中的RTP时戳和接收的时刻进行计算。
关于包的传送时间,接收者最先了解到的是它的时间戳和接收者当前时间之间的差值。该差值是:Di=Ri-Si,表示从包被盖上戳开始,到它在信源的输出链路上被实际发送为止,其中的传送时间和某个机器时间。RFC 1889建议使用NTP来完成端点的端到端同步,但是也有非同步端点实现存在。
包i和包j之间增加的延迟差(二阶效应)计算公式如下:设Rj代表第j个包的接受时刻,Sj代表第j个包的RTP时戳值,则第i个RTP报文与第j个RTP报文间的抖动为D(i,j)
在生成RTCP报文时,其应当传送的时延抖动的值可用以下公式进行递推计算
其中:J为要传送的时延抖动值。对后一项除以16是为了消除连带噪声。
抖动是分组交换的必然结果,影响抖动的因素一般和网络的拥塞程度有关。由于语音同数据在同一条物理线上传输,语音数据通常会由于数据报文占用了物理线路而导致阻塞。解决抖动通常采用缓冲队列来解决(在网关、IAD上均有JitterBuffer来消除抖动),每收到一个数据包,先将其放人缓冲区,应用程序在缓冲区另一端取数据,只要缓冲区足够大,抖动一定能被平滑掉。而错序是由于网络拥挤而使某些后发的数据包先到达收端而引起的,只要设置足够大的缓冲区来对数据包重新排序,就能解决这个问题。或者需要IP承载网采用QoS策略,保证语音数据的最高优先级,得到最先发送和获得高带宽也是解决抖动问题的主要手段。
3.2 时延
时延是处理和传输导致数据不能按时到达的延迟,是影响流媒体数据传输的一个主要因素。话音信号在端到端传输过程中受到的时延迟滞通常包括:编解码器引入的时延、打包时延、去抖动时延、承载网上的传输节点中排队、服务处理时延。这些时延累计的总和将影响话质,导致回声干扰和交互性的劣化。对于VoIP系统,规定时延一般控制在150 ms内。
分组语音网络中的延迟可分为固定延迟和可变延迟。前者相对容易得到,笔者不作考虑。在计算丢包率时,主要考虑可变延迟。丢包判定等待时限Twait设定的大小在很大程度上影响丢包率计算的准确性,也就是可变延迟的影响,它与语音包的传输延迟Ttrf有关,Twait越大等待时限就越长。但不能超过保证语音流连续播放的时间上限Tmax(Tmax一般取250 ms),即:Twait=min(Twait,Tmax)。Ttrf可根据RTCP协议的SR控制包中的NTP(Network Time Protoco1)时间戳计算得到,见图2。
使用RTCP包计算网络时延要求在两个端点之间传送发送方报告(RTCPSR)。RTCP包中的报文类型以二进制表示,其中十进制的200代表此包的报文类型为SR(发送报文)。收到200的包类型,表明可以提供足够的信息,计算与这个SSRC有关的发送方的延迟。那么就计算发送方流(SSRC)和源流(SSRC_n)之间的双向延迟。每次在收到RTCPSR包时,必须保存上一个时间戳(Last SR Timestamp,LSR)和DLSR(Delay SinceLast SR)值。SSRC的LSR域是从该SSRC接收到的NTP时间戳的中间32 bit,只要接收到一个NTP时间戳就可以了。如果没有接收到NTP时间戳,那么这个域应该设置为0。信源可能无法获得时钟,或者其他可接受的消逝的时间。因此,在会话持续期间,特定SSRC的这个域可能保持为0。DLSR域是端点从发送者那里接收到一个SR以后过去的时问。这个计数器的每一步代表1/65 536 s。一般来说,在NTP时戳,LSR或DLSR等于零时,不应该计算双向延迟。网络传输时延Ttrf即为双向延迟的一半。具体计算方法如下:设目的网关接收源网关发送的控制包SRi,经过一段时延DLSR后,向源网关发送相应的响应SR控制包SRj,并从控制包SRj中的NTP时间戳域中提取中间的32 bit作为控制包SRi的LSR。如果没有收到源网关发出的SR控制包,则L5R和DLSR都置为0。最后,将DLSR和LSR的值填入到控制包SRj的相应域中。设源网关接收到控制包SRj时刻为A,则当前网络传输延时为
Ttrf=(A-LSR-DLSR)/2 (3)
由实验包中的数据得出的Ttrf是使用32 bit归一化NTP时戳计算的,所以要把延迟转换成毫秒单位,结果中前16 bit将与延迟秒数对应,后16 bit与1/65 536 s的数字对应。因此,上面16 bit必须乘以1 000,把秒转换成毫秒。下面16 bit必须除以65 536,然后再乘以1 000。两个因数之和可以得到最终结果。
实时语音传输要求端对端时延不能太大,端到端的时延要求分成4个等级:≤150ms(最好)、150~250ms(较好)、250~450ms(一般)和450ms以上(差)。可以通过设定IP优先级、路由选择、RED(随机早期检查)等技术来缩短IP网络时延。
①优先级是指对每个数据包的级别进行分类,不同级别的数据包在网络进行预留带宽分配、通过顺序、时延抖动、丢包等方面处理时,所受到的待遇不同,这样可以确保语音、图像等对实时性要求比较高的数据包优先传输,以提高传输质量。
②选择合适的路由绕过那些负载过重的路由器,直接连到主干网进行传输。
③当网络拥挤发生拥塞时,RED就优先丢弃一些对话音影响较小的数据包,并让终点站降低传输速率,避免路由器或交换设备缓冲区溢出。
此外,采用流量控制、队列管理、数据包保护技术也可以提高网络的管理性能,从而缩小网络时延。
3.3 丢包率
丢包率定义为在网络中传输数据包时丢弃数据包的最高比率。数据包丢失一般是由网络拥塞引起的。丢包对VoIP语音质量的影响较大,在本实验环境中,采用G.711时,当丢包率大于10%时,已不能接受,而在丢包率为5%时,基本还可以接受,因此,要求IP承载网的丢包率小于5%。
丢包率为单位时间内丢失的语音包的数目。检测的具体方法是统计语音流中的连续2个SR(发送端报告控制包)的时间间隔Tint内实际接收语音包的数目Nreal,然后按下面公式来计算丢包率Rlost
Rlost=(Nexp-Nreal)Nexp (4)
其中:Nexp为期望的语音包数目,它等于在Tint内接收到语音包的最小序号Ni和最大序号Nj之差,用下面公式来表示:Nexp=Nj-Ni,实际接收到的语音包Nreal可能包括在Tint之前的迟到包(由于语音数据采用UDP协议传输,不存在重复包),所以计算所得的丢包率会出现负数。此时丢包率定义为0。
丢包率的计算可在每个丢包判定等待时限内计算,也可按照一定的时间间隔来计算一次,而这个时间间隔要根据网络的负载、网络的稳定情况来确定。为了减少网络的负载,不是每次计算的丢包率都要反馈给源网关的,而是事先根据网络用户期望的语音质量来确定两个丢包率状态的阈值R1和R2。对于音频而言,取R1=3%,R2=8%。当Rlost>R2时,将丢包率信息的标志位,置为“overload”(拥塞),表示网络处于拥塞状态等。
 
 
根据RTP头中的sequence number域,可以在接收端很轻易地发现包丢失,为丢包修复奠定基础。在实际使用中发现,绝大多数丢包是单个丢包,两个或两个以上包丢失的比例较小。针对单个包的丢失,传统的丢包处理方法有两种:一种方法是重发,但在传输语音数据时,重发将引起播放质量下降,出现无法识别的话音或回音现象;另一种方法是忽略,这同样会影响播放质量。更好的方案是使用拆分法优化丢包损失。拆分法的基本思想是:在发送端把原来要打入一个RTP包的话音数据按照采样间隔分成两块,然后采用相同的压缩算法分别压缩、打入RTP包,并标记相同的时印进行传输。在接收方执行相反的过程,把解压缩后的数据采样、合并、回放。
如果某个RTP包在传输过程中丢失,那么丢失的只是原数据包按采样间隔的一半信息,接收端可以用接受到的另一半信息,利用插值等方法恢复出原话音包的大部分信息,从而使话音质量不至于下降太多。
posted @ 2009-08-03 18:10 shelvenn's blog 阅读(380) 评论(0) 编辑

RTCP:RTP 控制协议(RTP Control Protocol)
RTP 控制协议(RTCP)采用与数据包相同的分发机制,将控制包周期性传输到所有会话参与者中。底层协议必须提供数据和控制包的
多路发送,例如使用不同的 UDP 端口号。RTCP 主要完成四个功能服务:
      RTCP 提供数据分发质量反馈信息。这是 RTP 作为传输协议的部分功能并且它涉及到了其它传输协议的流控制和拥塞控制。
      RTCP 为 RTP 源携带一个持久性传输层标识符,称为规范名或 CNAME。由于一旦发现冲突或程序重启时,SSRC 标识符会随之改变,
所以接收方需要 CNAME 来跟踪每一个参与者。同时接收方还要求 CNAME 能够与一组相关 RTP 会话中来自于给定参与者的多重数据
流相关联,例如同步视频和音频。
      上述前两个功能要求所有的参与者都要发送 RTCP 包,因此必须控制速率以便 RTP 按比例增加大量的参与者。通过每一个参与者发
送各自的控制包给其它所有参与者,每一个参与者能够独立观察到参与者数量,该数量可用来计算控制包的发送速率。
OPTIONAL 功能用于传送最少会话控制信息,例如在用户界面显示参与者标识。这对于“松散受控”会话(在没有成员控制或阐述协
商的情况下,参与者可以加入或退出该会话)是非常有用的。
      上述功能1-3适用于所有环境,尤其是 IP 组播环境。RTP 应用程序设计者应该避免设计只能工作于单播模式并且不能增加到大量数
量的机制。在某些情况下如单向链接中,不可能有来自接收方的反馈,所以 RTCP 的传输就可能由发送方和接收方分别独立控制。
协议结构
2 3 8 16 bit
Version P RC Packet type
Length
Version ― 识别 RTP 版本。RTP 数据包中的该值与 RTCP 数据包中的一样。 当前规范定义的版本值为2。
P ― 间隙(Padding)。设置时,RTCP 数据包包含一些其它 padding 八位位组,它们不属于控制信息。Padding 的最后八位是用于
计算应该忽略多少间隙八位位组。一些加密算法中需要计算固定块大小时也可能需要使用 Padding 字段。在一个复合 RTCP 数据包
中,只有最后的个别数据包中才需要使用 padding,这是因为复合数据包是作为一个整体来加密的。
RC ― 接收方报告计数。包含在该数据包中的接收方报告块的数量,有效值为0。
Packet type ― 包括常量200,识别这是一个 RTCP SR 数据包。
Length ― RTCP 数据包的大小(32位字减去1),包含头和任意间隙(偏移量的引入使得0成为有效值,并避免了扫描复合 RTCP 数
据包过程中的无限循环现象,而采用32位字计数方法则避免了对4的倍数的有效性校验)。
posted @ 2009-08-03 18:06 shelvenn's blog 阅读(157) 评论(0) 编辑

2009年7月30日 #

H.264 RTP payload 格式

H.264 视频 RTP 负载格式

1. 网络抽象层单元类型 (NALU)

NALU 头由一个字节组成, 它的语法如下:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

F: 1 个比特.
  forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.

NRI: 2 个比特.
  nal_ref_idc. 取 00 ~ 11, 似乎指示这个 NALU 的重要性, 如 00 的 NALU 解码器可以丢弃它而不影响图像的回放. 不过一般情

况下不太关心

这个属性.

Type: 5 个比特.
  nal_unit_type. 这个 NALU 单元的类型. 简述如下:

  0     没有定义
  1-23  NAL单元  单个 NAL 单元包.
  24    STAP-A   单一时间的组合包
  24    STAP-B   单一时间的组合包
  26    MTAP16   多个时间的组合包
  27    MTAP24   多个时间的组合包
  28    FU-A     分片的单元
  29    FU-B     分片的单元
  30-31 没有定义

2. 打包模式

  下面是 RFC 3550 中规定的 RTP 头的结构.

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |V=2|P|X|  CC   |M|     PT      |       sequence number         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                           timestamp                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |           synchronization source (SSRC) identifier            |
      +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
      |            contributing source (CSRC) identifiers             |
      |                             ....                              |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  负载类型 Payload type (PT): 7 bits
  序列号 Sequence number (SN): 16 bits
  时间戳 Timestamp: 32 bits
 
  H.264 Payload 格式定义了三种不同的基本的负载(Payload)结构. 接收端可能通过 RTP Payload
  的第一个字节来识别它们. 这一个字节类似 NALU 头的格式, 而这个头结构的 NAL 单元类型字段
  则指出了代表的是哪一种结构,

  这个字节的结构如下, 可以看出它和 H.264 的 NALU 头结构是一样的.
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+
  字段 Type: 这个 RTP payload 中 NAL 单元的类型. 这个字段和 H.264 中类型字段的区别是, 当 type
  的值为 24 ~ 31 表示这是一个特别格式的 NAL 单元, 而 H.264 中, 只取 1~23 是有效的值.
  
  24    STAP-A   单一时间的组合包
  24    STAP-B   单一时间的组合包
  26    MTAP16   多个时间的组合包
  27    MTAP24   多个时间的组合包
  28    FU-A     分片的单元
  29    FU-B     分片的单元
  30-31 没有定义

  可能的结构类型分别有:

  1. 单一 NAL 单元模式
     即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的
  NALU 头类型字段是一样的.

  2. 组合封包模式
    即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24.
  那么这里的类型值分别是 24, 25, 26 以及 27.

  3. 分片封包模式
    用于把一个 NALU 单元封装成多个 RTP 包. 存在两种类型 FU-A 和 FU-B. 类型值分别是 28 和 29.

2.1 单一 NAL 单元模式

  对于 NALU 的长度小于 MTU 大小的包, 一般采用单一 NAL 单元模式.
  对于一个原始的 H.264 NALU 单元常由 [Start Code] [NALU Header] [NALU Payload] 三部分组成, 其中 Start Code 用于标示

这是一个

NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", NALU 头仅一个字节, 其后都是 NALU 单元内容.
  打包时去除 "00 00 01" 或 "00 00 00 01" 的开始码, 把其他数据封包的 RTP 包即可.

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |F|NRI|  type   |                                               |
      +-+-+-+-+-+-+-+-+                                               |
      |                                                               |
      |               Bytes 2..n of a Single NAL unit                 |
      |                                                               |
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


  如有一个 H.264 的 NALU 是这样的:

  [00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

  这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.

  封装成 RTP 包将如下:

  [ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ]

  即只要去掉 4 个字节的开始码就可以了.


2.2 组合封包模式

  其次, 当 NALU 的长度特别小时, 可以把几个 NALU 单元封在一个 RTP 包中.

 
       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          RTP Header                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |STAP-A NAL HDR |         NALU 1 Size           | NALU 1 HDR    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                         NALU 1 Data                           |
      :                                                               :
      +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |               | NALU 2 Size                   | NALU 2 HDR    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                         NALU 2 Data                           |
      :                                                               :
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


2.3 Fragmentation Units (FUs).

  而当 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包. 也称为 Fragmentation Units (FUs).
 
       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      | FU indicator  |   FU header   |                               |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
      |                                                               |
      |                         FU payload                            |
      |                                                               |
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

      Figure 14.  RTP payload format for FU-A

   The FU indicator octet has the following format:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

   The FU header has the following format:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+


3. SDP 参数

  下面描述了如何在 SDP 中表示一个 H.264 流:

  . "m=" 行中的媒体名必须是 "video"
  . "a=rtpmap" 行中的编码名称必须是 "H264".
  . "a=rtpmap" 行中的时钟频率必须是 90000.
  . 其他参数都包括在 "a=fmtp" 行中.

  如:

  m=video 49170 RTP/AVP 98
  a=rtpmap:98 H264/90000
  a=fmtp:98 profile-level-id=42A01E; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==

  下面介绍一些常用的参数.

3.1 packetization-mode:
  表示支持的封包模式.
  当 packetization-mode 的值为 0 时或不存在时, 必须使用单一 NALU 单元模式.
  当 packetization-mode 的值为 1 时必须使用非交错(non-interleaved)封包模式.
  当 packetization-mode 的值为 2 时必须使用交错(interleaved)封包模式.
  这个参数不可以取其他的值.

3.2 sprop-parameter-sets:
  这个参数可以用于传输 H.264 的序列参数集和图像参数 NAL 单元. 这个参数的值采用 Base64 进行编码. 不同的参数集间用","

号隔开.
 
3.3 profile-level-id:
  这个参数用于指示 H.264 流的 profile 类型和级别. 由 Base16(十六进制) 表示的 3 个字节. 第一个字节表示 H.264 的

Profile 类型, 第

三个字节表示 H.264 的 Profile 级别:
 
3.4 max-mbps:
  这个参数的值是一个整型, 指出了每一秒最大的宏块处理速度.

 

posted @ 2009-07-30 14:11 shelvenn's blog 阅读(967) 评论(0) 编辑

2009年6月7日 #

gcc和g++都是GNU(组织)的一个编译器。

误区一:gcc只能编译c代码,g++只能编译c++代码
两者都可以,但是请注意:
1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。
2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。
 
误区二:gcc不会定义__cplusplus宏,而g++会
实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。
 
误区三:编译只能用gcc,链接只能用g++
严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。

gcc和g++的区别 

我们在编译c/c++代码的时候,有人用gcc,有人用g++,于是各种说法都来了,譬如c代码用gcc,而c++代码用g++,或者说编译用gcc,链接用g++,一时也不知哪个说法正确,如果再遇上个extern "C",分歧就更多了,这里我想作个了结,毕竟知识的目的是令人更清醒,而不是更糊涂。
 
误区一:gcc只能编译c代码,g++只能编译c++代码

两者都可以,但是请注意:
1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的,例如:
#include <stdio.h>
int main(int argc, char* argv[]) {
   if(argv == 0) return;
   printString(argv);
   return;
}
int printString(char* string) {
  sprintf(string, "This is a test.\n");
}
如果按照C的语法规则,OK,没问题,但是,一旦把后缀改为cpp,立刻报三个错:“printString未定义”;
“cannot convert `char**' to `char*”;
”return-statement with no value“;
分别对应前面红色标注的部分。可见C++的语法规则更加严谨一些。
2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。
 
误区二:gcc不会定义__cplusplus宏,而g++会

实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。
 
误区三:编译只能用gcc,链接只能用g++

严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。
 
误区四:extern "C"与gcc/g++有关系

实际上并无关系,无论是gcc还是g++,用extern "c"时,都是以C的命名方式来为symbol命名,否则,都以c++方式命名。试验如下:
me.h:
extern "C" void CppPrintf(void);
 
me.cpp:
#include <iostream>
#include "me.h"
using namespace std;
void CppPrintf(void)
{
     cout << "Hello\n";
}
 
test.cpp:
#include <stdlib.h>
#include <stdio.h>
#include "me.h"       
int main(void)
{
    CppPrintf();
    return 0;
}
 
1. 先给me.h加上extern "C",看用gcc和g++命名有什么不同

[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   CppPrintf, @function
[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   CppPrintf, @function
完全相同!
              
2. 去掉me.h中extern "C",看用gcc和g++命名有什么不同

[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   _Z9CppPrintfv, @function
[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   _Z9CppPrintfv, @function
完全相同!
【结论】完全相同,可见extern "C"与采用gcc/g++并无关系,以上的试验还间接的印证了前面的说法:在编译阶段,g++是调用gcc的。

posted @ 2009-06-07 15:36 shelvenn's blog 阅读(376) 评论(0) 编辑

RTSP简介(ZT)
Real Time Streaming Protocol或者RTSP(实时流媒体协议),是由Real network 和Netscape共同提出的如何有效地在IP网络上传输流媒体数据的应用层协议。RTSP提供一种可扩展的框架,使能够提供能控制的,按需传输实时数据,比如音频和视频文件。源数据可以包括现场数据的反馈和存贮的文件。rtsp对流媒体提供了诸如暂停,快进等控制,而它本身并不传输数据,rtsp作用相当于流媒体服务器的远程控制。传输数据可以通过传输层的tcp,udp协议,rtsp也提供了基于rtp传输机制的一些有效的方法。
RTSP消息格式:
       RTSP的消息有两大类,一是请求消息(request),一是回应消息(response),两种消息的格式不同.
请求消息:
       方法 URI RTSP版本       CR LF
       消息头 CR LF          CR LF         
       消息体 CR LF
其中方法包括OPTION回应中所有的命令,URI是接受方的地址,例如:rtsp://192.168.20.136  RTSP版本一般都是 RTSP/1.0.每行后面的CR LF表示回车换行,需要接受端有相应的解析,最后一个消息头需要有两个CR LF 回应消息:
       RTSP版本 状态码 解释      CR LF
       消息头 CR LF          CR LF
       消息体 CR LF
其中RTSP版本一般都是RTSP/1.0,状态码是一个数值,200表示成功,解释是与状态码对应的文本解释.
简单的rtsp交互过程:
       C表示rtsp客户端,S表示rtsp服务端
1.C->S:OPTION request       //询问S有哪些方法可用
1.S->C:OPTION response    //S回应信息中包括提供的所有可用方法
 
2.C->S:DESCRIBE request      //要求得到S提供的媒体初始化描述信息
2.S->C:DESCRIBE response    //S回应媒体初始化描述信息,主要是sdp
 
3.C->S:SETUP request             //设置会话的属性,以及传输模式,提醒S建立会话
3.S->C:SETUP response          //S建立会话,返回会话标识符,以及会话相关信息
 
4.C->S:PLAY request        //C请求播放
4.S->C:PLAY response            //S回应该请求的信息
 
S->C:发送流媒体数据
5.C->S:TEARDOWN request      //C请求关闭会话
5.S->C:TEARDOWN response //S回应该请求
 
上述的过程是标准的、友好的rtsp流程,但实际的需求中并不一定按部就班来。
其中第3和4步是必需的!第一步,只要服务器客户端约定好,有哪些方法可用,则option请求可以不要。第二步,如果我们有其他途径得到媒体初始化描述信息(比如http请求等等),则我们也不需要通过rtsp中的describe请求来完成。第五步,可以根据系统需求的设计来决定是否需要。
RTSP中常用方法:
1.OPTION方法
目的是得到服务器提供的可用方法:
OPTIONS rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 1         //每个消息都有序号来标记,第一个包通常是option请求消息
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
 
服务器的回应信息包括提供的一些方法,例如:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 1         //每个回应消息的cseq数值和请求消息的cseq相对应
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, SCALE, GET_PARAMETER //服务器提供的可用的方法
2.DESCRIBE方法
C向S发起DESCRIBE请求,为了得到会话描述信息(SDP):
DESCRIBE rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 2
token:
Accept: application/sdp
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
 
服务器回应一些对此会话的描述信息(sdp):
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 2
x-prev-url: rtsp://192.168.20.136:5000
x-next-url: rtsp://192.168.20.136:5000
x-Accept-Retransmit: our-retransmit
x-Accept-Dynamic-Rate: 1
Cache-Control: must-revalidate
Last-Modified: Fri, 10 Nov 2006 12:34:38 GMT
Date: Fri, 10 Nov 2006 12:34:38 GMT
Expires: Fri, 10 Nov 2006 12:34:38 GMT
Content-Base: rtsp://192.168.20.136:5000/xxx666/
Content-Length: 344
Content-Type: application/sdp
 
v=0        //以下都是sdp信息
o=OnewaveUServerNG 1451516402 1025358037 IN IP4 192.168.20.136
s=/xxx666
u=http:///
e=admin@
c=IN IP4 0.0.0.0
t=0 0
a=isma-compliance:1,1.0,1
 
a=range:npt=0-
m=video 0 RTP/AVP 96    //m表示媒体描述,下面是对会话中视频通道的媒体描述
a=rtpmap:96 MP4V-ES/90000
a=fmtp:96 profile-level-id=245;config=000001B0F5000001B509000001000000012000C888B0E0E0FA62D089028307
a=control:trackID=0//trackID=0表示视频流用的是通道0
 
3.SETUP方法
客户端提醒服务器建立会话,并确定传输模式:
SETUP rtsp://192.168.20.136:5000/xxx666/trackID=0 RTSP/1.0    
CSeq: 3
Transport: RTP/AVP/TCP;unicast;interleaved=0-1      
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
//uri中带有trackID=0,表示对该通道进行设置。Transport参数设置了传输模式,包的结构。接下来的数据包头部第二个字节位置就是interleaved,它的值是每个通道都不同的,trackID=0的interleaved值有两个0或1,0表示rtp包,1表示rtcp包,接受端根据interleaved的值来区别是哪种数据包。
 
服务器回应信息:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 3
Session: 6310936469860791894     //服务器回应的会话标识符
Cache-Control: no-cache
Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=6B8B4567
4.PLAY方法
客户端发送播放请求:
PLAY rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 4
Session: 6310936469860791894
Range: npt=0.000-      //设置播放时间的范围
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
服务器回应信息:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 4
Session: 6310936469860791894
Range: npt=0.000000-
RTP-Info: url=trackID=0;seq=17040;rtptime=1467265309     
//seq和rtptime都是rtp包中的信息
5.TEARDOWN方法
客户端发起关闭请求:
TEARDOWN rtsp://192.168.20.136:5000/xxx666 RTSP/1.0
CSeq: 5
Session: 6310936469860791894
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
服务器回应:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 5
Session: 6310936469860791894
Connection: Close
 
以上方法都是交互过程中最为常用的,其它还有一些重要的方法如get/set_parameter,pause,redirect等等
ps:
sdp的格式
v=<version>
o=<username> <session id> <version> <network type> <address type> <address>
s=<session name>
i=<session description>
u=<URI>
e=<email address>
p=<phone number>
c=<network type> <address type> <connection address>
b=<modifier>:<bandwidth-value>
t=<start time> <stop time>
r=<repeat interval> <active duration> <list of offsets from start-time>
z=<adjustment time> <offset> <adjustment time> <offset> ....
k=<method>
k=<method>:<encryption key>
a=<attribute>
a=<attribute>:<value>
m=<media> <port> <transport> <fmt list>
v = (协议版本)
o = (所有者/创建者和会话标识符)
s = (会话名称)
i = * (会话信息)
u = * (URI 描述)
e = * (Email 地址)
p = * (电话号码)
c = * (连接信息)
b = * (带宽信息)
z = * (时间区域调整)
k = * (加密密钥)
a = * (0 个或多个会话属性行)
时间描述:
t = (会话活动时间)
r = * (0或多次重复次数)
媒体描述:
m = (媒体名称和传输地址)
i = * (媒体标题)
c = * (连接信息 — 如果包含在会话层则该字段可选)
b = * (带宽信息)
k = * (加密密钥)
a = * (0 个或多个媒体属性行)

posted @ 2009-06-07 11:34 shelvenn's blog 阅读(995) 评论(0) 编辑

2008年8月25日 #

                        DirectDraw基础篇(学东西还是基础的好哦!)
第一节 DirectDraw简介
Grubers的一个观点是DirectDraw“只是一个bltting发动机”。这是相当准确的,但却太简化了。更准确地讲,DirectDraw是一个可以提供软件仿真测试的独立于硬件设备的bltting发动机。DirectDraw的主要用途是尽可能快、尽可能可靠并且尽可能连续地将图形考贝到视频显示设备上。
   另外一个定义DirectDraw的方式是把它作为一个视频存储器管理器,同常规的存储器管理器一样,DirectDraw发放存储器信息包,跟踪每一个信息包的状态。信息包可以随意地创建、复制、修改或破坏,同时这些操作的细节被程序员隐含起来,这样讲是过于简单了。此外,DirectDraw是能够使用系统RAM和视频RAM的。存储器管理器也经常被设计成和主要目标一样强健,而不只是追求性能。对于DirectDraw,性能只是设计目标之一。
从技术角度讲,DirectDraw是随同设备驱动器集合的便携式API。DirectDraw设计成完全避开传统意义上的Windows图形机构(GDI,或称图形设备接口)。GDI由于性能低而名声不好,所以DirectDraw的设备独立性在提供最佳性能方面是至关重要的。

第二节 DirectDraw基本概念
1. 显示模式
显示模式是由允许将要显示的图形输出的显示硬件支持的可视配置。最常用的显示模式属性是分辨率。Windows使用的显示模式的默认值是640×480的分辨率。这意味着,水平方向有640个像素,垂直方向有480个像素。其他一些常见的显示模式分辨率有800×600,1024×768。一些显示卡支持Mode X显示模式。一个典型的Mode X显示模式的分辨率为320×200。
   显示模式也随像素深度的变化而变化。像素深度决定着每一个像素所容纳的多少不同的值,因而也就可以显示多少种颜色。例如对于8位像素深度的显示模式,每个像素能够再现256种颜色的一种。像素深度为16位的显示模式支持65536种颜色(即2的n次方),典型的像素深度为8、16、24和32位。
   显示模式由安装在机器中的显示设备或视频卡支持。显示设备有自己的RAM,从计算机的RAM中分离出来。我们把位于显示设备中的存储器称为显示RAM,而把常规存储器称为系统RAM。
支持一个给定的显示模式的RAM的容量取决于显示模式的分辨率和像素深度。例如,640×480×8(640×480像素,深度为8位)的显示模式需要307200字节。1024×768×16的显示模式需要1572864字节。支持显示模式的存储器必须是显示RAM。一个给定显示设备所支持的显示模式因此也被可以利用的显示RAM的容量所限制。例如,1024×768×16的显示模式因为需要一兆字节以上的内存,所以就不能被只有一兆字节RAM的显示设备所支持。
DirectDraw的一个主要特征是显示模式切换。这允许一个DirectDraw应用程序检测和激活所安装的显示设备所支持的任何显示模式。我们将在第4章中讨论显示模式切换的细节。
2. 硬件加速
DirectDraw具有最优性能的最重要的原因是,它尽可能地使用硬件加速来进行设计。硬件加速发生在当显示设备能够用建立在显示设备之中的处理功能执行操作时。硬件加速具有两个优点,首先,当硬件加速出现的时候,硬件按指定要求设计成支持图形操作,这提供了执行给定任务的最快的方法:其次,硬件加速使得计算主处理器从执行操作中解放出来,这使得主处理器可以执行其他任务。
3. 表面
表面是存储器的一个矩形部分的DirectDraw术语,通常包括图像数据。该存储器通常用来表示一个存在于显示RAM或系统RAM中的表面。驻留在显示RAM中的表面享有超高性能,因为绝大多数显示硬件不能对系统RAM直接存取。
表面分为向大类,最简单的类型是脱离屏幕表面。脱离屏幕表面可以驻留在显示RAM中或系统RAM中,但却不能被显示。这类表面一般用于存储子画面和背景。
另一方面,一个主表面是可在屏幕上看到的视频RAM的一部分部分。所有的DirectDraw程序(可以提供视频输出)都拥有主表面。主表面必须驻留在显示RAM中。
主表面通常很复杂,或是可翻转的。可翻转表面允许页面翻转,这是一项整个表面的内容可以通过一个硬件操作而瞬时可见的技术。页面翻转用于许多基于DirectDraw或其他的图形应用程序中,因为它可以产生相当平滑、不闪烁的动画。一个可翻转的主表面实际上是两个表面,一个可见,另一个不可见。不可见的表面称为后备缓冲区。当发生页面翻转时,以前是后备缓冲区的表面就成为可见的,而以前可见的表面则成为后备缓冲区。
   离屏表面和主表面都有两类:调色板的和无调色板的。在DirectDraw中,只有8位表面是调色板表面。调色板表面并不包含色彩数据,但是却引入一个色彩表。该表称为调色板。像素深度为16、24或32位的表面是无调色板表面。无调色板表面存储实际色彩值,而不引入调色板。
因为在无调色板表面中的每一个像素都存储色彩数据,所以知道表面的像素格式是很重要的。像素格式描述了存储于像素中的红色、绿色和蓝色(RGB)元件的方式。像素格式随像素深度、显示模式和硬件设计的不同而不同,在第5章中可以了解所有的像素格式。
4. Bltting
Bltting是用于复制的图形语言。典型的blt操作是将离屏表面的内容拷贝到一个后备缓冲区中。当Bltting通过硬件完成的时候,执行速度相当快。如果无法得到硬件加速,DirectDraw将使用一个软件操作来仿真blt。这种仿真操作虽然也能够完成任务,但却比硬件慢得多.一般只有驻留在显示RAM中的表面能够通过使用显示硬件来完成blt。
   blt操作调用一个源表面和一个目标表面,源表面的内容被拷贝到目标表面中。源表面中的内容在操作中不会改变,只有目标表面受blt的影响。blt操作也并不需要使用全部的源表面或目标表面。源表面中的任何矩形区域可以被放置于目标表面中的任何位置。
不规则形状表面的bltting(例如典型的子画面)是以透明方式完成的。透明性是通过指定表面中某个不被blt操作拷贝的像素而获得的。像素值通过使用色彩键码给以标志。
色彩键码可以附加到源表面或目标表面上。源色彩键码是很普遍的。源色彩键码允许透明性,因为源表面中的像素值并未被考贝。至于目标色彩,只有目标表面中通过色彩所指定的像素值能够被源表面的内容所覆盖。
DirectDraw也支持一些特定的操作,包括拉伸、压缩、镜像映射,以及混合等。这些功能的实现往往取决于显示硬件。DirectDraw能够仿真其中的某些操作,但是跟性能相比,价格往往是昂贵的。
DirectDraw也有不能仿真的功能(例如目标色彩键码)。使用这些功能是冒险的,除非该功能为所安装的显示硬件支持,否则使用该功能的操作将失败。这给DirectDraw的开发者带来两种基本选择:要么放弃使用这些功能:要么往应用程序中增加定制软件。
5. 调色板
   使用8位显示模式的应用程序需要提供调色板。调色板就是任何时候都可以使用的色彩表。如果8位显示模式不需要调色板,应用程序将被迫使用256种颜色的固定设置。调色板允许用户定义将要使用的256种颜色之一。
   当你使用调色板显示模式时,必须保证在应用程序中的图像也使用同一调色板。如果没有做到这一点,所显示的一些或全部图像中将出现错误的颜色。调色板也会带来麻烦,尤其是用一个调色板来显示大量图像的时候。调色板也有一些优势。正如前面提到的,调色板允许在一个有限色彩的场合使用最多的色彩。调色板也允许调色板动画。
   调色板动画是动画通过改变调色板项目,而不是改变像素值来执行的技术,这就使得一个屏幕上的很多像素可以瞬时改变颜色。对于一些有限的应用程序,诸如分配的、重复的动画,调色板动画很有用处。
6. 剪裁
理想状态下,一个blt操作就是整个表面被blt成为另一个表面。通常源表面被blt成为目标表面的边,或者目标表面被另一个表面或窗口遮蔽。像这样的情况就需要进行剪裁。剪裁只允许一部分或一个表面的一部分被blt。
在编写窗口DirectDraw应用程序时经常用到剪裁,因为这些应用程序必须遵守Windows桌面的规则。我们将在本章后面讨论窗口应用程序。
DirectDraw提供全矩形剪裁支持。也有这种情况,就是付费提供定制剪裁例程,我们将在第3章中研究定制剪裁解决方案。
7. 其他表面
离屏表面和主表面(具有任选的后备缓冲区)是绝大多数DirectDraw应用程序的主干。然而一些其他的表面就有不同,包括重叠表面、alpha通道表面、Z-缓冲区以及3D设备表面等。
重叠表面是硬件单色画面,因而也就在仅在支持重叠的显示硬件上获得。和软件单色画面不同,它可以被移动而不需要背景图像被恢复。
alpha通道表面用来执行alpha调配。Alpha调配是透明的高级形式。允许表面以透明度或半透明方式来拷贝。alpha通道表面可用来控制每一像素的透明度设置。alpha通道表面的深度有1、2、4、8位。1位深度alpha通道表面仅支持两种透明设置,不透明(非透明)或不可见(全透明)。另一方面,8位alpha通道表面允许256种不同的透明度设置。Alpha调配是不被DirectDraw仿真的功能的一个例子。为了使用alpha调配,因而就需要有支持它的显示硬件或建立在应用程序之中的定制调配方案。
Z-缓冲区和3D设备表面用于3D应用程序中。这些类型的表面已被特别地加入到DirectDraw之中,以支持Direct3D。Z-缓冲区用于景象绘制时期,以跟踪景象中离浏览者最近的对象,从而该对象可以在其他对象的前面出现。3D设备表面可以用来作为Direct3D绘制目标的表面。本书并不包括Z-缓冲区或3D设备。

第三节 元件对象模型(COM)
1.Microsoft的COM规格
DirectDraw根据Microsoft的COM(Component Object Model)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构,COM是一个大的项目,但是它并不是本软件讨论的对象。我们讨论COM只是为了方便使用DirectDraw进行编程。
COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
2. 对象和接口的比较
COM在对象和接口之间具有很大的区别。COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。因为我们无法存取COM对象,所以这里绝大多数时候是根据接口来讲的。
一个COM对象能够支持多个接口。这听起来像个特例,但是它经常出现,因为根据COM规格,一个COM接口一旦定义之后,就不能再被改变或增加。这样做是为保证旧程序在一个COM对象升级的时候不会被停止使用。这个初始接口始终存在,一个新的、替换的接口在提供存取对象的新的函数性的时候才被提供。
3. IUnknown接口
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即界面)。DirectDraw接口总以“I”开头。但是在文献中经常看不到这个标志。以后提到接口时也将省略“I”标志。
   IUnknown接口将提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef()
●Release()
●QueryInterface()
AddRef()和Release()成员函数为称为生命期封装(lifetime encapsulation)的COM功能提供支持。生命期封装是一个将每一个对象根据它自己的结构放置的协议。
生命期封装通过引用值来实现。每一个对象拥有一个可以跟踪对象的指针数,或者引用的内部值。当对象创建之后,该值为1。如果附加的接口或接口的指针被创建,则该值递增。与此类似,如果接口的指针被破坏,则该值递减。当它的引用数到0的时候,该对象自行破坏。
AddRef()函数用来使对象的内部引用值递增。绝大部分时间里,该函数通过DirectDraw API被用户调用。例如,当你使用DirectDrawaw API创建一个新的接口时,创建函数就自动调用AddRef()。
Release()函数用来给对象的内部引用值递减。用户应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。AddRef()和Release()函数都返回一个值,表示对象新的引用值。
QueryInterface()函数允许COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
4. GUID
为了查询一个对象是否支持使用QueryInterface()函数的指定接口,就有秘要识别有问题的接口。这通过接口的GUID(Globally Unique IDentifier)来实现。一个GUID是一个128位的值,也就是说,对于所有意图和目的是唯一的。所有DirectDraw接口的GUIDs都包含在DirectX头文件中。
上述对于COM的简单介绍,就是为有效使用DirectDraw API所需要的全部内容。以后当我们再讨论DirectDraw API时,你会发现这些内容是有联系的。

第四节 DirectDraw接口函数
1.关于 DirectDraw API
衡量API的一个方法就是看它的大小。一个庞大复杂的API可能就是计划不周的结果。另一方面,一个庞大的API有时就意味着每一种情况都有可能出现。一个小的API就是一个新的、缺乏功能的软件包的证据。它也意味着,一个API只能做它所需要做的,而不能多做一点。
DirectDraw API是比较小的,因此本章中所讨论的每一个函数不致于使本章看起来像一本参考手册。DirectDraw提供很少的方便,也很少有限制。
DirectDraw由个COM对象构成,每个对象可以通过一个或多个接口存取。这些接口包括:
●DirectDraw
●DirectDraw2
●DirectDrawSurface
●DirectDrawSurface2
●DirectDrawSurface3
●DirectDrawPalette
●DirectDrawClipper
我们将讨论每一个接口,并随后讨论它们的成员函数。但我们并不讨论每个函数的细节,因为我们并不是向您提供一份参考手册。相反,我们将讨论每个函数是干什么的,为什么这样使用,以及你有可能如何去使用它。
当DirectX首次推出的时候(早先它被称作Games SDK),DirectDraw核心函数性以DirectDraw接口表示。当DirectX2推出的时候,DirectDraw也已经被升级了。DirectDraw遵守COM规格而未被改变。新的函数性可能通过DirectDraw2接口存取。
特别要注意的是,DirectDraw2接口是DirectDraw接口的超级设置。DirectDraw2接口可提供DirectDraw接口的所有函数,另外还增加了一些新的函数。如果你正在使用DirectX或更高版高,那么你可以随意选用DirectDraw接口或DirectDraw2接口。但是,由于DirectDraw2接口较DirectDraw接口的功能更强,所以没有必要使用DirectDraw接口。同样,Microsoft并不主张使用这些无组织的、网络可变的接口。因此,在本书以后的程序中我们只使用DirectDraw2接口。
DirectDraw和DirectDraw2接口提供的成员函数如下(按字母顺序排列):
●Compact()
●CreateClipper()
●CreatePalette()
●CreateSurface()
●DuplicateSurface()
●EnumDisplayModes()
●EnumSurfaces()
●FlipToGDISurface()
●GetAvailableVidMem()
●GetCaps()
●GetDisplayMode()
●GetFourCCCodes()
●GetGDISurface()
●GetMonitorFrequency()
●GetScanline()
●GetVerticalBlankStatus()
●RestoreDisplayMode()
●SetCooperativeLevel()
●SetDisplayMode()
●WaitForVerticalBlank()
接下来我们讨论DirectDraw接口函数。注意,在本章以后的内容中,DirectDraw接口既表示DirectDraw接口,也表示DirectDraw2接口。只有在区分DirectDraw接口和DirectDraw2接口的函数时,才加以区别。
1. 接口创建函数
DirectDraw接口表示DirectDraw本身。该接口在被用于创建其他DirectDraw接口实例时,是一个主接口。DirectDraw接口提供三个这样的接口实例创建函数:
●CreateClipper()
●CreatePalette()
●CreateSurface()
CreateClipper()函数用于创建DirectDrawClipper接口实例。并非所有的DirectDraw应用程序都用到剪裁器,所以该函数并不是在所有的程序中都出现。我们将很快讨论DirectDrawClipper的细节。
CreatePalette()函数用于创建DirectDrawPalette接口实例。同DirectDrawClipper一样,并非所有的DirectDraw应用程序都用到调色板。比如,应用程序使用16位显示模式时,就不用调色板。但是,当应用程序使用8位显示模式时,就必须创建至少一个DirectDrawPalette实例。
CreateSurface()函数用于创建DirectDrawSurface接口实例。任何一个DirectDraw应用程序都要用表面来生成图像数据,因此经常要用到这一函数。
DirectDraw接口自己的实例是由DirectDrawCreate()函数创建的。DirectDrawCreate()是DirectDraw函数中少有的几个常规函数之一,但并不是COM接口成员函数。
2. GetCaps()函数
DirectDraw接口允许准确确定软硬件都支持的特征。GetCaps()函数可以对两个DDCAP结构实例进行初始化。一个结构表明哪些特征由显示硬件直接支持,另一个结构表明哪些特征由软件仿真支持。最好是用GetCaps()函数来决定你将用到的特征是否被支持。
提示:DirectX浏览器
DirectX SKD是与DXVIEW程序同时推出的。DXVIEW说明了DirectX组件的功能,包括DirectDraw。大多数系统中,有两个DirectDraw项目:主显示驱动器和硬件仿真层。第一项说明了显示硬件的功能。第二项说明了在缺乏硬件支持的情况下,DirectDraw将要仿真的一些特征。在具有两个以上的DirectDraw支持的显示卡的计算机中,DXVIEW会显示卡的功能。
3. SetCooperativeLevel()函数
SetCooperativeLevel()函数用于指定应用程序所要求的对显示硬件的控制程度。比如,一个正常合作度意味着应用程序既改变不了当前显示模式,也不能指定整个系统调色板的内容。而一个专有的合作度允许显示模式切换,并能完全控制调色板。不管你决定使用哪种合作度,都必须调用SetCooperativeLevel()函数。
4. 显示模式函数
DirectDraw接口提供4种显示模式操作函数。它们是:
●EnumDisplayModes()
●GetDisplayMode()
●RestoreDisplayMode()
●SetDisplayMode()
EnumDisplayModes()函数可用于查询DirectDraw使用何种显示模式。通过设置EnumDisplayModes()函数默认值可以得到所有的显示模式,而且可以通过显示模式描述消除那些不感兴趣的模式。进行显示模式切换的过程中最好使用EnumDisplayModes()函数。现在市场上有各种各样的显示设备,每种显示设备都有自己的特征和局限。除了默认的640×480×8窗口显示模式,最好不要依靠任何给定的显示模式的支持。
   GetDisplayMode()函数可以检索到有关当前显示模式的信息,并在DDSURFACEDESC结构实例中显示当前显示模式的宽度、高度、像素深度以及像素格式等信息。还有别的途径可以检索到同样的信息(比如检索主表面描述),因此该函数并不出现在所有的程序中。
   SetDisplayMode()函数用于激活所支持的显示模式。SetDisplayMode()函数的DirectDraw2版本还允许设定显示模式的刷新率。而DirectDraw接口版本的SetDisplayMode()函数只能进行显示模式宽度、高度和像素深度的设置。任何一个要进行显示模式切换的程序都要用到SetDisplayMode()函数。
RestoreDisplayMode()函数用于存储调用SetDisplayMode()函数之前的显示模式。SetDisplayMode()和RestoreDisplayMode()函数都要求优先使用SetCooperativeLevel()函数得到的专有合作存取。
5. 表面支持函数
除了CreateSurface()函数之外,DirectDraw接口还提供了以下向个表面相关函数:
●DuplicateSurface()
●EnumSurfaces()
●FlipToGDISurface()
●GetGDISurface()
●GetAvailableVidMem()
●Compact()
DuplicateSurface()函数用于考贝当前表面。该函数只复制表面接口,不复制内存。被复制的表面与源表面共享内存,因此改变内存的内容就同时改变了两个表面的图像。
EnumSurfaces()函数可用于迭代所有满足指定标准的表面。如果没有指定标准,那么所有当前表面都被枚举。
FlipToGDISurface()函数的作用是在终止页面翻转应用程序前确保主表面得以正确存储。取消页面翻转时,有两个表面交替显示。这就是说,在终止应用程序之前有可能没有保存最初的可见表面。这种情况下,Windows通过绘制一个不可见表面来恢复。利用FlipToGDISurface()函数就可以轻而易举地避免发生这种情况。
GetGDISurface()函数可以向只被GDI认可的表面返回一个提针。GDI表面是Windows用于输出的表面。在进行屏幕捕捉时,这个函数非常有用,DirectDraw可以捕捉到Windows桌面的任一部分。
GetAvailableVidMem()函数用于检索正在使用中的视频存储器(显示RAM)的数量。这一函数由DirectDraw2接口提供,而不是由DirectDraw接口提供。该函数用于确定应用程序利用显示RAM可创建表面的数量。
Compact()函数不是通过DirectX5实现的,但它可以为视频存储器提供碎片整理技巧。在基于显示RAM的表面被不断创建或受到破坏的时候,可以释放大量内存。
6. 监视器刷新函数
DirectDraw接口提供了4种适于计算机显示设备或监视器的函数,但这些函数不适于显示卡,它们是:
●GetMonitorFrequency()
●GetScanLine()
●GetVerticalBlankStatus()
●WaitForVerticalBlank()
这些函数尤其与监视器的刷新机制紧密机连。这在确保生成动画时尽可能不产生闪烁和图像撕裂现象时是至关重要的。但必须注意,并非所有的显示卡/监视器组合都支持这些函数。
GetMonitorFrequency()函数用于检索监视器当前的刷新率。刷新率通常用赫兹表示,缩写为Hz。例如,60Hz的刷新率表示屏幕每秒更新60次。
GetScanLine()函数用于向监视器返回当前正在被刷新的扫描行(水平像素行)。不是所有的显示设备/监视器组合都支持该函数。如果这一功能得不到支持,该函数将返回DDERR-UNSUPPORTED。
对于高性能图形应用程序来说,通常要求利用垂直刷新同步地更新屏幕。尤其是,当显示器刚完成屏幕刷新时,最好能够更新主表面。否则,屏幕的一部分显示新的图像数据,而另一部分仍显示旧的图像数据,这种现象就是所谓的图像撕裂。DirectDraw默认利用垂直刷新同步更新屏幕。如果不是这样还可以利用GetVerticalBlankStatus()和WaitForVerticalBlank()函数实现同步刷新。
7. GetFourCCCodes()函数
DirectDraw接口提供的最后一个函数是GetFourCCCodes()函数。该函数用于返回显示卡所支持的FourCC代码。FourCC代码用于描述非RGB或YUV表面。我们不在此讨论YOV表面,它们已超出本书的范围。
第五节 DirectDrawSurface接口函数
同DirectDraw接口一样,DirectDrawSurface接口也遵守COM规格.最初,表面支持是由DirectDrawSurface接口提供的。DirectX2介绍了DirectDrawSurface2接口的新的函数性,DirectX5介绍了DirectDrawSurface3接口。
尽管本软件中讨论的是DirectDraw2接口,而不是DirectDraw接口,但我们仍忠于最初的DirectDrawSurface接口,因为DirectDrawSurface2和DirectDrawSurface3接口新增的函数并不十分重要。在以后的内容里,我们将用DirectDrawSurface接口表示这3种接口,除非特别注明。
DirectDrawSurface是最大的DirectDraw接口,它允许表面内容的拷贝、清除以及被调用程序直接存取。DirectDrawSurawSurface接口总共提供36个成员函数,按字母顺序排列如下:
●AddAttachedSurface()
●AddOverlayDirtyRect()
●Blt()
●BltBatch()
●BltFast()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●EnumOverlayZOrders()
●Flip()
●GetAttachedSurface()
●GetBltstatus()
●GetCaps()
●GetClipper()
●GetColorKey()
●GetDC()
●GetDDInterface()
●GetFlipStatus()
●GetOverlayPosition()
●GetPalette()
●GetPixelFormat()
●GetSurfaceDesc()
●IsLost()
●Lock()
●PageLock()
●PageUnlock()
●ReleaseDC()
●Restore()
●SetClipper()
●SetColorKey()
●SetOverlayPosition()
●SetPalette()
●SetSurfaceDesc()
●Unlock()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
1. 表面描述函数
我们首先讨论的个可用于检索表面自身信息的函数,它们是:
●GetCaps()
●GetPixelFormat()
●GetSurfaceDesc()
●SetSurfaceDesc()
同DirectDraw接口提供的GetCaps()函数一样,DirectDrawSurface接口提供的GetCaps()函数用于输出表征哪些特征可被表面支持的数据。该信息包括:表面是主表面还是离屏表面;表面使用的存储器定位于显示RAM还是系统RAM。
GetPixelFormat()函数在用于高彩和真彩表面时是非常重要的,这是由于像素格式因显示卡的不同而不同。该函数返回表征码,这些表征码表明每一种颜色部件是如何存储的。
GetSurfaceDesc()函数返回一个表面描述。该信息包括表面的宽度、高度和深度。表面像素格式(同样被GetPixelFormat()函数检索)也包含在其中。
SetSurfaceDesc()函数(对于DirectX5)来讲是新增的,只由DirectDrawSurface3接口提供)允许设定某些表面属性。该函数可用于指定表面使用的内存。这一点在设计定制表面存储器管理器策略时非常有用。
2。 表面Blt函数
DirectDrawSurface接口提供3个支持blt操作的函数:
●Blt()
●BltBatch()
●BltFast()
Blt()函数是一个主要函数。Blt()函数能够进行常规的blting(无特殊影响的简单的表面到表面的blt),同时支持延伸、旋转、镜像和颜色填充的操作。当用于同剪裁器关联的表面时,Blt()可进行剪裁blt操作。
BltBatch()函数不是在DirectX3下实现的(你可以调用该函数,但什么也不会发生)。执行BltBatch()函数时,如果可能,它可同时进行多blt操作。
BltFast()函数是Blt()函数的优化版本。BltFast()函数的效率提高了,但性能却下降了。BltFast()函数不能进行一些特殊的blt操作,而Blt()函数可以。而且,BltFast()函数不能用于剪裁。但是BltFast()函数支持源和目标色彩键码blt的操作。在遵循定制剪裁例程的情况下,BltFast()函数可进行DirectDraw能够提供的最快捷、灵活的blt操作。在下章中我们将执行一个定制剪裁例程。
以上3个blt函数均将源表面和目标表面作为变量。其他的数据,例如blt在目标表面上的理想定位,是通过指定理想blt操作的确切属性来提供的。一旦可能,这3个函数将进行硬件加速blt。
3. Flip()函数
Flip()函数用于页面翻转操作。调用Flip()函数可隐藏屏幕上先前可见的表面,并使一个后备缓冲区显现。只有被明确地创建为翻转表面的表面,才响应该函数的调用。
必须牢记,真正的翻转操作不可能总是成功。页面翻转要求有足够的显示RAM容纳两整屏有效数据。如果满足不了这一要求,系统RAM中将创建一个后备缓冲区。这时调用Flip()函数进行的是blt操作而不是页面翻转。基于系统RAM的后备缓冲区中的内容被拷贝到主表面上。这样会严重影响性能,但是,在真正的页面翻转中如果没有足够的显示RAM,又不退出程序,也只能如此了。如果你的应用程序要求最佳性能,就得设法避免激活不能进行真正页面翻转的显示模式。
4. 表面状态函数
下面讨论两个能检索有关操作和翻转操作信息的函数,它们是:
●GetBltStatus()
●GetFlipStatus()
GetBltStatus()函数用于确定当前是否进行blt操作。这一点很重要,因为正被blt的表面不能进行其他操作。该函数表明,给定的表面是否正是一个进行blt操作的源表面或目标表面。
同样地,GetBltStatus()函数表明是否正在进行翻转操作。即使DirectDraw通过blt操作仿真页面翻转,该函数而不GetBltStatus()也必须用于由Flip()函数初始化的监视器页面翻转。
5. 色彩键码函数
DirectDrawSurface接口提供了以下两个函数,来设置和检查表面色彩键码或色彩键码的范围,它们是:
●GetColorKey()
●SetColoKey()
默认状态下,表面没有色彩键码。一个色彩键码只对应一种颜色,但某些硬件支持色彩键码范围。色彩键码和色彩键码范围是DDCOLORKEY结构定义的。GetColorKey()和SetColoKey()函数都将该结构的指针作为变量。在要求表面的一部分透明时或需要进行目标色彩键码操作时,可以使用这两个函数。
6. Lock和Unlock()函数
DirectDraw的一个主要特点,就是能够提供对图像数据的直接存取。直接存取可以提供最佳性能和更好的灵活性,因为没有中间API影响运行速度,并且开发人员呆任意使用图像数据。对表面存储器的中间存取通过以下出众个函数实现:
●Unlock()
●Lock()
Lock()函数向组成表面的存储器返回一个指针,不管表面存储器位于显示RAM还是系统RAM。存储器一般按线性风格排列,以便能简单地进行图像 数据存取。Unolock()函数在完成表面存储器的存取之后指定给DirectDraw。
对图像数据的直接存取必须付出代价。为了支持这种存取方式,DirectDraw在表面锁定的时候必须关闭基本的Windows机构。在Windows95状态下,如果忘记解锁表面,必定会损坏机器。
因此,表面锁定的时间应尽量缩短。测试前应仔细检查Lock()和Unlock()函数之间的程序调用。因为这一程序无法用传统的调试程序进行调试。
锁定表面不能被blt和翻转,因此试图保持表面处于锁定状态没有任何有益之处。而且,一旦表面解锁,由Lock()函数检索的指针就失效了。
表面锁定后不能再次被锁定。在表面锁定时也就无法调用Lock()函数。
7. GetDC()ReleaseDC()函数
对表面的直接存取占用很大内存,有时候把表面作为一个常规的Windows设备会更好。在此,DirectDrawSurface接口提供以下两个函数:
●GetDC()
●ReleaseDC()
GetDC()函数提供了一个DC(设备环境),可以用常规的Win32函数写到表面上。例如,DC可以用Win32的TextOut()函数在表面上绘制文本。用完DC后必须马上调用ReleaseDC()函数。
同Lock()和Unlock()函数使用一样,在调用完GetDC()函数后必须马上调用ReleaseDC()函数。这是因为GetDC()函数内部调用Lock函数,而ReleaseDC()函数内部调用Unlock()函数。
8. PageLock()和PageUnlock()函数
接下来,我们讨论另外两个与Lock()函数和Unlock()函数看上去非常相像的函数:
●PageLock()
●PageUnlock()
尽管这两个函数看上去很像Lock()和Unlock()函数,但它们却有完全不同的作用。PageLock()和PageUnlock()函数用于控制Windows对基于系统RAM的表面的处理方式。这两个函数由DirectDrawSurface2接口提供,而不是由DirectDrawSurface接口提供。
当Windows认为当前正在运行的其他应用程序或进程更适于使用内存时,Windows会向硬盘释放部分内存。这种缓冲对于整个系统内存都起作用,因此驻留在系统内存中的DirectDraw表面有可能被存到硬盘上。如果要用到这样的表面,Windows需要花费一定的时间从硬盘上读取表面数据。
PageLock()函数提示Windows哪些给定的表面不应该释放到硬盘上。这样,在使用表面时就不用耗费时间进行硬盘存取了。相反地,PageUnlock()函数用于告知Windows哪些表面内存可被释放。
过程调用PageLock()函数会减少缓冲内存的总量,从而导致Windows的速度大大降低。至于这种情况何时发生,取决于页面锁定系统内存量及机器提供的系统内存量。
PageLock()和PageUnlock()函数主要是由DirectDraw提供而非DirectDraw应用程序。举个例子来说,DirectDraw自动使用PageLock()函数,以确保运行blt操作时,基于系统RAM的表面不被释放到硬盘。
PageLock()函数可以被同一个表面多次调用。DirectDraw用参考计数法记录PageLock()函数被调用的次数,因此多次调用PageUnlock()函数就必须避免多次调用PageLock()函数。
PageLock()和PageUnlock()函数对于驻留在显示RAM中的表面不起作用。
9. IsLost()的Restore()函数
现在讨论两个与使用驻留在显示RAM中的表面有关的函数:
●IsLost()
●Restore()
让我们来看一看下面这种情况。应用程序正在运行时,尽量把表面分配到显示RAM中,剩下的创建到系统RAM中。应用程序在运行一段时间之后,用户执行或切换到另一个应用程序。该应用程序是任意的,可以是一个常规的Windows程序,如Windows开发程序或记事本。它也可以是另外的DirectDraw应用程序,该程序也试图将尽可能多地分配显示RAM。如果DirectDraw不接受显示RAM,那么新的应用程序就很可能根本运行不了。相反的情况就意味着,应用程序不允许分配到任何显示RAM中。
因此,DirectDraw可以随意将任何一个或者所有的基于显示RAM的表面从非激活应用程序中移走。这种情况就是所谓的表面丢失。从技术上讲,程序仍具有表面,但它们不再同任何内存相关。要使用丢失的表面会导致DDERR-SURFACELOST错误。IsLost()函数可用于确定一个表面是否丢失了内存。
表面内存丢失后可通过Restore()函数恢复,但只能在应用程序被重新激活之后才可恢复。这会导致应用程序无法将处于最小化状态的所有表面复原。
Restore()函数可恢复附属于表面的任一内存,但并不恢复内存的内容。表面被复原后,应用程序就可以恢复表面内容了。
注意,这种用法不适合利用系统RAM创建的表面。如果需要用到基于系统RAM的表面所占内存,那么Windows会立即将这些表面释放到硬盘上。Windows自动地处理存储和恢复,包括恢复表面的内容。
10. GetDDInterface()函数
GetDDInterface()函数可检索用于创建给定表面的DirectDraw接口的指针。由于程序中大多数情况下只有一个DirectDraw接口实例,所以GetDDInterface()函数并不常用。但是一个应用程序中有可能使用多个DirectDraw接口,在这种情况下,GetDDInterface()函数会起到重要作用。
11. 表面连接函数
DirectDrawSurface接口提供以下4个函数,用来维持表面间的连接:
●AddAttachedSurface()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●GetAttachedSurface()
DirectDraw支持大量的用于表面间的连接情况。最常见的情况就是页面翻转。进行页面翻转时,两个或两个以上的表面连接成环状,每次调用Flip()函数时,都会使连成环状的表面中的下一个表面显现。
表面连接函数用于创建、检查或消除表面间的连接,但这些函数并非必不可少的。DirectDraw往往是自动创建连接表面。比如,当创建一个主翻转表面时,可以指定用于连接表面的后备缓冲区的数量。DirectDraw就会创建这些表面,并将它们连接起来。
12. 重叠函数
DirectDrawSurface接口用于支持重叠的函数如下:
●AddOverlayDirtyRect()
●EnumOverlayZOrders()
●GetOverlayPosition()
●SetOverlayPosition()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
GetOverlayPosition()和SetOverlayPosition()函数用于控制重叠的位置。UpdateOverlay()函数用于更新大量的重叠设置,包括重叠是否可见,以及重叠是以色彩键码还是用alpha混合到背景表面上。
UpdateOverlayDisplay()函数用于更新显示设置。该函数用于更新整个重叠显示,或者只更新由AddOverlayDirtyRect()函数指定的矩形重叠部分。EnumOverlayZOrders()函数可根据重叠的Z值(Z值控制哪一个重叠位于最上面)重复重叠。重叠可按从前到后或从后到前的顺序枚举。
13. 剪裁器函数
DirectDraw支持的剪裁是将DirectDrawClipper接口(该接口我们尚未讨论)的一个实例连接到表面。一旦连接完毕,剪裁器对象就会有规律地blt到表面。剪裁器/表面的连接由以下两个DirectDrawSurface函数控制:
●GetClipper()
●SetClipper()
SetClipper()函数用来将剪裁器对象连接到表面。GetClipper()函数用于向前一个连接的剪裁器对象返回一个指针。SetClipper()函数还可用于解除表面同剪裁器的连接,具体的做法是通过指定NULL来替代DirctDrawClipper接口指针。
14。 调色板函数
像剪裁器一样,调色板也可连接到表面。DirctDrawSurface接口为此提供以下两个函数:
●GetPalette()
●SetPalette()
SetPalette()函数用来将DirctDrawPalette接口(该接口我们接下来就要讨论)的一个实例连接到表面。GetPalette()函数用于检索前一个连接调色板的指针。
调色板可被连接到任何表面,但只有连接到主表面时,调色板才起作用。当与主表面连接时,调色板决定显示硬件调色板的设置。
第六节 DirectDrawPlette接口函数
DirctDraw提供DirctDrawPalette接口用于调色板显示模式和表面。尽管Windows支持几种低于8位像素深度的显示模式,但DirctDraw所支持的唯一的调色板显示模式是8位模式。
DirctDrawPalette接口实例由DirctDraw CreatePalette()函数创建。CreatePalette()函数用大量的标志来定义调色板属性。
DirctDrawPalette接口只提供以下3个函数:
●GetCaps()
●GetEntries()
●SetEntries()
GetCaps()函数用于检索有关调色板的信息,包括调色板项目数量,调色板是否支持同步垂直刷新,以及在8位调色板状态下是否所有的256个调色板项目都能被设定。
SetEntries()函数允许在程序中设置调色板的色彩值。该数据从文件中读取。而这些项目在运行过程中可被计算和设定。GetEntries()函数用于检索先前设定的调色板项目。
DirctDrawPalette()接口实例可利用DirctDrawSurface SetPalette()函数连接到表面。将不同调色板连接到主表面或利用SetEntries()函数改变调色板项目都可激活调色板。

第七节 DirectDrawClipper接口函数
DirctDrawClipper接口支持剪裁。将剪裁器对象连接到表面并在blt操作中将其当作目标表面就可以进行剪裁。
directDrawClipper实例由DirectDraw CreateClipper()函数创建。DirectDrawClipper接口支持以下5个函数:
●SetHWnd()
●GetHWnd()
●IsClipListChanged()
●SetClipList()
●GetClipList()
剪裁器对象一般用于出现在窗口中的DirctDraw应用程序必需的剪裁。要求剪裁器必须确保在blt操作过程中考虑到桌面上其他的窗口。比如,当应用程序的全部或一部分被另一个窗口遮蔽,剪裁就必须确保被遮蔽的窗口不被DirctDraw应用程序破坏。
桌面剪裁由SetWnd()函数完成。SetHWnd()函数将剪裁器对象连接到一个窗口句柄。这样就初始化了Windows和剪裁器对象之间的通讯。当桌面上的任何一个窗口发生变化时,剪裁器对象就会得到通知,并作出反应。GetHWnd()函数用于决定剪裁器同哪一个窗口句柄连接。IsClipListChanged()函数用于决定内部剪裁清单是否因桌面的改变而被更新。
SetClipList()和GetClipList()函数为DirectDrawClipper接口提供便利的定制使用。SetClipList()函数用于定义一些矩形区域,这些矩形区域用来定义blt操作的合法区域。GetClipList()函数用于检索剪裁器的内部剪裁数据。
一旦被连接到表面,Blt(),BltBatch()以及UpdateOverlay()函数所进行的blt操作将根据DirctDrawCliper接口中包含的数据被自动地剪裁。注意,Blt Fast()函数在此被忽略。BltFast()函数不支持剪裁。

第八节 附加DirectDraw接口
DirctDraw还提供了另外个我们没有讨论的接口,它们是:
●DDVideoPortContainer
●DirectDrawColorControl
●DirectDrawVideoport
这些接口由DirectX5介绍,它们提供低水平视频端口控制。这些接口还向DirctDraw表面提供流活动视频的方法。尽管利用这些接口可以为DirctDraw应用程序增加视频支持,但除非高水平视频APIs不能满足需要,否则最好不用这一方法。

第九节 DirectDraw结构
我们已讨论过DirctDraw接口及其成员函数了,接下来再看看DirctDraw定义的结构。DirctDraw总共定义了8全结构:
●DDBLTFX
●DDCAPS
●DDOVERLAYFX
●DDPIXELFORMAT
●DDSURFACEDESC
●DDSCAPS
●DDBLTBATCH
●DDCOLORKEY
我们已经见过其中的一些结构了,比如在讨论DirctDrawSurface SetColorKey()函数时我们就接触过DDCOLORKEY结构。在此,我们不详细讨论每一个结构的细节,但必须指出,DirctDraw quirk被忘记时会导致失败。
以上所列结构中的前5个有一个称作dwSize的字段。该字段用于存储结构的大小,设定该字段的工作由你来做。另外,该字段如果没有被正确设定的话,那么任何一个将这5个结构作为变量的DirctDraw函数都会失效。
以DDSURFACEDESC结构为例,使用结构的代码如下:
DDSURFACEDESC surfdesc;
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
首先声明结构,然后用sizeof()关键字设定dwSize字段。最后结构传递给DirctDrawSurface GetSurfaceDesc()函数。忘记设定dwSize字段将导致这段代码失效。
到底为什么DirctDraw坚持要求给出它所定义的结构的大小?原因在于这5个包含dwSize字段的结构将来有可能会改变。DirctDraw会检查结构的大小,以便确定正在使用的版本。DirctDraw坚持要求给出一个有效的大小值,是为了让开发者提供有效的结构大小。这样做是有好处的,因为DirctDraw的新版本可以正确运用旧版本的DirctDraw程序。
在使用结构之前,最好将结构初始化为零。这样,前面的代码就变成:
DDSURFACEDESC surfdesc;
ZeroMemory (&surfdesc,sizeof(surfdesc));
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
ZeroMemory()函数是一个Win32函数,它将作为第一个参数的存储器设定为零.ZeroMemory()函数的第二个参数表明有多少存储器应被初始化。这一做法的好处是,通过GetSurfaceDesc()函数调用可以知道结构的哪些字段被更新了。如果没有对结构进行初始化,就有可能将结构字段中不可预测的值当作DirectDraw的设定值。

第十节 窗口应用程序
DirctDraw应用程序主要有两种型式:窗口的和全屏的。窗口DirctDraw应用程序看起来就像一个常规的Windows程序。我们很快将讨论到全屏应用程序。
窗口应用程序包括窗口边界、标题框以及菜单,这些都是传统的Windows应用程序中常见的部分。由于窗口应用程序同其他窗口一起出现在桌面上,因此它们被迫使用Windows当前所使用的分辨率和比特深度。
窗口程序有一个主表面,但只在进行真实页面翻转时才显现。而且,主表面并不代表窗口的客户区域(该区域在窗口边界内)。主表面还代表整个桌面。这就是说,你的程序必须追踪窗口的位置和大小,以便在窗口内正确显示可见的输出。换言之,利用窗口化的应用程序中可以在整个桌面上进行绘图。
如果不允许页面翻转,那么图像就必须从离屏缓冲区blt到主表面上。这就增加了图像撕裂的可能性,因为blt比页面翻转速度慢。为了避免图像撕裂,blt操作可以与监视的刷新率保持同步。
如果与窗口客户区域同样大小的离屏缓冲区被创建到显示RAM中,窗口应用程序就可以很好地运行。这样,窗口的内容可利用离屏表面合成。然后离屏表面可以通过硬件加速很快地到主表面上。
由于显示存储器的缺乏而不得不将离屏缓冲区创建到系统RAM中时,会严重影响性能。不幸的是,这种情况常常发生,尤其是在只有2MB显示卡的时候,这是因为人们总希望为自己Windows的桌面设置高分辨的显示模式。例如,采用1024×768×16显示模式的主表面自己就要占用2MB的RAM。在一个2MB显示卡上,几乎没有显示RAM留给离屏表面。
窗口应用程序的另一个问题是剪裁。一个性能良好的应用程序必须有一个连接到主表面的剪裁对象。这是有损性能的,原因在于为了检查剪裁器的剪裁清单内容,blt操作只能在一个小的矩形部分内进行。而且,不能使用优化的BltFast()函数。Bltting必须用较慢的,(而且更笨重的)Blt()函数。
最后要讲的是,窗口应用程序不允许全调色板控制。由于Windows保留了20个调色板项,所以在256种颜色只有236种颜色可被设定。被Windows保留的颜色只用系统调色板的前10个项和后10个项。因此在给图像上色时,只能使用中间236个调色板项。

第十一节全屏应用程序
包含对显示设备进行专有存取的DirctDraw应用程序就是全屏应用程序。这种应用程序可以任意选取显示卡所支持的显示模式,并享有全调色板控制。另外,全屏应用程序还可进行页面翻转。因此同窗口应用程序相比,全屏应用程序速度更快,灵活性更好。
典型的全屏应用程序首先检查所支持的显示模式,并激活其中一个。然后创建具有一个或更多后备缓冲区的可翻转主表面,剩下的显示RAM用于创建离屏表面。当显示RAM耗尽时,就启用系统RAM。屏幕被在后备缓冲区中合成的第一个场景更新,然后进行页面翻转。即使主表面占用了所有的可用的显示RAM,全屏应用程序还可输出执行其窗口化的副本,这是因为全屏应用程序可进行真实页面翻转。
由于全屏应用程序不一定非得使用Windows的当前显示模式,所以显示RAM的可用与否不存在多少问题。如果只检测到2MB的显示RAM,就可使用低分辨率显示模式来保留内存。如果检测到4MB的显示RAM,应用程序就可使用要求的显示模式并仍有保持良好性能。
全调色板控制也是个有利因素。这样可以使用所有256个调色板项而无需根据Windows保留的20中颜色重新分配位图。

第十二节混合应用程序
混合应用程序既可以在全屏方式下运行也可在窗口方式下运行。混合应用程序内部非常复杂,但却可以提供良好的性能。用户可在窗口方式下运行,如果太慢,则可切换到全屏方式下运行。
编写混合应用程序最好的方法是编写一个定制库,该库包括那些与应用程序使用全屏方式还是窗口方式无关的函数。

posted @ 2008-08-25 18:16 shelvenn's blog 阅读(733) 评论(0) 编辑

2008年8月21日 #

摘要: 小知识:RGB与YUV----摘自《DirectShow实务精选》 作者:陆其明计算机彩色显示器显示色彩的原理与彩色电视机一样,都是采用R(Red)、G(Green)、B(Blue)相加混色的原理:通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。这种色彩的表示方法称为RGB色彩空间表示(它也是多媒体计算机技术中用得最多的一种色彩空间表示方法)。根据三基色原理,任意...阅读全文
posted @ 2008-08-21 09:38 shelvenn's blog 阅读(1408) 评论(0) 编辑

2008年3月27日 #

摘要: 在所有的预处理指令中,#pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 其格式一般为: #pragma para 其中para为参数,下面来看一些常用的参数。...阅读全文
posted @ 2008-03-27 10:00 shelvenn's blog 阅读(1373) 评论(0) 编辑

2008年3月12日 #

摘要: 流媒体指的是在网络中使用流技术传输的连续时基媒体,其特点是在播放前不需要下载整个文件,而是采用边下载边播放的方式,它是视频会议、IP电话等应用场合的技术基础。RTP是进行实时流媒体传输的标准协议和关键技术,本文介绍如何在Linux下利用JRTPLIB进行实时流媒体编程。一、流媒体简介 随着Internet的日益普及,在网络上传输的数据已经不再局限于文字和图形,而是逐渐向声音和视频等多媒体格式过渡。...阅读全文
posted @ 2008-03-12 11:23 shelvenn's blog 阅读(7106) 评论(0) 编辑