ppin168 斗鱼tv有客户端吗么

ppin168谁比较了解呢?ppin168谁教教我玩这里的游戏啊?_百度知道
ppin168谁比较了解呢?ppin168谁教教我玩这里的游戏啊?
提问者采纳
抚念益慈柔
很多权威┃鸿|利会 网址: wd.ch.kg ┃网站都说这里网站好,都是排行第一的,值得推荐!
其他类似问题
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁direcshow之视频捕捉
摘要:本篇文档主要描述关于用Directshow进行视频开发的一些技术
主要包括下面内容
1关于视频捕捉(About Video Capture in Dshow)
2选择一个视频捕捉设备(Select capture device)
3预览视频(Previewing Video)
4如何捕捉视频流并保存到文件(Capture video to File)
5将设备从系统中移走时的事件通知(Device remove Notify)
6如何控制Capture Graph(Controlling Capture Graph)
7如何配置一个视频捕捉设备
8从静止图像pin中捕捉图片
1关于视频捕捉(About Video Capture in Dshow)
1视频捕捉Graph的构建
一个能够捕捉音频或者视频的graph图都称之为捕捉graph图。捕捉graph图比一般的文件回放graph图要复杂许多,dshow提供了一个Capture
Graph Builder COM组件使得捕捉graph图的生成更加简单。Capture Graph
Builder提供了一个ICaptureGraphBuilder2接口,这个接口提供了一些方法用来构建和控制捕捉graph。
首先创建一个Capture Graph Builder对象和一个graph manger对象,然后用filter graph
manager 作参数,调用ICaptureGraphBuilder2::SetFiltergraph来初始化Capture
Graph Builder。看下面的代码把
HRESULT InitCaptureGraphBuilder(
IGraphBuilder **ppGraph, // Receives the pointer.
ICaptureGraphBuilder2 **ppBuild // Receives the pointer.
if (!ppGraph || !ppBuild)
return E_POINTER;
IGraphBuilder *pGraph = NULL;
ICaptureGraphBuilder2 *pBuild = NULL;
&// Create the Capture Graph Builder.
& HRESULT hr =
CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
& CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2,
(void**)&pGraph);
& if (SUCCEEDED(hr))
& // Create the Filter Graph Manager.
& hr = CoCreateInstance(CLSID_FilterGraph, 0,
CLSCTX_INPROC_SERVER,
& IID_IGraphBuilder, (void**)&pGraph);
& if (SUCCEEDED(hr))
& // Initialize the Capture Graph Builder.
& pBuild-&SetFiltergraph(pGraph);
&// Return both interface pointers to the
& *ppBuild = pB
& *ppGraph = pG // The caller must release
both interfaces.
& return S_OK;
pBuild-&Release();
& // Failed
2视频捕捉的设备
现在许多新的视频捕捉设备都采用的是WDM驱动方法,在WDM机制中,微软提供了一个独立于硬件设备的驱动,称为类驱动程序。驱动程序的供应商提供的驱动程序称为minidrivers。Minidrivers提供了直接和硬件打交道的函数,在这些函数中调用了类驱动。
在directshow的filter图表中,任何一个WDM捕捉设备都是做为一个WDM Video Capture
过滤器(Filter)出现。WDM Video Capture过滤器根据驱动程序的特征构建自己的filter
下面是陆其明的一篇有关于dshow和硬件的文章,可以拿来参考一下
//陆文章开始
大家知道,为了提高系统的稳定性,Windows操作系统对硬件操作进行了隔离;应用程序一般不能直接访问硬件。DirectShow
Filter工作在用户模式(User mode,操作系统特权级别为Ring 3),而硬件工作在内核模式(Kernel
mode,操作系统特权级别为Ring 0),那么它们之间怎么协同工作呢?
DirectShow解决的方法是,为这些硬件设计包装Filter;这种Filter能够工作在用户模式下,外观、控制方法跟普通Filter一样,而包装Filter内部完成与硬件驱动程序的交互。这样的设计,使得编写DirectShow应用程序的开发人员,从为支持硬件而需做出的特殊处理中解脱出来。DirectShow已经集成的包装Filter,包括Audio
Capture Filter(qcap.dll)、VfW Capture Filter(qcap.dll,Filter的Class
Id为CLSID_VfwCapture)、TV Tuner Filter(KSTVTune.ax,Filter的Class
Id为CLSID_CTVTunerFilter)、Analog Video Crossbar Filter(ksxbar.ax)、TV
Audio Filter(Filter的Class
Id为CLSID_TVAudioFilter)等;另外,DirectShow为采用WDM驱动程序的硬件设计了KsProxy
Filter(Ksproxy.ax,)。我们来看一下结构图:
从上图中,我们可以看出,Ksproxy.ax、Kstune.ax、Ksxbar.ax这些包装Filter跟其它普通的DirectShow
Filter处于同一个级别,可以协同工作;用户模式下的Filter通过Stream
Class控制硬件的驱动程序minidriver(由硬件厂商提供的实现对硬件控制功能的DLL);Stream
Class和minidriver一起向上层提供系统底层级别的服务。值得注意的是,这里的Stream
Class是一种驱动模型,它负责调用硬件的minidriver;另外,Stream
Class的功能还在于协调minidriver之间的工作,使得一些数据可以直接在Kernel
mode下从一个硬件传输到另一个硬件(或同一个硬件上的不同功能模块),提高了系统的工作效率。(更多的关于底层驱动程序的细节,请读者参阅Windows
下面,我们分别来看一下几种常见的硬件。
VfW视频采集卡。这类硬件在市场上已经处于一种淘汰的趋势;新生产的视频采集卡一般采用WDM驱动模型。但是,DirectShow为了保持向后兼容,还是专门提供了一个包装Filter支持这种硬件。和其他硬件的包装Filter一样,这种包装Filter的创建不是像普通Filter一样使用CoCreateInstance,而要通过系统枚举,然后BindToObject。
音频采集卡(声卡)。声卡的采集功能也是通过包装Filter来实现的;而且现在的声卡大部分都有混音的功能。这个Filter一般有几个Input
pin,每个pin都代表一个输入,如Line
In、Microphone、CD、MIDI等。值得注意的是,这些pin代表的是声卡上的物理输入端子,在Filter
Graph中是永远不会连接到其他Filter上的。声卡的输出功能,可以有两个Filter供选择:DirectSound
Renderer Filter和Audio Renderer (WaveOut)
Filter。注意,这两个Filter不是上述意义上的包装Filter,它们能够同硬件交互,是因为它们使用了API函数:前者使用了DirectSound
API,后者使用了waveOut API。这两个Filter的区别,还在于后者输出音频的同时不支持混音。(顺便说明一下,Video
Renderer Filter能够访问显卡,也是因为使用了GDI、DirectDraw或Direct3D
API。)如果你的机器上有声卡的话,你可以通过GraphEdit,在Audio Capture
Sources目录下看到这个声卡的包装Filter。
WDM驱动的硬件(包括视频捕捉卡、硬件解压卡等)。这类硬件都使用Ksproxy.ax这个包装Filter。Ksproxy.ax实现了很多功能,所以有“瑞士军刀”的美誉;它还被称作为“变色龙Filter”,因为该Filter上定义了统一的接口,而接口的实现因具体的硬件驱动程序而异。在Filter
Graph中,Ksproxy Filter显示的名字为硬件的Friendly
name(一般在驱动程序的.inf文件中定义)。我们可以通过GraphEdit,在WDM
Streaming开头的目录中找到本机系统中安装的WDM硬件。因为KsProxy.ax能够代表各种WDM的音视频设备,所以这个包装Filter的工作流程有点复杂。这个Filter不会预先知道要代表哪种类型的设备,它必须首先访问驱动程序的属性集,然后动态配置Filter上应该实现的接口。当Ksproxy
Filter上的接口方法被应用程序或其他Filter调用时,它会将调用方法以及参数传递给驱动程序,由驱动程序最终完成指定功能。除此以外,WDM硬件还支持内核流(Kernel
Streaming),即内核模式下的数据传输,而无需经过到用户模式的转换。因为内核模式与用户模式之间的相互转换,需要花费很大的计算量。如果使用内核流,不仅可以避免大量的计算,还避免了内核数据与主机内存之间的拷贝过程。在这种情况下,用户模式的Filter
Graph中,即使pin之间是连接的,也不会有实际的数据流动。典型的情况,如带有Video Port
Pin的视频捕捉卡,Preview时显示的图像就是在内核模式下直接传送到显卡的显存的。所以,你也休想在VP
Pin后面截获数据流。
讲到这里,我想大家应该对DirectShow对硬件的支持问题有了一个总体的认识。对于应用程序开发人员来说,这方面的内容不用研究得太透,而只需作为背景知识了解一下就好了。其实,大量繁琐的工作DirectShow已经帮我们做好了。
//陆其明文章结束
Direcshow中视频捕捉的Filter
捕捉Filter一般都有两个或多个输出pin,他们输出的媒体类型都一样,比如预览pin和捕捉pin,因此根据媒体类型就不能很好的区别这些pin。此时就要根据pin的功能来区别每个pin了,每个pin都有一个GUID,称为pin的种类。
如果想仔细的了解pin的种类,请看后面的相关内容Working with Pin
Categories。对于大多数的应用来说,ICaptureGraphBuilder2提供了一些函数可以自动确定pin的种类。
预览pin和捕捉pin
视频捕捉Filter都提供了预览和捕捉的输出pin,预览pin用来将视频流在屏幕上显示,捕捉pin用来将视频流写入文件。
预览pin和输出pin有下面的区别:
1 为了保证捕捉pin对视频桢流量,预览pin必要的时候可以停止。
2 经过捕捉pin的视频桢都有时间戳,但是预览pin的视频流没有时间戳。
预览pin的视频流之所以没有时间戳的原因在于filter图表管理器在视频流里加一个很小的latency,如果捕捉时间被认为就是render时间的话,视频renderFilter就认为视频流有一个小小的延迟,如果此时render
filter试图连续播放的时候,就会丢桢。去掉时间戳就保证了视频桢来了就可以播放,不用等待,也不丢桢。
预览pin的种类GUID为PIN_CATEGORY_PREVIEW
捕捉pin的种类GUID为PIN_CATEGORY_CAPTURE
Video Port pin
Video Port是一个介于视频设备(TV)和视频卡之间的硬件设备。同过Video
Port,视频数据可以直接发送到图像卡上,通过硬件的覆盖,视频可以直接在屏幕显示出来。Video
Port就是连接两个设备的。
使用Video Port的最大好处是,不用CPU的任何工作,视频流直接写入内存中。当然它也有下面的缺点drawbacks:
如果捕捉设备使用了Video Port,捕捉Filter就用一个video port pin代替预览pin。
video port pin的种类GUID为PIN_CATEGORY_VIDEOPORT
一个捕捉filter至少有一个Capture pin,另外,它可能有一个预览pin 和一个video port pin
,或者两者都没有,也许filter有很多的capture
pin,和预览pin,每一个pin都代表一种媒体类型,因此一个filter可以有一个视频capture
pin,视频预览pin,音频捕捉pin,音频预览pin。
Upstream WDM Filters
在捕捉Filter之上,WDM设备可能需要额外的filters,下面就是这些filter
TV Tuner Filter
TV Audio Filter.
Analog Video Crossbar Filter
尽管这些都是一些独立的filter,但是他们可能代表的是同一个硬件设备,每个filter都控制设备的不同函数,这些filter通过pin连接起来,但是在pin中没有数据流动。因此,这些pin
的连接和媒体类型无关。他们使用一个GUID值来定义一个给定设备的minidriver,例如:TV tuner Filter
和video capture filter都支持同一种medium。
在实际应用中,如果你使用ICaptureGraphBuilder2来创建你的capture
graphs,这些filters就会自动被添加到你的graph中。更多的详细资料,可以参考WDM Class Driver
2选择一个视频捕捉设备(Select capture device)
如何选择一个视频捕捉设备,可以采用系统设备枚举,详细资料参见Using the System Device Enumerator
。enumerator可以根据filter的种类返回一个设备的monikers。Moniker是一个com对象,可以参见IMoniker的SDK。
对于捕捉设备,下面两种类是相关的。
CLSID_AudioInputDeviceCategory 音频设备
CLSID_VideoInputDeviceCategory 视频设备
下面的代码演示了如何枚举一个视频捕捉设备
& ICreateDevEnum *pDevEnum = NULL;
& IEnumMoniker *pEnum = NULL;
// Create the System Device Enumerator.
& HRESULT hr =
CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
& CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
& reinterpret_cast(&pDevEnum));
& if (SUCCEEDED(hr))
& //创建一个枚举器,枚举视频设备
& hr = pDevEnum-&CreateClassEnumerator(
CLSID_VideoInputDeviceCategory,
& &pEnum, 0);
IEnumMoniker接口pEnum返回一个IMoniker接口的列表,代表一系列的moniker,你可以显示所有的设备,然后让用户选择一个。
采用IMoniker::BindToStorage方法,返回一个IPropertyBag接口指针。然后调用IPropertyBag::Read读取moniker的属性。下面看看都包含什么属性
1 FriendlyName 是设备的名字
2 Description
属性仅仅适用于DV和D-VHS/MPEG摄象机,如果这个属性可用,这个属性更详细的描述了设备的资料
3DevicePath 这个属性是不可读的,但是每个设备都有一个独一无二的。你可以用这个属性来区别同一个设备的不同实例
下面的代码演示了如何显示遍历设备的名称 ,接上面的代码
HWND hL // Handle to the list box.
& IMoniker *pMoniker = NULL;
& while (pEnum-&Next(1, &pMoniker, NULL) ==
& IPropertyBag *pPropB
& hr = pMoniker-&BindToStorage(0, 0,
IID_IPropertyBag,
& (void**)(&pPropBag));
& if (FAILED(hr))
& pMoniker-&Release();
& // Skip this one, maybe the next one
will work.
& // Find the description or friendly name.
& VARIANT varN
& VariantInit(&varName);
& hr = pPropBag-&Read(L"Description",
&varName, 0);
& if (FAILED(hr))
& hr = pPropBag-&Read(L"FriendlyName",
&varName, 0);
& if (SUCCEEDED(hr))
& // Add it to the application's list box.
& USES_CONVERSION;
& (long)SendMessage(hList, LB_ADDSTRING, 0,
& (LPARAM)OLE2T(varName.bstrVal));
& VariantClear(&varName);
& pPropBag-&Release();
& pMoniker-&Release();
如果用户选中了一个设备调用IMoniker::BindToObject为设备生成filter,然后将filter加入到graph中。
IBaseFilter *pCap = NULL;
& hr = pMoniker-&BindToObject(0, 0,
IID_IBaseFilter, (void**)&pCap);
& if (SUCCEEDED(hr))
& hr = m_pGraph-&AddFilter(pCap, L"Capture
3预览视频(Previewing Video)
为了创建可以预览视频的graph,可以调用下面的代码
ICaptureGraphBuilder2 *pB // Capture Graph Builder
& // Initialize pBuild (not shown).
IBaseFilter *pC // Video capture filter.
hr = pBuild-&RenderStream(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video,
& pCap, NULL, NULL);
4如何捕捉视频流并保存到文件(Capture video to File)
1 将视频流保存到AVI文件
下面的图表显示了graph图
AVI Mux filter接收从capture pin过来的视频流,然后将其打包成AVI流。音频流也可以连接到AVI Mux
Filter上,这样mux filter就将视频流和视频流合成AVI流。File writer将AVI流写入到文件中。
可以像下面这样构建graph图
IBaseFilter *pM
& hr = pBuild-&SetOutputFileName(
& &MEDIASUBTYPE_Avi, // Specifies AVI for the
target file.
& L"C:\\Example.avi", // File name.
& &pMux, // Receives a pointer to the
& NULL); // (Optional) Receives a pointer to the
file sink.
第一个参数表明文件的类型,这里表明是AVI,第二个参数是制定文件的名称。对于AVI文件,SetOutputFileName函数会创建一个AVI
mux Filter 和一个 File writer Filter
,并且将两个filter添加到graph图中,在这个函数中,通过File Writer Filter
请求IFileSinkFilter接口,然后调用IFileSinkFilter::SetFileName方法,设置文件的名称。然后将两个filter连接起来。第三个参数返回一个指向
Mux的指针,同时,它也通过第四个参数返回一个IFileSinkFilter参数,如果你不需要这个参数,你可以将这个参数设置成NULL。
然后,你应该调用下面的函数将capture filter 和AVI Mux连接起来。
hr = pBuild-&RenderStream(
& &PIN_CATEGORY_CAPTURE, // Pin
& &MEDIATYPE_Video, // Media type.
& pCap, // Capture filter.
& NULL, // Intermediate filter (optional).
& pMux); // Mux or file sink filter.
& // Release the mux filter.
& pMux-&Release();
第5个参数就是使用的上面函数返回的pMux指针。
当捕捉音频的时候,媒体类型要设置为MEDIATYPE_Audio,如果你从两个不同的设备捕捉视频和音频,你最好将音频设置成主流,这样可以防止两个数据流间drift,因为avi
mux filter为同步音频,会调整视频的播放速度的。为了设置master
流,调用IConfigAviMux::SetMasterStream方法,可以采用如下的代码:
IConfigAviMux *pConfigMux = NULL;
& hr = pMux-&QueryInterface(IID_IConfigAviMux,
(void**)&pConfigMux);
& if (SUCCEEDED(hr))
& pConfigMux-&SetMasterStream(1);
& pConfigMux-&Release();
SetMasterStream的参数指的是数据流的数目,这个是由调用RenderStream的次序决定的。例如,如果你调用RenderStream首先用于视频流,然后是音频,那么视频流就是0,音频流就是1。
添加编码filter
& IBaseFilter *pE
& // Add it to the filter graph.
& pGraph-&AddFilter(pEncoder, L"Encoder);
// Render the stream.
pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video,
& pCap, pEncoder, pMux);
& pEncoder-&Release();
2将视频流保存成wmv格式的文件
为了将视频流保存成并编码成windows media video (WMV)格式的文件,将capture pin连到WM ASF
Writer filter。
构建graph图最简单的方法就是将在ICaptureGraphBuilder2::SetOutputFileName方法中指定MEDIASUBTYPE_Asf的filter。如下
&IBaseFilter* pASFWriter = 0;
& hr = pBuild-&SetOutputFileName(
& &MEDIASUBTYPE_Asf, // Create a Windows Media
& L"C:\\VidCap.wmv", // File name.
& &pASFWriter, // Receives a pointer to the
& NULL); // Receives an IFileSinkFilter interface
pointer (optional).
参数MEDIASUBTYPE_Asf 告诉graph builder,要使用wm asf
writer作为文件接收器,于是,pbuild
就创建这个filter,将其添加到graph图中,然后调用IFileSinkFilter::SetFileName来设置输出文件的名字。第三个参数用来返回一个ASF
writer指针,第四个参数用来返回文件的指针。
在将任何pin连接到WM ASF Writer之前,一定要对WM ASF Writer进行一下设置,你可以同过WM ASF
Writer的IConfigAsfWriter接口指针来进行设置。
&IConfigAsfWriter *pConfig = 0;
pASFWriter-&QueryInterface(IID_IConfigAsfWriter,
(void**)&pConfig);
& if (SUCCEEDED(hr))
& // Configure the ASF Writer filter.
& pConfig-&Release();
然后调用ICaptureGraphBuilder2::RenderStream将capture Filter 和 ASF
writer连接起来。
&hr = pBuild-&RenderStream(
& &PIN_CATEGORY_CAPTURE, // Capture pin.
& &MEDIATYPE_Video, // Video. Use
MEDIATYPE_Audio for audio.
& pCap, // Pointer to the capture filter.
& pASFWriter); // Pointer to the sink filter (ASF
3保存成自定义的文件格式
如果你想将文件保存成自己的格式,你必须有自己的 file writer。看下面的代码
&IBaseFilter *pMux = 0;
& IFileSinkFilter *pSink = 0;
& hr = pBuild-&SetOutputFileName(
& &CLSID_MyCustomMuxFilter,
//自己开发的Filter
& L"C:\\VidCap.avi", &pMux, &pSink);
4如何将视频流保存进多个文件
当你将视频流保存进一个文件后,如果你想开始保存第二个文件,这时,你应该首先将graph停止,然后通过IFileSinkFilter::SetFileName改变
File Writer
的文件名称。注意,IFileSinkFilter指针你可以在SetOutputFileName时通过第四个参数返回的。
看看保存多个文件的代码吧
&IBaseFilter *pM
& IFileSinkFilter *pSink
pBuild-&SetOutputFileName(&MEDIASUBTYPE_Avi,
L"C:\\YourFileName.avi",
& &pMux, &pSink);
& if (SUCCEEDED(hr))
pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video,
& pCap, NULL, pMux);
&if (SUCCEEDED(hr))
& pControl-&Run();
& pControl-&Stop();
& // Change the file name and run the graph
& pSink-&SetFileName(L"YourFileName02.avi",
& pControl-&Run();
& pMux-&Release();
& pSink-&Release();
5组合视频的捕捉和预览
如果想组建一个既可以预览视频,又可以将视频保存成文件的graph,只需要两次调用ICaptureGraphBuilder2::RenderStream即可。代码如下:
&// Render the preview stream to the video
pBuild-&RenderStream(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video,
& pCap, NULL, NULL);
// Render the capture stream to the mux.
pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video,
& pCap, NULL, pMux);
在上面的代码中,graph builder 其实隐藏了下面的细节。
1 如果capture Filter既有preview pin 也有captrue
pin,那么RenderStream仅仅将两个pin和render filter接起来。如下图
2如果caprture Filter只有一个capture pin,那么Capture Graph Builder就采用一个Smart
Tee Filter将视频流分流,graph图如下
5如何控制Capture Graph(Controlling Capture Graph)
Filter图表管理器可以通过IMediaControl接口控制整个graph的运行,停止和暂停。但是当一个graph有捕捉和预览两个数据流的时候,如果我们想单独的控制其中的一个数据流话,我们可以通过ICaptureGraphBuilder2::ControlStream
下面讲一下如何来单独控制捕捉和预览数据流。
1 控制捕捉视频流
下面的代码,让捕捉数据流在graph开始运行1秒后开始,允运行4秒后结束。
&// Control the video capture stream.
& REFERENCE_TIME rtStart = , rtStop =
& const WORD wStartCookie = 1, wStopCookie = 2; //
Arbitrary values.
& hr = pBuild-&ControlStream(
& &PIN_CATEGORY_CAPTURE, // Pin
& &MEDIATYPE_Video, // Media type.
& pCap, // Capture filter.
& &rtStart, &rtStop, // Start and stop
& wStartCookie, wStopCookie // Values for the
start and stop events.
& pControl-&Run();
第一个参数表明需要控制的数据流,一般采用的是pin种类GUID,
第二个参数表明了媒体类型。
第三个参数指明了捕捉的filter。如果想要控制graph图中的所有捕捉filter,第二个和第三个参数都要设置成NULL。
第四和第五个参数表明了流开始和结束的时间,这是一个相对于graph开始的时间。
只有你调用IMediaControl::Run以后,这个函数才有作用。如果graph正在运行,这个设置立即生效。
最后的两个参数用来设置当数据流停止,开始能够得到的事件通知。对于任何一个运用此方法的数据流,graph当流开始的时候,会发送EC_STREAM_CONTROL_STARTED通知,在流结束的时候,要发送EC_STREAM_CONTROL_STOPPED通知。wStartCookie和wStopCookie是作为第二个参数的。
看看事件通知处理过程吧
&while (hr = pEvent-&GetEvent(&evCode,
&param1, &param2, 0), SUCCEEDED(hr))
& switch (evCode)
& case EC_STREAM_CONTROL_STARTED:
& // param2 == wStartCookie
&case EC_STREAM_CONTROL_STOPPED:
& // param2 == wStopCookie
& pEvent-&FreeEventParams(evCode, param1,
ControlStream还定义了一些特定的值来表示开始和停止的时间。
MAXLONGLONG 从不开始,只有在graph停止的时候才停止
NULL, 立即开始和停止
例如,下面的代码立即停止捕捉流。
&pBuild-&ControlStream(&PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video, pCap,
& 0, 0, // Start and stop times.
& wStartCookie, wStopCookie);
2控制预览视频流
只要给ControlStream第一个参数设置成PIN_CATEGORY_PREVIEW就可以控制预览pin,整个函数的使用和控制捕捉流一样,但是唯一区别是在这里你没法设置开始和结束时间了,因为预览的视频流没有时间戳,因此你必须使用NULL或者MAXLONGLONG。例子
&Use NULL to start the preview stream:
pBuild-&ControlStream(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, pCap,
& NULL, // Start now.
& 0, // (Don't care.)
& wStartCookie, wStopCookie);
& Use MAXLONGLONG to stop the preview
pBuild-&ControlStream(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, pCap,
& 0, // (Don't care.)
& MAXLONGLONG, // Stop now.
& wStartCookie, wStopCookie);
3关于数据流的控制
Pin的缺省的行为是传递sample,例如,如果你对PIN_CATEGORY_CAPTURE使用了ControlStream,但是对于PIN_CATEGORY_PREVIEW没有使用该函数,因此,当你run
graph的时候,preview 流会立即运行起来,而capture 流则要等到你设置的时间运行。
6如何配置一个视频捕捉设备
1显示VFW驱动的视频设备对话框
如果视频捕捉设备采用的仍然是VFW方式的驱动程序,则必须支持下面三个对话框,用来设置视频设备。
1 Video Source
用来选择视频输入设备并且调整设备的设置,比如亮度和对比度。
2Video Format
用来设置桢的大小和位
3Video Display
用来设置视频的显示参数
为了显示上面的三个对话框,你可以do the following
1 停止graph。
2向捕捉filter请求IAMVfwCaptureDialogs接口,如果成功,表明设备支持VFW驱动。
3调用IAMVfwCaptureDialogs::HasDialog来检查驱动程序是否支持你请求的对话框,如果支持,返回S_OK,否则返回S_FALSE。注意不要用SUCCEDED宏。
4如果驱动支持该对话框,调用IAMVfwCaptureDialogs::ShowDialog显示该对话框。
5 重新运行graph
&pControl-&Stop(); // Stop the graph.
& // Query the capture filter for the
IAMVfwCaptureDialogs interface.
& IAMVfwCaptureDialogs *pVfw = 0;
pCap-&QueryInterface(IID_IAMVfwCaptureDialogs,
(void**)&pVfw);
& if (SUCCEEDED(hr))
& // Check if the device supports this dialog
& if (S_OK ==
pVfw-&HasDialog(VfwCaptureDialog_Source))
& // Show the dialog box.
pVfw-&ShowDialog(VfwCaptureDialog_Source, hwndParent);
& pControl-&Run();
2 调整视频的质量
WDM驱动的设备支持一些属性可以用来调整视频的质量,比如亮度,对比度,饱和度,等要向调整视频的质量,do the
1 从捕捉filter上请求IAMVideoProcAmp接口
对于你想调整的任何一个属性,调用IAMVideoProcAmp::GetRange可以返回这个属性赋值的范围,缺省值,最小的增量值。IAMVideoProcAmp::Get返回当前正在使用的值。VideoProcAmpProperty枚举每个属性定义的标志。
3调用IAMVideoProcAmp::Set来设置这个属性值。设置属性的时候,不用停止graph。
看看下面的代码是如何调整视频的质量的
HWND hT // Handle to the trackbar control.
& // Initialize hTrackbar (not shown).
// Query the capture filter for the IAMVideoProcAmp
interface.
& IAMVideoProcAmp *pProcAmp = 0;
pCap-&QueryInterface(IID_IAMVideoProcAmp,
(void**)&pProcAmp);
& if (FAILED(hr))
& // The device does not support IAMVideoProcAmp,
so disable the control.
& EnableWindow(hTrackbar, FALSE);
& long Min, Max, Step, Default, Flags, V
&// Get the range and default value.
m_pProcAmp-&GetRange(VideoProcAmp_Brightness, &Min,
&Max, &Step,
& &Default, &Flags);
& if (SUCCEEDED(hr))
& // Get the current value.
& hr = m_pProcAmp-&Get(VideoProcAmp_Brightness,
&Val, &Flags);
& if (SUCCEEDED(hr))
& // Set the trackbar range and position.
& SendMessage(hTrackbar, TBM_SETRANGE, TRUE,
MAKELONG(Min, Max));
& SendMessage(hTrackbar, TBM_SETPOS, TRUE,
& EnableWindow(hTrackbar, TRUE);
& // This property is not supported, so disable
the control.
& EnableWindow(hTrackbar, FALSE);
3调整视频输出格式
我们知道视频流可以有多种输出格式,一个设备可以支持16-bit RGB, 32-bit RGB, and
YUYV,在每一种格式下,设备还可以调整视频桢的大小。
在WDM驱动设备上,IAMStreamConfig
接口用来报告设备输出视频的格式的,VFW设备,可以采用对话框的方式来设置,参见前面的内容。
捕捉Filter的捕捉pin和预览pin都支持IAMStreamConfig
接口,可以通过ICaptureGraphBuilder2::FindInterface获得IAMStreamConfig接口。
&IAMStreamConfig *pConfig = NULL;
& hr = pBuild-&FindInterface(
& &PIN_CATEGORY_PREVIEW, // Preview pin.
& 0, // Any media type.
& pCap, // Pointer to the capture filter.
& IID_IAMStreamConfig, (void**)&pConfig);
设备还支持一系列的媒体类型,对于每一个媒体类型,设备都要支持一系列的属性,比如,桢的大小,图像如何缩放,桢率的范围等。
通过IAMStreamConfig::GetNumberOfCapabilities获得设备所支持的媒体类型的数量。这个方法返回两个值,一个是媒体类型的数量,二是属性所需结构的大小。
这个结构的大小很重要,因为这个方法是用于视频和音频的,视频采用的是VIDEO_STREAM_CONFIG_CAPS结构,音频用AUDIO_STREAM_CONFIG_CAPS结构。
通过函数IAMStreamConfig::GetStreamCaps来枚举媒体类型,要给这个函数传递一个序号作为参数,这个函数返回媒体类型和相应的属性结构体。看代码把
&int iCount = 0, iSize = 0;
pConfig-&GetNumberOfCapabilities(&iCount, &iSize);
// Check the size to make sure we pass in the correct
structure.
& if (iSize ==
sizeof(VIDEO_STREAM_CONFIG_CAPS)
& // Use the video capabilities structure.
& for (int iFormat = 0; iFormat & iC
iFormat++)
& VIDEO_STREAM_CONFIG_CAPS
& AM_MEDIA_TYPE *pmtC
& hr = pConfig-&GetStreamCaps(iFormat,
&pmtConfig, (BYTE*)&scc);
& if (SUCCEEDED(hr))
& // Delete the media type when you are
pConfig-&SetFormat(pmtConfig);//重新设置视频格式
& DeleteMediaType(pmtConfig);
你可以调用IAMStreamConfig::SetFormat设置新的媒体类型
hr = pConfig-&SetFormat(pmtConfig);
如果pin没有连接,当连接的时候就试图用新的格式,如果pin已经在连接了,它就会用的新的媒体格式重新连接。在任何一种情况下,下游的filter都有可能拒绝新的媒体格式。
在SetFormat前你可以修改VIDEO_STREAM_CONFIG_CAPS结构来重新设置媒体类型。
如果GetStreamCaps返回的是24-bit RGB format,桢的大小是320 x 240
像素,你可以通过检查媒体类型的major type,subtpye,和format等值
&if ((pmtConfig.majortype == MEDIATYPE_Video)
& (pmtConfig.subtype == MEDIASUBTYPE_RGB24)
& (pmtConfig.formattype == FORMAT_VideoInfo)
& (pmtConfig.cbFormat &= sizeof
(VIDEOINFOHEADER)) &&
& (pmtConfig.pbFormat != NULL))
& VIDEOINFOHEADER *pVih =
(VIDEOINFOHEADER*)pmtConfig.pbF
& // pVih contains the detailed format
information.
& LONG lWidth = pVih-&bmiHeader.biW
& LONG lHeight =
pVih-&bmiHeader.biH
VIDEO_STREAM_CONFIG_CAPS结构里包含了该媒体类型的视频长度和宽度的最大值和最小值,还有递增的幅度值,就是每次调整视频size的幅度,例如,设备可能返回如下的值
? MinOutputSize: 160 x 120
? MaxOutputSize: 320 x 240
? OutputGranularityX: 8 pixels (horizontal step size)
? OutputGranularityY: 8 pixels (vertical step size)
这样你可以在(160, 168, 176, ... 304, 312, 320) 范围内设置宽度,在 (120, 128, 136,
... 104, 112, 120).设置高度值,
如果想设置新的值,直接修改在GetStreamCaps函数中返回的值即可,
&pVih-&bmiHeader.biWidth = 160;
& pVih-&bmiHeader.biHeight = 120;
& pVih-&bmiHeader.biSizeImage =
DIBSIZE(pVih-&bmiHeader);
然后将媒体类型传递给SetFormat函数,就可修改视频格式了。
7将设备从系统中移走时的事件通知(Device remove Notify)
如果用户将一个graph正在使用的即插即用型的设备从系统中去掉,filter图表管理器就会发送一个EC_DEVICE_LOST事件通知,如果该设备又可以使用了,filter图表管理器就发送另外的一个EC_DEVICE_LOST通知,但是先前组建的捕捉filter
graph图就没法用了,用户必须重新组建graph图。
当系统中有新的设备添加时,dshow是不会发送任何通知的,所以,应用程序如果想要知道系统中何时添加新的设备,应用程序可以监控WM_DEVICECHANGE消息。
8从静止图像pin中捕捉图片
有些照相机,摄像头除了可以捕获视频流以外还可以捕获单张的,静止的图片。通常,静止的图片的质量要比流的质量要高。摄像头一般都一个按钮来触发,或者是支持软件触发。支持输出静态图片的摄像头一般都要提供一个静态图片pin,这个pin的种类是PIN_CATEGORY_STILL。
从设备中获取静态图片,我们一般推荐使用windows Image Acquisition (WIA)
APIs。当然,你也可以用dshow来获取图片。
在graph运行的时候利用IAMVideoControl::SetMode来触发静态的pin。代码如下
&pControl-&Run(); // Run the graph.
& IAMVideoControl *pAMVidControl = NULL;
pCap-&QueryInterface(IID_IAMVideoControl,
(void**)&pAMVidControl);
& if (SUCCEEDED(hr))
& // Find the still pin.
& IPin *pPin = 0;
& hr = pBuild-&FindPin(pCap, PINDIR_OUTPUT,
&PIN_CATEGORY_STILL, 0,
& FALSE, 0, &pPin);
& if (SUCCEEDED(hr))
& hr = pAMVidControl-&SetMode(pPin,
VideoControlFlag_Trigger);
& pPin-&Release();
& pAMVidControl-&Release();
首先向capture Filter
请求IAMVideoContol,如果支持该接口,就调用ICaptureGraphBuilder2::FindPin请求指向静止pin
的指针,然后调用pin的put_Mode方法。
根据不同的摄像头,你可能静态pin连接前要render 该pin。
捕捉静态图片常用的filter是Sample Grabber filter,Sample
Grabber使用了一个用户定义的回调汗水来处理图片。关于这个filter的详细用法,参见Using the Sample
Grabber.。
下面的例子假设静态pin传递的是没有压缩的RGB图片。首先定义一个类,从ISampleGrabberCB继承。
&// Class to hold the callback function for the
Sample Grabber filter.
& class SampleGrabberCallback : public
ISampleGrabberCB
& // Implementation is described later.
// Global instance of the class.
& SampleGrabberCallback g_StillCapCB;
然后将捕捉filter的静态pin连接到Sample Grabber,将Sample Grabber连接到Null
Renderer filter。Null
Renderer仅仅是将她接收到的sample丢弃掉。实际的工作都是在回调函数里进行,连接Null Renderer
仅仅是为了给Sample Grabber's 输出pin上连接点东西。具体见下面的代码
& // Add the Sample Grabber filter to the
& IBaseFilter *pSG_F
& hr = CoCreateInstance(CLSID_SampleGrabber, NULL,
CLSCTX_INPROC_SERVER,
& IID_IBaseFilter, (void**)&pSG_Filter);
& hr = pGraph-&AddFilter(pSG_Filter,
L"SampleGrab");
// Add the Null Renderer filter to the graph.
& IBaseFilter *pN
& hr = CoCreateInstance(CLSID_NullRendere, NULL,
CLSCTX_INPROC_SERVER,
& IID_IBaseFilter, (void**)&pNull);
& hr = pGraph-&AddFilter(pSG_Filter,
L"NullRender");
然后通过RenderStream将still pin ,sample grabber ,null
Renderer连接起来
&hr = pBuild-&RenderStream(
& &PIN_CATEGORY_STILL, // Connect this pin
& &MEDIATYPE_Video, // with this media type
& pCap, // on this filter ...
& pSG_Filter, // to the Sample Grabber ...
& pNull); // ... and finally to the Null
然后调用ISampleGrabber指针,来通过这个指针可以分配内存。
& // Configure the Sample Grabber.
& ISampleGrabber *pSG;
pSG_Filter-&QueryInterface(IID_ISampleGrabber,
(void**)&pSG);
& pSG-&SetOneShot(FALSE);
& pSG-&SetBufferSamples(TRUE);
设置你的回调对象
&pSG-&SetCallback(&g_StillCapCB, 0); //
0 = Use the SampleCB callback
获取静态pin和sample grabber之间连接所用的媒体类型
&// Store the media type for later use.
& AM_MEDIA_TYPE g_StillMediaT
pSG-&GetConnectedMediaType(&g_StillMediaType);
& pSG-&Release();
媒体类型包含一个BITMAPINFOHEADER结构来定义图片的格式,在程序退出前一定要释放媒体类型
&// On exit, remember to release the media
& FreeMediaType(g_StillMediaType);
看看下面的回调类吧。这个类从ISampleGrabber接口派生,但是它没有保持引用计数,因为应用程序在堆上创建这个对象,在整个graph的生存周期它都存在。
所有的工作都在BufferCB函数里完成,当有一个新的sample到来的时候,这个函数就会被sample
Grabber调用到。在下面的例子里,bitmap被写入到一个文件中
&class SampleGrabberCallback : public
ISampleGrabberCB
& // Fake referance counting.
& STDMETHODIMP_(ULONG) AddRef() { return 1;
& STDMETHODIMP_(ULONG) Release() { return 2;
&STDMETHODIMP QueryInterface(REFIID riid, void
**ppvObject)
& if (NULL == ppvObject) return E_POINTER;
& if (riid == __uuidof(IUnknown))
& *ppvObject = static_cast(this);
& return S_OK;
& if (riid == __uuidof(ISampleGrabberCB))
& *ppvObject = static_cast(this);
& return S_OK;
& return E_NOTIMPL;
&STDMETHODIMP SampleCB(double Time, IMediaSample
& return E_NOTIMPL;
&STDMETHODIMP BufferCB(double Time, BYTE *pBuffer,
long BufferLen)
& if ((g_StillMediaType.majortype !=
MEDIATYPE_Video) ||
& (g_StillMediaType.formattype !=
FORMAT_VideoInfo) ||
& (g_StillMediaType.cbFormat &
sizeof(VIDEOINFOHEADER)) ||
& (g_StillMediaType.pbFormat == NULL))
& return VFW_E_INVALIDMEDIATYPE;
& HANDLE hf = CreateFile("C:\\Example.bmp",
GENERIC_WRITE,
& FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0,
& if (hf == INVALID_HANDLE_VALUE)
& return E_FAIL;
& long cbBitmapInfoSize =
g_StillMediaType.cbFormat - SIZE_PREHEADER;
& VIDEOINFOHEADER *pVideoHeader =
(VIDEOINFOHEADER*)g_StillMediaType.pbF
&BITMAPFILEHEADER
& ZeroMemory(&bfh, sizeof(bfh));
& bfh.bfType = 'MB'; // Little-endian for
& bfh.bfSize = sizeof( bfh ) + BufferLen +
cbBitmapInfoS
& bfh.bfOffBits = sizeof( BITMAPFILEHEADER ) +
cbBitmapInfoS
& // Write the file header.
& DWORD dwWritten = 0;
& WriteFile( hf, &bfh, sizeof( bfh ),
&dwWritten, NULL );
& WriteFile(hf, HEADER(pVideoHeader),
cbBitmapInfoSize, &dwWritten, NULL);
& WriteFile( hf, pBuffer, BufferLen,
&dwWritten, NULL );
& CloseHandle( hf );
& return S_OK;
作者Blog:
- 作者: benjaminlei 日, 星期一 16:11 
关键字 Windows
原作者姓名 陆其明
文章原始出处 http://hqtech.nease.net
Windows Media媒体内容采用的是ASF文件格式。Microsoft公司提供了Windows Media Format
SDK,以支持以下三大功能:ASF文件的生成(包括ASF数据的流化)、ASF文件的编辑和ASF文件的播放(包括ASF媒体流的播放)。在开始编程之前,对SDK系统有个总体的认识和理解是必要的。(注:如果未作特别说明,本书所述的SDK或WMF
SDK均是指Windows Media Format SDK。)
SDK采用了面向对象的设计,是一套完全基于COM组件模型的高级开发包。这里说高级,意味着SDK屏蔽了ASF数据格式、网络传输控制等较为底层的流媒体技术实现细节,留给开发者的只有比较上层的编程接口。
图2.1 WMF SDK系统结构
如图2.1,SDK引入了一系列的对象,如生成器、读取器、编辑器等。正是这些SDK对象屏蔽了Windows
Media技术实现细节,使得应用程序开发者不需要了解太多的流媒体底层技术,也能开发出非常专业的流媒体应用系统。
在SDK引入的众多对象中,最重要的莫过于图2.1中列出的那三个了。其中,生成器一般用于将非压缩的媒体数据编码成Windows
Media格式的数据。整个编码过程非常简单,只要通过一个叫Profile的对象配置好目标数据流格式,再将输入数据的格式告诉生成器,然后依次将输入数据按照既定的方式传给生成器,剩下的事就都可以交给生成器了。生成器内部会根据Profile的配置,自动选择一种Windows
Media编码器,然后对输入数据进行压缩编码,并且完成打包,最终写成文件或者向网络流化。
图2.2 生成器的工作流程
如图2.2是生成器大致的工作流程。可以看到,生成器内部对输入数据可能有一个预处理过程。因为特定编码器能够接受的数据格式总是有限的。如果输入数据格式不能被编码器接受,生成器内部会自动进行一个预处理过程。这些预处理工作包括视频的图像大小缩放、帧率调整、色彩空间转换、水印叠加,音频的采样频率转换等等。另外,生成器本身不负责编码后的数据的最终去向,而是把这个任务交给了一种叫接收器(Sink)的对象,比如交给文件接收器就可以将数据保存为文件,交给网络接收器就可以将数据进行广播,交给推接收器就可以将数据分发到运行Windows
Media Services的服务器上等等。SDK采用这种“生成器 +
接收器”的设计是很先进的,它保证了SDK系统具有很强的扩展性。比如我们可以开发自己的接收器,按我们的要求决定压缩数据的最终去向。
如果你用过Microsoft公司官方发布的Windows Media内容的编码软件Windows Media
Encoder的话,不要怀疑,通过WMF SDK的学习,你也可以开发出一个类似的编码软件。
SDK有两个读取器:异步读取器和同步读取器。两个读取器的功能类似,都是用于读取ASF数据,不同的是它们的工作方式:异步读取器内部自带一个线程来把数据流推给应用程序;而同步读取器内部没有驱动数据流的线程,应用程序直接(在应用程序的线程中)通过同步读取器上的接口函数来获取数据。(注:异步读取器通常简称为读取器,而只在与同步读取器对比时才强调“异步”特性。)
图2.3 读取器的工作流程
默认情况下,读取器输出一种非压缩格式的数据。也就是说,读取器内部会自动选择合适的解码器对ASF流数据进行解压缩,如图2.3。当然,通过适当的设置,读取器也可以输出ASF流数据原有的压缩格式。
值得注意的是,异步读取器在各个版本的SDK中都能使用;而同步读取器是在9系列的SDK中才开始有的。之所以增加同步读取器,是因为数据的同步读取模式在一些特殊的场合下较有优势,比如在一些进行内容编辑的应用程序中,使用同步读取器可以快速读取指定位置的一部分数据。另外,同步读取器通过直接的接口函数调用来提供数据,使用起来也比较简单。当然,同步读取器也有明显的不足,比如不支持读取网络文件,不支持DRM(数字版权管理)等等。因此,在绝大多数ASF文件读取的场合下还是使用异步读取器。
最后来简单介绍一下编辑器。说是编辑器,大家不要误会它能做媒体内容的修改。其实它只能编辑一些元数据。所谓元数据,就是这个媒体内容的标题、描述、作者、版权等信息。
- 作者: benjaminlei 日, 星期四 14:13 
Getting Started
with the Game API
While DirectDraw is not available to game developers for the
Pocket PC platform, the really good news is that Microsoft created
a new game API especially for the Pocket PC named "GAPI" -- the
game application programming interface. It allows not only direct
access to the display memory but also to the Pocket PC hardware
Applies to:
&&&Microsoft
Windows Powered Pocket PC 2000
&&&GAPI SDK,
Runtime DLLs, and a sample, which are located on the Second CD of
the EVTools in the PocketPCSDK\Program Files\Support\GameAPI
Step 1: Create a Simple Hello World
Application
The GAPI is not integrated in the Pocket PC SDK. So some
preliminary steps:
Manually copy the GX.H Include file into the Include directory
of the Pocket PC SDK.
Copy the CPU-dependent GX.LIB in the corresponding Lib
directory of the Pocket PC SDK.
And finally, copy the CPU-dependent GX.DLL into the Windows
directory of your Pocket PC.
Now you're ready to build your first simple Hello World
Launch Microsoft eMbedded Visual C++, create a new Pocket PC
application project, and name it "FirstGX."
Once the wizard has finished creating the new application, let's
add the Game API initialization calls.
Add the following lines of source-code shown in bold at the end
of the InitInstance function:
UpdateWindow(hWnd);
// Try opening the Display for Full screen access
if (GXOpenDisplay(hWnd, GX_FULLSCREEN) == 0) {
return FALSE;
// we won't be able to draw.
// Initialize the Hardware Buttons
GXOpenInput();
// Get the Display properties
g_gxdp = GXGetDisplayProperties();
// Get information about the Hardware Keys and fills
// the g_gxkl structure
g_gxkl = GXGetDefaultKeys(GX_NORMALKEYS);
return TRUE;
The code uses two variables that have to be defined in the
global space of the FirstGX.cpp source file. We also have to
include GX.H to our source to avoid compiler errors.
Add the following lines directly below the include files, the
eMbedded Visual C++ wizard has created:
#include "gx.h"
GXDisplayProperties g_
// GX struct
GXKeyList g_
// GX struct
To be a good citizen we should clean up the Game API before we
leave the application.
Insert the following two lines at the end of the WinMain
GXCloseDisplay();
GXCloseInput();
Step 2: Writing Directly in the Display
Before we can start coding here we have to dig into a little
theory. Depending on your Pocket PC hardware you can have either a
16-bit color display (The Casio, HP, or Compaq) or an 8-bit display
(black-and-white or color, like the Compaq Aero 1550). To determine
which display your Pocket PC has, you can evaluate the member cBPP
of the g_gxdp structure. It will tell you exactly how many bits you
have to deal with.
Before directly accessing the display memory for 16-bit
displays, you have to know how the display controller interprets
the 16-bit color mask. There are two possible flag settings:
KfDirect555 and kfDirect565. The 555 translates into a color mask
XRRRRRGG.GGGBBBBB for Red-Green-Blue in
a short variable, while 565 translates into
RRRRRGG.GGGXBBBBB.
On 8-bit color devices you can use a byte to address the 256
individual colors.
Now we are ready to write to the screen. We want to fill the
whole screen with one color when one of the keys is pressed.
Add the following function to the top of your FirstGX.cpp
source file:
bool ClearScreen(int colRed,int colGreen, int colBlue)
// 16 bit per pixel code.
Note 2 different pixel formats.
switch (g_gxdp.cBPP)
case 16: {
unsigned short PixelCol = 0;
if (g_gxdp.ffFormat & kfDirect565) {
PixelCol = (unsigned short)
(((colRed & 0xff))&&3)&& 11 |
((colGreen & 0xff)&&3) && 6 |
((colBlue & 0xff)&&3));
} else if (g_gxdp.ffFormat & kfDirect555) {
PixelCol = (unsigned short)
((colRed & 0xff)&& 10 |
(colGreen & 0xff) && 5 |
(colBlue & 0xff));
unsigned short * pusLine = (unsigned short *)GXBeginDraw();
if (pusLine == NULL) // NOT OK TO DRAW
for (unsigned int y = 0; y & g_gxdp.cyH y++) {
unsigned short * pusDest = pusL
for (unsigned int x = 0;
x & g_gxdp.cxW x++) {
*pusDest = PixelC
pusDest += g_gxdp.cbxPitch && 1;
pusLine += g_gxdp.cbyPitch && 1;
GXEndDraw();
// Get a Random Color
// IMPORTANT:
// 8Bit are using a palette and the formular
// below does not compute a read RGB color
unsigned char bPixel = (colRed &0xf)&&5 |
(colGreen&0x3)&&3 |
(colBlue&0xF);
unsigned char * pbLine = (unsigned char *)GXBeginDraw();
if (pbLine == NULL)// NOT OK TO DRAW
for (unsigned int y = 0; y & g_gxdp.cyH y++) {
unsigned char * pbDest = pbL
for (unsigned int x = 0;
x & g_gxdp.cxW x++) {
*pbDest = bP
pbDest += g_gxdp.cbxP
pbLine += g_gxdp.cbyP
GXEndDraw();
Step 3: Accessing the Hardware Keys
This is the simplest task of all. All of you should be familiar
with WM_ messages of Microsoft Windows and how to use them. With
this knowledge you already know how to use the Pocket PC hardware
keys. Just parse the WM_KEYUP or WM_KEYDOWN message in your Windows
procedure.
Here is the WM_KEYDOWN branch for our FirstGX application:
case WM_KEYDOWN:
vkKey = (short)wP
if (vkKey == g_gxkl.vkUp) {
ClearScreen(0,0,0); // Black
if (vkKey == g_gxkl.vkDown) {
ClearScreen(255,0,0); // Red
if (vkKey == g_gxkl.vkLeft) {
ClearScreen(0,255,0); // Green
if (vkKey == g_gxkl.vkRight) {
ClearScreen(0,0,255); // Blue
if (vkKey == g_gxkl.vkStart) {
SendMessage(hWnd, WM_CLOSE, 0, 0);
A list of all possible key names is found in the GXKeyList
structure definition in the gx.h include file.
Step 4: Get Ready to Launch!
Before we can compile and download our new FirstGX application
to our Pocket PC we have to add the gx.lib to the list of libraries
of our project. Now compile and download your first Game API
application to your Pocket PC and play with the keys to see what's
happening.
Conclusion: Easy Stuff!
As you can see, the Game API is very simple but still powerful
enough to cover all your needs to write fast action games. The
sample provided with the Game API shows some more details and
tricks on how to use the GAPI. If you wish you can download the
full source code for this sample.
- 作者: benjaminlei 日, 星期四 14:03 
本文作者:sodme
本文出处:
声明:本文可以不经作者同意任意转载,但任何对本文的引用都须注明作者、出处及此声明信息。谢谢!!
  要了解此篇文章中引用的本人写的另一篇文章,请到以下地址:
  以上的这篇文章是早在去年的时候写的了,当时正在作休闲平台,一直在想着如何实现一个可扩充的支持百万人在线的游戏平台,后来思路有了,就写了那篇总结。文章的意思,重点在于阐述一个百万级在线的系统是如何实施的,倒没真正认真地考察过QQ游戏到底是不是那样实现的。
  近日在与业内人士讨论时,提到QQ游戏的实现方式并不是我原来所想的那样,于是,今天又认真抓了一下QQ游戏的包,结果确如这位兄弟所言,QQ游戏的架构与我当初所设想的那个架构相差确实不小。下面,我重新给出QQ百万级在线的技术实现方案,并以此展开,谈谈大型在线系统中的负载均衡机制的设计。
  从QQ游戏的登录及游戏过程来看,QQ游戏中,也至少分为三类服务器。它们是:
  第一层:登陆/账号服务器(Login
Server),负责验证用户身份、向客户端传送初始信息,从QQ聊天软件的封包常识来看,这些初始信息可能包括“会话密钥”此类的信息,以后客户端与后续服务器的通信就使用此会话密钥进行身份验证和信息加密;
  第二层:大厅服务器(估且这么叫吧, Game Hall
Server),负责向客户端传递当前游戏中的所有房间信息,这些房间信息包括:各房间的连接IP,PORT,各房间的当前在线人数,房间名称等等。
  第三层:游戏逻辑服务器(Game Logic Server),负责处理房间逻辑及房间内的桌子逻辑。
  从静态的表述来看,以上的三层结构似乎与我以前写的那篇文章相比并没有太大的区别,事实上,重点是它的工作流程,QQ游戏的通信流程与我以前的设想可谓大相径庭,其设计思想和技术水平确实非常优秀。具体来说,QQ游戏的通信过程是这样的:
  1.由Client向Login Server发送账号及密码等登录消息,Login
Server根据校验结果返回相应信息。可以设想的是,如果Login Server通过了Client的验证,那么它会通知其它Game
Hall Server或将通过验证的消息以及会话密钥放在Game Hall Server也可以取到的地方。总之,Login
Server与Game Hall Server之间是可以共享这个校验成功消息的。一旦Client收到了Login
Server返回成功校验的消息后,Login Server会主动断开与Client的连接,以腾出socket资源。Login
Server的IP信息,是存放在QQGame\config\QQSvrInfo.ini里的。
  2.Client收到Login Server的校验成功等消息后,开始根据事先选定的游戏大厅入口登录游戏大厅,各个游戏大厅Game
Hall Server的IP及Port信息,是存放在QQGame\Dirconfig.ini里的。Game Hall
Server收到客户端Client的登录消息后,会根据一定的策略决定是否接受Client的登录,如果当前的Game Hall
Server已经到了上限或暂时不能处理当前玩家登录消息,则由Game Hall
Server发消息给Client,以让Client重定向到另外的Game Hall
Server登录。重定向的IP及端口信息,本地没有保存,是通过数据包或一定的算法得到的。如果当前的Game Hall
Server接受了该玩家的登录消息后,会向该Client发送房间目录信息,这些信息的内容我上面已经提到。目录等消息发送完毕后,Game
Hall Server即断开与Client的连接,以腾出socket资源。在此后的时间里,Client每隔30分钟会重新连接Game
Hall Server并向其索要最新的房间目录信息及在线人数信息。
  3.Client根据列出的房间列表,选择某个房间进入游戏。根据我的抓包结果分析,QQ游戏,并不是给每一个游戏房间都分配了一个单独的端口进行处理。在QQ游戏里,有很多房间是共用的同一个IP和同一个端口。比如,在斗地主一区,前50个房间,用的都是同一个IP和Port信息。这意味着,这些房间,在QQ游戏的服务器上,事实上,可能是同一个程序在处理!!!QQ游戏房间的人数上限是400人,不难推算,QQ游戏单个服务器程序的用户承载量是2万,即QQ的一个游戏逻辑服务器程序最多可同时与2万个玩家保持TCP连接并保证游戏效率和品质,更重要的是,这样可以为腾讯省多少money呀!!!哇哦!QQ确实很牛。以2万的在线数还能保持这么好的游戏品质,确实不容易!QQ游戏的单个服务器程序,管理的不再只是逻辑意义上的单个房间,而可能是许多逻辑意义上的房间。其实,对于服务器而言,它就是一个大区服务器或大区服务器的一部分,我们可以把它理解为一个庞大的游戏地图,它实现的也是分块处理。而对于每一张桌子上的打牌逻辑,则是有一个统一的处理流程,50个房间的50*100张桌子全由这一个服务器程序进行处理(我不知道QQ游戏的具体打牌逻辑是如何设计的,我想很有可能也是分区域的,分块的)。当然,以上这些只是服务器作的事,针对于客户端而言,客户端只是在表现上,将一个个房间单独罗列了出来,这样作,是为便于玩家进行游戏以及减少服务器的开销,把这个大区中的每400人放在一个集合内进行处理(比如聊天信息,“向400人广播”和“向2万人广播”,这是完全不同的两个概念)。
  4.需要特别说明的一点。进入QQ游戏房间后,直到点击某个位置坐下打开另一个程序界面,客户端的程序,没有再创建新的socket,而仍然使用原来大厅房间客户端跟游戏逻辑服务器交互用的socket。也就是说,这是两个进程共用的同一个socket!不要小看这一点。如果你在创建桌子客户端程序后又新建了一个新的socket与游戏逻辑服务器进行通信,那么由此带来的玩家进入、退出、逃跑等消息会带来非常麻烦的数据同步问题,俺在刚开始的时候就深受其害。而一旦共用了同一个socket后,你如果退出桌子,服务器不涉及释放socket的问题,所以,这里就少了很多的数据同步问题。关于多个进程如何共享同一个socket的问题,请去google以下内容:WSADuplicateSocket。
  以上便是我根据最新的QQ游戏抓包结果分析得到的QQ游戏的通信流程,当然,这个流程更多的是客户端如何与服务器之间交互的,却没有涉及到服务器彼此之间是如何通信和作数据同步的。关于服务器之间的通信流程,我们只能基于自己的经验和猜想,得出以下想法:
  1.Login Server与Game Hall Server之前的通信问题。Login
Server是负责用户验证的,一旦验证通过之后,它要设法让Game Hall
Server知道这个消息。它们之前实现信息交流的途径,我想可能有这样几条:a. Login
Server将通过验证的用户存放到临时数据库中;b. Login
Server将验证通过的用户存放在内存中,当然,这个信息,应该是全局可访问的,就是说所有QQ的Game Hall
Server都可以通过服务器之间的数据包通信去获得这样的信息。
  2.Game Hall
Server的最新房间目录信息的取得。这个信息,是全局的,也就是整个游戏中,只保留一个目录。它的信息来源,可以由底层的房间服务器逐级报上来,报给谁?我认为就如保存的全局登录列表一样,它报给保存全局登录列表的那个服务器或数据库。
  3.在QQ游戏中,同一类型的游戏,无法打开两上以上的游戏房间。这个信息的判定,可以根据全局信息来判定。
  以上关于服务器之间如何通信的内容,均属于个人猜想,QQ到底怎么作的,恐怕只有等大家中的某一位进了腾讯之后才知道了。呵呵。不过,有一点是可以肯定的,在整个服务器架构中,应该有一个地方是专门保存了全局的登录玩家列表,只有这样才能保证玩家不会重复登录以及进入多个相同类型的房间。
  在前面的描述中,我曾经提到过一个问题:当登录当前Game Hall
Server不成功时,QQ游戏服务器会选择让客户端重定向到另位的服务器去登录,事实上,QQ聊天服务器和MSN服务器的登录也是类似的,它也存在登录重定向问题。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 斗鱼tv有电脑客户端吗 的文章

 

随机推荐