ONVIF客户端搜索设备获取rtsp地址开发笔记(精华篇)

原文  http://blog.csdn.net/gubenpeiyuan/article/details/25618177

概要:

前言及鸣谢:

感谢guog先生,快活林高先生,onvif全国交流群的的酷夏先生在开发过程中给予的巨大支持,没有你们的帮助开发过程将异常艰难啊。谢谢了!

ONVIF介绍:

ONVIF致力于通过全球性的开放接口标准来推进 网络视频 在安防市场的应用,这一接口标准将确保不同厂商生产的网络视频产品具有互通性。2008年11月,论坛正式发布了ONVIF第一版规范——ONVIF核心规范1.0。随着 视频监控 的网络化应用,产业链的分工将越来越细。有些厂商专门做摄像头,有些厂商专门做DVS,有些厂商则可能专门做平台等,然后通过 集成商 进行集成,提供给最终客户。这种产业合作模式,已经迫切的需要行业提供越来越标准化的接口平台。

流程总览:

本文开发环境:Centos6.4  Gsoap:2.8.16  soap:1.2 onvif:2.4 。 注: 本文提供的参考代码其实网上都可以找到,这里做一个整理,供大家交流学习,共同提高。

搜索:Probe : 发现网络摄像头,获取 webserver 地址

http://192.168.15.240/onvif/device_service

能力获取:GetCapabilities :获取设备能力文件,从中识别出媒体信息地址 URI:    http://192.168.15.240/onvif/Media

媒体信息获取:GetProfiles : 获取媒体信息文件,识别主通道、子通道的视频编码分辨率

RTSP地址获取:GetStreamUri :获取指定通道的流媒体地址    rtsp://192.168.15.240:554/Streaming/Channels/2?transportmode=unicast

Gsoap及开发框架生成:

1.下载Gsoap:地址: http://sourceforge.net/projects/gsoap2/files/gSOAP/

2.安装:  ./configure && make && make install

期间可能会有一些报错,自己解决哦。

3.离线或者在线生成onvif.h。如果不需要最新的版本推荐离线方式。笔者使用的是这种方式。

离线文件下载地址:感谢guog先生的共享:

命令:

wsdl2h -o onvif.h -c -s -t ./typemap.dat devicemgmt.wsdl media.wsdl event.wsdl display.wsdl deviceio.wsdl imaging.wsdl ptz.wsdl receiver.wsdl recording.wsdl search.wsdl remotediscovery.wsdl replay.wsdl analytics.wsdl analyticsdevice.wsdl actionengine.wsdl accesscontrol.wsdl doorcontrol.wsdl

离线文件在:

http://download.csdn.net/detail/u011597695/5875143

在线命令:

wsdl2h -o onvif.h -c -s -t ./typemap.dat http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl http://www.onvif.org/onvif/ver10/event/wsdl/event.wsdl http://www.onvif.org/onvif/ver10/display.wsdl http://www.onvif.org/onvif/ver10/deviceio.wsdl http://www.onvif.org/onvif/ver20/imaging/wsdl/imaging.wsdl http://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl http://www.onvif.org/onvif/ver10/receiver.wsdl  http://www.onvif.org/onvif/ver10/recording.wsdl http://www.onvif.org/onvif/ver10/search.wsdl http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl http://www.onvif.org/onvif/ver10/replay.wsdl http://www.onvif.org/onvif/ver20/analytics/wsdl/analytics.wsdl http://www.onvif.org/onvif/ver10/analyticsdevice.wsdl http://www.onvif.org/ver10/actionengine.wsdl http://www.onvif.org/ver10/pacs/accesscontrol.wsdl http://www.onvif.org/ver10/pacs/doorcontrol.wsdl

(记得拷贝gsoap的typemap文件至生成目录下,wsdl2h命令需要这个。)

离线命令:

wsdl2h -o onvif.h -c -s -t ./typemap.dat devicemgmt.wsdl media.wsdl event.wsdl display.wsdl deviceio.wsdl imaging.wsdl ptz.wsdl receiver.wsdl recording.wsdl search.wsdl remotediscovery.wsdl replay.wsdl analytics.wsdl analyticsdevice.wsdl actionengine.wsdl accesscontrol.wsdl doorcontrol.wsdl

现在可以开始生成了:如下:

如果直接生成对应C的库文件会发生重复定义错误,可以修改该文件。

wsa5.h(288): **ERROR**: remote method name clash: struct/class ‘SOAP_ENV__Fault‘ already declared at line 274

打开文件gsoap_2.8.16/gsoap-2.8/gsoap/import/ wsa5.h

将277行int SOAP_ENV__Fault修改为int SOAP_ENV__Fault_alex

笔者没有使用这种方法,是将这个结构体直接注释的方式,最后的结果是,都可以使用。

同时上一步生成的onvif.h文件中没有打开wsse.h, 导致最后生成代码中SOAP_ENV__Header 结构体中缺少定义 wsse__Security数据段,无法进行鉴权命令。

即:添加对openssl的支持,在上一步生成的onvif.h中添加(可选)

#import "wsse.h"  

随后使用命令生成:

soapcpp2  -c onvif.h -x -I/root/Tools/Gsoap/gsoap-2.8/gsoap/import -I/root/Tools/Gsoap/gsoap-2.8/gsoap/ -I/root/Tools/Gsoap/gsoap-2.8/gsoap/custom -I/root/Tools/Gsoap/gsoap-2.8/gsoap/extras -I/root/Tools/Gsoap/gsoap-2.8/gsoap/plugin


到此为止,基于 C 的客户端和服务器的Onvif开发框架及已经搭建完成。

设备搜索原理及编程技巧:

搜索发现的基本原理是:设备上服务器监听239.255.255.250的3702端口。所以,如果要实现跨网段搜索onvif设备需要路由的支持。只要组播数据包能收到,设备就能被搜到。原理是这样。参考代码:

struct soap* NewSoap(struct SOAP_ENV__Header *header,struct soap* soap,
		wsdd__ProbeType *req_,
		wsdd__ScopesType *sScope_)
{
	soap = soap_new();
	if(NULL == soap )
	{
		printf("sopa new error\r\n");
		return NULL;
	}

	soap->recv_timeout = 5;
	soap_set_namespaces(soap, namespaces);

	soap_default_SOAP_ENV__Header(soap, header);

	uuid_t uuid;
	char guid_string[100];
	uuid_generate(uuid);
	uuid_unparse(uuid, guid_string);

	header->wsa__MessageID = guid_string;
	header->wsa__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
	header->wsa__Action = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
	soap->header = header;

	soap_default_wsdd__ScopesType(soap, sScope_);
	sScope_->__item = "";
	soap_default_wsdd__ProbeType(soap, req_);
	req_->Scopes = sScope_;
	req_->Types = ""; //"dn:NetworkVideoTransmitter";

	return soap ;
}
int i = 0;
	result = soap_send___wsdd__Probe(soap, MULTICAST_ADDRESS, NULL, &req);

	while(result == SOAP_OK)
	{
		result = soap_recv___wsdd__ProbeMatches(soap, &resp);
		if(result == SOAP_OK)
		{
			if(soap->error)
			{
				printf("soap error 1: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
				result = soap->error;
			}
			else
			{
				printf("Onvif Device detected *********************************************\r\n");
				for(i = 0; i < resp.wsdd__ProbeMatches->__sizeProbeMatch; i++)
				{
					printf("__sizeProbeMatch        : %d\r\n", resp.wsdd__ProbeMatches->__sizeProbeMatch);
					printf("wsa__EndpointReference       : %p\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference);
					printf("Target EP Address       : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
					printf("Target Type             : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
					printf("Target Service Address  : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
					printf("Target Metadata Version : %d\r\n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
					if(resp.wsdd__ProbeMatches->ProbeMatch->Scopes)
					{
						printf("Target Scopes Address   : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);
					}
				}
				break;
			}
		}
		else if (soap->error)
		{
			printf("[%d] soap error 2: %d, %s, %s\n", __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
			result = soap->error;
		}
	}

注:搜索到的设备可以加入到自己的设备管理中,这里就不做过多的说明了。

设备鉴权:

鉴权的实现可以很简单也可以很难,这里笔者使用的是gsoap提供的方法:直接调用即可:

soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD);

原理也很容易明白其实,就是讲http的soap消息加入对应header中xml的元素而已,然后敏感消息digest MD5加密编码。

所以编译过程中需要使用 lcrypto 也就很正常了。

获取能力:

soap 的http消息通信,参考代码:

void UserGetCapabilities(struct soap *soap	,struct __wsdd__ProbeMatches *resp,
		struct _tds__GetCapabilities *capa_req,struct _tds__GetCapabilitiesResponse *capa_resp)
{
    capa_req->Category = (enum tt__CapabilityCategory *)soap_malloc(soap, sizeof(int));
    capa_req->__sizeCategory = 1;
    *(capa_req->Category) = (enum tt__CapabilityCategory)(tt__CapabilityCategory__Media);

    capa_resp->Capabilities = (struct tt__Capabilities*)soap_malloc(soap,sizeof(struct tt__Capabilities)) ;

	soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD);
	printf("\n--------------------Now Gettting Capabilities NOW --------------------\n\n");

    int result = soap_call___tds__GetCapabilities(soap, resp->wsdd__ProbeMatches->ProbeMatch->XAddrs, NULL, capa_req, capa_resp);

	if (soap->error)
    {
            printf("[%s][%d]--->>> soap error: %d, %s, %s\n", __func__, __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
            int retval = soap->error;
            exit(-1) ;
    }
    else
    {
    	printf(" \n--------------------GetCapabilities  OK! result=%d--------------\n \n",result);
        if(capa_resp->Capabilities==NULL)
        {
            printf(" GetCapabilities  failed!  result=%d \n",result);
        }
        else
        {

            printf(" Media->XAddr=%s \n", capa_resp->Capabilities->Media->XAddr);
        }
    }
}

获取媒体信息Profile:

soap 的http消息通信,参考代码:

void UserGetProfiles(struct soap *soap,struct _trt__GetProfiles *trt__GetProfiles,
		struct _trt__GetProfilesResponse *trt__GetProfilesResponse ,struct _tds__GetCapabilitiesResponse *capa_resp)
{
	int result=0 ;

	printf("\n-------------------Getting Onvif Devices Profiles--------------\n\n");
	soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD);
	result = soap_call___trt__GetProfiles(soap, capa_resp->Capabilities->Media->XAddr, NULL, trt__GetProfiles, trt__GetProfilesResponse);
	if (result==-1)
	//NOTE: it may be regular if result isn‘t SOAP_OK.Because some attributes aren‘t supported by server.
	//any question email [email protected]
	{
		printf("soap error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
		result = soap->error;
		exit(-1);
	}
	else{
		printf("\n-------------------Profiles Get OK--------------\n\n");
		if(trt__GetProfilesResponse->Profiles!=NULL)
		{
			if(trt__GetProfilesResponse->Profiles->Name!=NULL){
				printf("Profiles Name:%s  \n",trt__GetProfilesResponse->Profiles->Name);

			}
			if(trt__GetProfilesResponse->Profiles->token!=NULL){
				printf("Profiles Taken:%s\n",trt__GetProfilesResponse->Profiles->token);
			}
		}
		else{
			printf("Profiles Get inner Error\n");

		}
	}
	printf("Profiles Get Procedure over\n");

}

获取RTSP的URI:

soap 的http消息通信,参考代码:

void UserGetUri(struct soap *soap,struct _trt__GetStreamUri *trt__GetStreamUri,struct _trt__GetStreamUriResponse *trt__GetStreamUriResponse,
		 struct _trt__GetProfilesResponse *trt__GetProfilesResponse,struct _tds__GetCapabilitiesResponse *capa_resp)
{
	int result=0 ;
	trt__GetStreamUri->StreamSetup = (struct tt__StreamSetup*)soap_malloc(soap,sizeof(struct tt__StreamSetup));//初始化,分配空间
	trt__GetStreamUri->StreamSetup->Stream = 0;//stream type

	trt__GetStreamUri->StreamSetup->Transport = (struct tt__Transport *)soap_malloc(soap, sizeof(struct tt__Transport));//初始化,分配空间
	trt__GetStreamUri->StreamSetup->Transport->Protocol = 0;
	trt__GetStreamUri->StreamSetup->Transport->Tunnel = 0;
	trt__GetStreamUri->StreamSetup->__size = 1;
	trt__GetStreamUri->StreamSetup->__any = NULL;
	trt__GetStreamUri->StreamSetup->__anyAttribute =NULL;

	trt__GetStreamUri->ProfileToken = trt__GetProfilesResponse->Profiles->token ;

	printf("\n\n---------------Getting Uri----------------\n\n");

	soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD);
	soap_call___trt__GetStreamUri(soap, capa_resp->Capabilities->Media->XAddr, NULL, trt__GetStreamUri, trt__GetStreamUriResponse);

	if (soap->error) {
	printf("soap error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
	result = soap->error;

	}
	else{
		printf("!!!!NOTE: RTSP Addr Get Done is :%s \n",trt__GetStreamUriResponse->MediaUri->Uri);
	}
}

最后贴一个终端截图:

开发注意事项:(必读)

soap通信的命名空间如果错误则不能检索到设备:编译好的wsdd.nsmap文件需要修改命名空间,如下:

如果要正常开发,被检索到,或者发现其他设备需要nsmap修改如下:1.1换1.2

以下命名空间表示SOAP1.1版本:

{"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/*/soap-envelope", NULL},

{"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", "http://www.w3.org/*/soap-encoding", NULL}, //1.1

以下命名空间表示SOAP1.2版本:

{"SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope", "http://schemas.xmlsoap.org/soap/envelope/", NULL},

{"SOAP-ENC", "http://www.w3.org/2003/05/soap-encoding", "http://schemas.xmlsoap.org/soap/encoding/", NULL},  //1.2

另外存在的客户端搜索不到设备情况:

1.是否有vpn,存在的话,本机IP会产生变化导致不能搜到?抓包可以看到,3702端口包的数据源地址改变。

2.uuid是否已经赋值。

3.有时,windows宿主机装有虚拟机,也可能造成onvif客户端的ip获取错误。故搜索不到。

这些问题,在交换机或者路由支持本地局域网跨网段数据UDP交互时,均不会产生。

调试技巧:

fsend/ frecv 打印出发送和接收到的报文。使用xml编辑器分析。当然也可以直接用浏览器看。

1、打开onvif调试开关,以便让onvif打印一些可用的调试信息。

在Makefile中添加调试宏定义如: CC = gcc -DDEBUG

2、打开调试宏后,默认在程序运行的目录产生三个文件:

RECV.log

SENT.log

TEST.log

RECV.log是onvif接收到的SOAP数据,没接收一条,都会在RECV.log中记录

SENT.log是onvif发送出去的SOAP数据,没发送一套,也会在SENT.log中生成记录

最后是TEST.log,如果说RECV和SENT可以用wireshark工具抓包代替,那么TEST.log是谁也替代不了的,TEST.log记录了onvif的实时的工作状态。

尤其当出现segmentation fault错误,TEST.log就成了唯一一个能够定位到具体内存出错的地方了。

SOAP_TYPE返回soap->error=4的错误说明

关于数据正确(抓包可收到数据),但soap返回错误,为4 及 SOAP_TYPE 的问题:

GetCapabilities的过程错误时。

多次调试后得出结论,是tt__CapabilityCategory 的设置问题,有的设备不具备全部功能,而请求全部或请求没有的功能就可能造成这种问题,推荐写5(tt__CapabilityCategory__Media)  这是大多数设置有的能力,而且最常用。

GetProfile时错误:

其实数据在抓包过程中也能完全抓到,多次调试后,发现结构体需要的Name以及token关键字被赋值。其他的没有,说明本点返回与服务器的支持性有很大关系。及,开发过程中需要对应自己的需求,根据实际的需要和返回错误,读取返回结构体数据。

资源:

ONVIFDEVICEMANAGER下载地址:

http://pan.baidu.com/share/link?shareid=1967805400&uk=70662920&fid=3981296515

ONVIFTESTTOOL下载地址:

http://www.cr173.com/soft/66448.html

官网开发者向导资料下载地址:

http://www.onvif.org/Resources/WhitePapers.aspx

参考文章:

onvif规范的实现:使用gSOAP创建SOAP调用实例

http://blog.csdn.net/ghostyu/article/details/8162280

Onvif开发之服务端成功对接Rtsp视频流篇

http://blog.csdn.net/max_min_go/article/details/17964643

linux设备上的Onvif 实现10:获取支持通道的RTSP地址

http://gaohtao.blog.163.com/blog/static/58241823201381113214599/

Onvif开发之客户端鉴权获取参数篇

http://blog.csdn.net/max_min_go/article/details/17617057

ONVIF协议开发资源

http://www.csdn.net/tag/onvif%252520%2525E5%25258D%25258F%2525E8%2525AE%2525AE

onvif开发之设备发现功能的实现

http://blog.csdn.net/love_xjhu/article/details/11821037

Linux设备上的Onvif实现16:实现Onvif鉴权

http://blog.csdn.net/u012084827/article/details/19031969

Onvif开发之Linux下gsoap的使用及移植

http://blog.csdn.net/love_xjhu/article/details/9772361

onvif开发总结

http://blog.csdn.net/zsl461975543/article/details/8971143

代码框架生成之Onvif开发

http://www.yc-edu.org/C__peixun/6655.html

linux设备上的Onvif 实现4:成功编译gsoap 2.8.15

http://blog.csdn.net/u012084827/article/details/12202133

onvif规范的实现:onvif开发常用调试方法 和常见的segmentation fault错误

http://blog.csdn.net/ghostyu/article/details/8432760

linux设备上的Onvif 实现6:获取摄像头的流媒体地址完整流程

http://blog.csdn.net/u012084827/article/details/12201997

S?O?A?P? ?错?误?代?码?表

http://wenku.baidu.com/link?url=rujSmnpjBxjS3mGZrejoVVOShcPu_5Wu_9RKrQ6qWCB12xrZUvVoFkYRepLu0y6oTk6-bB5AnJ_7KxF6s8rXcb1BFko6DbBpXg0_7G0D7cu

linux设备上的Onvif 实现8:编写媒体信息获取程序

http://blog.csdn.net/u012084827/article/details/12201897

时间: 2024-11-29 02:22:20

ONVIF客户端搜索设备获取rtsp地址开发笔记(精华篇)的相关文章

ONVIFclient搜索设备获取rtsp地址开发笔记(精华篇)

概要: 前言及鸣谢: 感谢guog先生.快活林高先生,onvif全国交流群的的酷夏先生在开发过程中给予的巨大支持,没有你们的帮助开发过程将异常艰难啊.谢谢了! ONVIF介绍: ONVIF致力于通过全球性的开放接口标准来推进网络视频在安防市场的应用,这一接口标准将确保不同厂商生产的网络视频产品具有互通性.2008年11月.论坛正式公布了ONVIF第一版规范--ONVIF核心规范1.0.随着视频监控的网络化应用,产业链的分工将越来越细. 有些厂商专门做摄像头.有些厂商专门做DVS.有些厂商则可能专

iOS开发笔记 - 网络篇

计算机网络基础 ??计算机网络是多台独立自主的计算机互联而成的系统的总称,最初建立计算机网络的目的是实现信息传递和资源共享. ??如果说计算机是第二次世界大战的产物,那么计算机网络则是美苏冷战的产物.20世纪60年代初期,美国国防部领导的ARPA提出研究一种崭新的.能够适应现代战争的.生存性很强的通信系统并藉此来应对苏联核攻击的威胁,这个决定促使了分组交换网的诞生,也奠定今天计算机网络的原型,这是计算机网络发展史上第一个里程碑式的事件. ??第二个里程碑式的事件是20世纪80年代初,国际标准化组

Direct2D 1.1 开发笔记 特效篇(三) 简单的像素着色器特效

(转载请注明出处) 这次我们实现一个自定义的转变. 实现Direct2D 自定义转变Shader Models需要HLSL(High Level Shading Language)的实现. HLSL是Shader的一种实现,但是HLSL只能在D3D中使用,所以有点蛋疼. Shader被描述为显卡执行的小段程序,能够高效(并行)地执行. 没学过?没关系,笔者也没有,但是详细的不会在这里说明(你TM逗我(╯‵□′)╯︵┴─┴),请到官网中看看. D2D 特效能用 HLSL 的  4.0 及其以上版本

iOS开发笔记 - 语言篇之Swift

?2014年的苹果全球开发者大会(WWDC),当Craig Federighi向全世界宣布"We have new programming language"(我们有了新的编程语言)的时候,全场响起了最热烈和持久的掌声,伴随着掌声到来的语言叫Swift.接下来Craig Federighi更是毫不掩饰的告诉大家,Swift将成为主宰iOS和Mac开发的新语言,甚至是整个软件行业中最举足轻重的语言. ??Swift正如它的名字那样迅速.敏捷,但这并不是它的全部.Swift是一个博采众长的

Direct2D 1.1 开发笔记 特效篇(四) 图形调试

 (转载请注明出处) 如同上节所述,这节讲讲怎么调试图形. 很可惜,微软并没有对VS Express 2013 for Windows Desktop(下面简称WDExpress)添加图形调试功能. 对于付费版的VS(VS2013 pro对于学生与教员可以免费获取)则带有图形调试功能: 调试--图形--启用诊断 即可. 对于免费版,则有VS Express 2013 for Windows(下面简称VSWinExpress)带有图形调试功能. 幸运地,一个付费版.WDExpress与VSWi

iOS开发笔记 - 工具篇

??工欲善其事,必先利其器.作为一个合格的iOS开发者,熟练的使用各种开发或辅助开发的工具也算是一项必备技能吧,下面是我整理的一些工具及其应用场景和使用方法. 文档浏览工具 - Dash 网络抓包工具 Charles Wireshark 版本控制工具 Github SourceTree CornerStone SQLite数据库可视化工具 SQLiteBrowser Datum 界面调试工具 - Reveal 调试仪表 - Gauge 剖面仪器 - Instruments Allocations

Direct2D 1.1 开发笔记 特效篇(一) 使用D2D特效

(转载请注明出处) 问! 为什么使用D2D 1.1版本,而不是1.0版本? 答: D2D 特效 在Direct2D 1.1 中,提供了ID2D1Effect接口,让程序员使用硬件加速的实时特效.这几乎就是使用D2D 1.1的绝对理由. 其他什么 CommandList 什么的,完全不知道嘛╮( ̄▽ ̄)╭ 头文件就是d2d1effects.h啥的,不过还要请链接"dxguid.lib"静态库 使用ID2D1DeviceContext::CreateEffect创建D2D特效, 使用ID2

微社区项目开发笔记(前端篇)

这个微社区项目的前端主要用到了JQuery和AJAX技术.用到的插件有jquery.form(表单静态提交).jquery.qqFace(QQ表情).pickadate(日期选择).(在此,我对各个插件的开发者表示由衷的感谢!) 各个插件的使用方法就不详细介绍了,想要了解的可以在网络上搜索. 这里主要介绍一下图片上传的方法: 众所周知,html表单里面提供了上传文件的控件: <input type="file"/> 但是,在追求交互效果的今天,这样的表现方式显然是不好的.所

Direct2D 1.1 开发笔记 特效篇(二) 简单的自定义特效

(转载请注明出处) 这节就来一个简单的自定义特效作为概念的入门. 首先需要头文件 #include <d2d1effectauthor.h> #include <d2d1effecthelpers.h> 为了实现一个自定义的D2D特效,需要继承ID2D1EffectImpl并实现其接口. 好了,这里因为仅仅是介绍一下概念,所以这次的自定义特效就定为下阴影吧,微软也是这么干的. 实现就用现成的,因为特效的输入也能是特效. Transform: 暂时称为"转变"吧,