1. Filter概述

Filter是一个COM组件,由一个或多个Pin组成。Pin也是一个COM组件。Filter文件的扩展名为.ax,但也可以是.dll。Filter根据其包含Input pin或Output pin的情况(或在Filter Graph的位置),大致可分为三类:Source Filter(仅有Output pin)、Transform Filter(同时具有Input pin和Output pin)和Renderer Filter(仅有Input pin)。

一般情况下,创建Filter使用一个普通的Win32 DLL项目。而且,一般Filter项目不使用MFC。这时,应用程序通过CoCreateInstance函数Filter实例;Filter与应用程序在二进制级别的协作。另外一种方法,也可以在MFC的应用程序项目中创建Filter。这种情况下,Filter不需注册为COM组件,Filter与应用程序之间的协作是源代码级别的;创建Filter实例,不再使用CoCreateInstance函数,而是直接new出一个Filter对象,如下:

m_pFilterObject = new CFilterClass();

// make the initial refcount 1 to match COM creation

m_pFilterObject ->AddRef();

因为Filter的基类实现了对象的引用计数,所以即使在第二种情况下,对创建后的Filter对象的操作也完全可以遵循COM标准。

Filter是一个独立功能模块,最好不要将Filter依赖于其他第三方的DLL。因为Filter具有COM的位置透明性特点,Filter文件可以放在硬盘的任何位置,只要位置移动后重新注册。但此时,如果Filter依赖其他DLL,则Filter对该DLL的定位就会出现问题。

Filter不能脱离Filter Graph单独使用。所以,如果你想绕过Filter Graph直接使用Filter实现的模块功能,请将你的Filter移植成DMO(DirectX Media Object)。

2. Filter的注册

Filter是COM组件,所以在使用前一定要注册。Filter的注册程序为regsvr32.exe。如果带上命令行参数/u,表示注销;如果带上是/s,表示不弹出任何注册/注销成功与否的提示对话框。如果你想在Build Filter项目的时候进行自动注册,请在VC的Project settings的Custom Build页如下设置:

Description: Register filter

Commands: regsvr32 /s /c $(TargetPath)

echo regsvr32 exe.time > $(TargetDir)/$(TargetName).trg

Outputs: $(TargetDir)/$(TargetName).trg

Filter的注册信息包括两部分:基本的COM信息和Filter信息。注册信息都存放在注册表中。前者的位置为:HKEY_CLASSES_ROOT/CLSID/Filter Clsid/,后者的位置为:HKEY_CLASSES_ROOT/CLSID/Category/Instance/ Filter Clsid/。COM信息标示了Filter是一个标准的可以通过CoCreateInstance函数创建的COM组件,Filter信息标示了我们通过Graphedit看到的描述这个Filter的信息。如果你不想让Graphedit看到(或者让Filter枚举器找到)你写的Filter,你完全可以不注册Filter信息。而且不用担心,你这么做也完全不会影响Filter的功能。

屏蔽注册Filter信息的方法也很简单。因为CBaseFilter实现了IAMovieSetup接口的两个函数:Register和Unregister。我们只需重载这两个函数,直接return S_OK就行了。(注意:IAMovieSetup是用以注册Filter信息部分的接口,但已经废弃,仅在AMovieDllRegisterServer和AMovieDllUnregisterServer调用才会用到。新写的Filter注册函数一般使用AMovieDllRegisterServer2,这个函数不使用IAMovieSetup接口。如果想要不注册Filter信息,最好自己实现Filter的两个导出函数:DllRegisterServer和DllUnregisterServer,其中只使用RegisterAllServers函数注册Ole Server。)

Filter的Merit值。这个值是微软的“智能连接”函数使用的。在Graphedit中,当我们加入一个Source Filter后,在它的pin上执行“Render”,会自动连上一些Filter。Merit的值参考如下:

MERIT_PREFERRED = 0x800000,

MERIT_NORMAL = 0x600000,

MERIT_UNLIKELY = 0x400000,

MERIT_DO_NOT_USE = 0x200000,

MERIT_SW_COMPRESSOR = 0x100000,

MERIT_HW_COMPRESSOR = 0x100050

Merit值只有大于MERIT_DO_NOT_USE的时候才有可能被“智能连接”使用;Merit的值越大,这个Filter的机会就越大。

3. Filter之间Pin的连接过程

Filter只有加入到Filter Graph中并且和其它Filter连接成完整的链路后,才会发挥作用。Filter之间的连接(也就是Pin之间的连接),实际上是连接双方的一个Media type的协商过程。连接的方向总是从Output pin指向Input pin。连接的大致过程为:如果调用连接函数时已经指定了完整的Media type,则用这个Media type进行连接,成功与否都结束连接过程;如果没有指定或不完全指定了Media type,则进入下面的枚举过程。枚举欲连接的Input pin上所有的Media type,逐一用这些Media type与Output pin进行连接(如果连接函数提供了不完全Media type,则要先将每个枚举出来的Media type与它进行匹配检查),如果Output pin也接受这种Media type,则Pin之间的连接宣告成功;如果所有Input pin上枚举的Media type,Output pin都不支持,则枚举Output pin上的所有Media type,并逐一用这些Media type与Input pin进行连接。如果Input pin接受其中的一种Media type,则Pin之间的连接到此也宣告成功;如果Output pin上的所有Media type,Input pin都不支持,则这两个Pin之间的连接过程宣告失败。

每个Pin都可以实现GetMediaType函数来提供该Pin上支持的所有Preferred Media type(但一般只在Output pin上实现,Input pin主要实现CheckMediaType看是否支持当前提供的Media type就行了)。连接过程中,Pin上枚举得到的所有Media type就是这里提供的。

在CBasePin类中有一个protected的成员变量m_bTryMyTypesFirst,默认值为false。在我们定制Filter的Output pin中改变这个变量的值为true,可以定制我们自己的连接过程(先枚举Output pin上的Media type)。

当Pin之间的连接成功后,各自的pin上都会调用CompleteConnect函数。我们可以在这里取得一些连接上的Media type的信息,以及进行一些计算等。在Output pin的CompleteConnect实现中,还有一个重要的任务,就是协商Filter Graph运行起来后Sample传输使用的内存配置情况。这同样是一个交互过程:首先要询问一下Input pin上的配置要求,如果Input pin提供内存管理器(Allocator),则优先使用Input pin上的内存管理器;否则,使用Output pin自己生成的内存管理器。我们一般都要实现DecideBufferSize来决定存放Sample的内存大小。注意:这个过程协商完成之后,实际的内存并没有分配,而要等到Output pin上的Active函数调用。

4. Filter Media type概述

Media type一般可以有两种表示:AM_MEDIA_TYPE和CMediaType。前者是一个Struct,后者是从这个Struct继承过来的类。

每个Media type有三部分组成:Major type、Subtype和Format type。这三个部分都使用GUID来唯一标示。Major type主要定性描述一种Media type,比如指定这是一个Video,或Audio或Stream等;Subtype进一步细化Media type,如果Video的话可以进一步指定是UYVY或YUY2或RGB24或RGB32等;Format type用一个Struct更进一步细化Media type。

如果Media type的三个部分都是指定了某个具体的GUID值,则称这个Media type是完全指定的;如果Media type的三个部分中有任何一个值是GUID_NULL,则称这个Media type 是不完全指定的。GUID_NULL具有通配符的作用。

常用的Major type:

MEDIATYPE_Video;

MEDIATYPE_Audio;

MEDIATYPE_AnalogVideo; // Analog capture

MEDIATYPE_AnalogAudio;

MEDIATYPE_Text;

MEDIATYPE_Midi;

MEDIATYPE_Stream;

MEDIATYPE_Interleaved; // DV camcorder

MEDIATYPE_MPEG1SystemStream;

MEDIATYPE_MPEG2_PACK;

MEDIATYPE_MPEG2_PES;

MEDIATYPE_DVD_ENCRYPTED_PACK;

MEDIATYPE_DVD_NAVIGATION;

常用的Subtype:

MEDIASUBTYPE_YUY2;

MEDIASUBTYPE_YVYU;

MEDIASUBTYPE_YUYV;

MEDIASUBTYPE_UYVY;

MEDIASUBTYPE_YVU9;

MEDIASUBTYPE_Y411;

MEDIASUBTYPE_RGB4;

MEDIASUBTYPE_RGB8;

MEDIASUBTYPE_RGB565;

MEDIASUBTYPE_RGB555;

MEDIASUBTYPE_RGB24;

MEDIASUBTYPE_RGB32;

MEDIASUBTYPE_ARGB32; // Contains alpha value

MEDIASUBTYPE_Overlay;

MEDIASUBTYPE_MPEG1Packet;

MEDIASUBTYPE_MPEG1Payload; // Video payload

MEDIASUBTYPE_MPEG1AudioPayload; // Audio payload

MEDIASUBTYPE_MPEG1System; // A/V payload

MEDIASUBTYPE_MPEG1VideoCD;

MEDIASUBTYPE_MPEG1Video;

MEDIASUBTYPE_MPEG1Audio;

MEDIASUBTYPE_Avi;

MEDIASUBTYPE_Asf;

MEDIASUBTYPE_QTMovie;

MEDIASUBTYPE_PCM;

MEDIASUBTYPE_WAVE;

MEDIASUBTYPE_dvsd; // DV

MEDIASUBTYPE_dvhd;

MEDIASUBTYPE_dvsl;

MEDIASUBTYPE_MPEG2_VIDEO;

MEDIASUBTYPE_MPEG2_PROGRAM;

MEDIASUBTYPE_MPEG2_TRANSPORT;

MEDIASUBTYPE_MPEG2_AUDIO;

MEDIASUBTYPE_DOLBY_AC3;

MEDIASUBTYPE_DVD_SUBPICTURE;

MEDIASUBTYPE_DVD_LPCM_AUDIO;

MEDIASUBTYPE_DVD_NAVIGATION_PCI;

MEDIASUBTYPE_DVD_NAVIGATION_DSI;

MEDIASUBTYPE_DVD_NAVIGATION_PROVIDER;

常用的Format type:

FORMAT_None

FORMAT_DvInfo DVINFO

FORMAT_MPEGVideo MPEG1VIDEOINFO

FORMAT_MPEG2Video MPEG2VIDEOINFO

FORMAT_VideoInfo VIDEOINFOHEADER

FORMAT_VideoInfo2 VIDEOINFOHEADER2

FORMAT_WaveFormatEx WAVEFORMATEX

5. Filter之间的数据传送

Filter之间的数据是通过Sample来传送的。Sample是一个COM组件,拥有自己的一段数据缓冲。Sample由Allocator统一管理。如下图所示:

Filter之间数据传送的方式有两种:Push模式和Pull模式。

所谓Push模式,即Source filter自己能够产生数据,并且一般在它的Output pin上有独立的子线程负责将数据发送出去,常见的情况如WDM模型的采集卡的Live Source Filter;而所谓Pull模式,即Source filter不具有把自己的数据送出去的能力,这种情况下,一般Source filter后紧跟着接一个Parser Filter或Splitter Filter,这种Filter一般在Input pin上有个独立的子线程,负责不断地从Source filter索取数据,然后经过处理后将数据传送下去,常见的情况如File source。Push模式下,Source filter是主动的;Pull模式下,Source filter是被动的。而事实上,如果将上图Pull模式中的Source filter和Splitter Filter看成另一个虚拟的Source filter,则后面的Filter之间的数据传送也与Push模式完全相同。

那么,数据到底是怎么通过连接着的Pin传送的呢?首先来看Push模式。在Source filter后面Filter的 Input pin上,一定实现了一个IMemInputPin接口,数据正是通过上一级Filter调用这个接口的Receive方法进行传送的。值得注意的是,数据从Output pin通过Receive方法调用传送到Input pin上,并没有进行内存拷贝,它只是一个相当于数据到达的“通知”。再看一下Pull模式。Pull模式下的Source filter的 Output pin上,一定实现了一个IAsyncReader接口;其后面的Splitter Filter,就是通过调用这个接口的Request方法或者SyncRead方法来获得数据。Splitter Filter然后像Push模式一样,调用下一级Filter的Input pin上的IMemInputPin接口Receive方法实现数据的往下传送。

一个DirectShow的应用程序,至少会有两条线程:主线程和Filter用于数据传送的子线程。既然是多线程,就不可避免会出现线程同步问题。Filter的状态改变都在主线程中完成,Filter的数据相关操作都在数据线程中调用。各线程一些主要函数调用参考如下:

Streaming thread(s): IMemInputPin::Receive, IMemInputPin::ReceiveMultiple, IPin::EndOfStream, IMemAllocator::GetBuffer.

Application thread: IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IMediaSeeking::SetPositions, IPin::BeginFlush, IPin::EndFlush.

Either: IPin::NewSegment.

这些函数切忌混合调用,否则会引起线程的死锁。另外值得注意的是,BeginFlush和EndFlush属于主线程调用,而不是数据线程调用。

6. Transform filter和Trans-in-place filter的区别

首先,这两种Filter是有共同点的,因为Trans-in-place filter本身就是从Transform filter中继承过来的。其次,我们要明白的是,Trans-in-place filter“尽力”使自己的Input pin和Output pin使用相同的Allocator,以免去一次Sample数据的memcpy。我们说“尽力”,就是说Trans-in-place filter也未必能够实现它的初衷。(如果Trans-in-place filter使用的Allocator是ReadOnly的,而Trans-in-place filter又要修改Sample的数据,则Trans-in-place filter的Input pin和Output pin将不得不使用不同的Allocator。)

Trans-in-place filter有一个protected的成员变量m_bModifiesData,默认值为true。如果你确信定制Trans-in-place filter不需要修改Sample数据,则将m_bModifiesData赋值为false,这样可以保证Input pin和Output pin使用相同的Allocator。

Trans-in-place filter的实现主要体现在以下三个函数:CTransInPlaceFilter::CompleteConnect、CTransInPlaceInputPin::GetAllocator和CTransInPlaceInputPin::NotifyAllocator。CompleteConnect中进行必要的重连(Reconnect),保证Trans-in-place filter的Input pin和Output pin使用相同的Media type。GetAllocator能够取得Trans-in-place filter下一级Filter的Input pin上的Allocator。NotifyAllocator“尽力”使Trans-in-place filter的Input pin和Output pin使用同一个Allocator。

7. IMediaSeeking的实现

IMediaSeeking的实现在Filter上,但应用程序应该从Filter Graph Manager上得到这个接口。在Filter级别,Filter Graph Manager首先从Renderer filter开始询问上一级Filter的Output pin是否支持IMediaSeeking接口。如果支持,则返回这个接口;如果不支持,则继续往上一级Filter询问,直到Source filter。一般在Source filter的Output pin上实现IMediaSeeking接口。(如果是File source,一般在Parser Filter或Splitter Filter实现这个接口。)对于Filter开发者来说,如果我们写的是Source filter,就要在Filter的Output pin上实现IMediaSeeking接口;如果写的是Transform filter,只需要在Output pin上将用户的接口请求往上传递给上一级Filter的Output pin;如果写的是Renderer Filter,需要在Filter上将用户的接口请求往上传递给上一级Filter的Output pin。

注意:为了保证Seek操作后Stream的同步性,如果实际实现IMediaSeeking接口的Filter有多个Output pin,一般仅有一个pin支持Seek操作。对于你定制的Transform filter,如果有多个Input pin,你需要自己决定当Output pin接收到IMediaSeeking接口请求时选择哪一条路径往上继续请求。

应用程序能够在任何时候(running, paused or stopped)对Filter graph执行Seek操作。但当Filter graph正在running的时候,Filter graph manager会先pause住,执行完Seek操作后,再重新run起来。

IMediaSeeking可以有如下几种Seek的时间格式:

TIME_FORMAT_FRAME Video frames.

TIME_FORMAT_SAMPLE Samples in the stream.

TIME_FORMAT_FIELD Interlaced video fields.

TIME_FORMAT_BYTE Byte offset within the stream.

TIME_FORMAT_MEDIA_TIME Reference time (100-nanosecond units).

但实现这个接口的Filter未必支持所有的这些格式。一般Filter都会支持TIME_FORMAT_MEDIA_ TIME,当使用其它的格式时,最好调用IMediaSeeking::IsFormatSupported进行一下确认。

对于Filter,不赞成使用IMediaPosition接口。IMediaPosition是用以支持Automation的(比如VB里面使用DirectShow),IMediaSeeking不支持Automation。

8. Filter的状态转换

Filter有三种状态:stopped, paused, running。paused是一种中间状态,stopped状态到running状态必定经过paused状态。paused可以理解为数据就绪状态,是为了快速切换到running状态而设计的。在paused状态下,数据线程是启动的,但被Renderer filter阻塞了。

paused与running两者间的状态转换,对于Source filter和Transform filter可以忽略不计,而对于Renderer filter(特别是Video renderer / Audio renderer)情形稍有不同。Renderer首先处理那个paused状态下Hold的Sample,当接收到新的Sample时,判断Sample上的时间戳。如果时间未到,Renderer会Hold住这个Sample进行等待。

Filter graph manager以从下到上的顺序对Filter进行状态转换,即从Renderer filter一直回溯到Source filter。这个顺序能够有效地避免Sample的丢失以及Filter graph的死锁。

Stopped to paused:首先从Renderer开始进行paused状态的转换。这时,Filter调用自己所有Pin的Active函数进行初始化(一般Pin在Active中进行Sample内存的分配,如果是Source filter还将启动数据线程),使Filter处于一种就绪状态。Source filter是最后一个完成到就绪状态转换的Filter。然后,Source filter启动数据线程,往下发送Sample。当Renderer接收到第一个Sample后就阻塞住。当所有的Renderer实现了状态转换,Filter graph manager才认为状态转换完成。

Paused to stopped:当Filter进入stopped状态时,调用自己所有Pin的Inactive函数(一般Pin在Inactive中进行Sample内存的释放,如果是Source filter还将终止数据线程)。释放所有Hold的Sample,以使上一级Filter的GetBuffer脱离阻塞;终止所有在Receive中的等待,以使上一级Filter的Receive函数调用返回。Filter在stopped状态下拒绝接受任何Sample。这样从Renderer filter往上一级一级脱离阻塞,当到达Source filter的时候,可以确保数据线程终止。

9. EndOfStream问题

当Source filter的所有数据都已经发送出去,则会调用下一级Filter的Input pin上的IPin::EndOfStream,直到Renderer filter。当这个Renderer filter的所有Input pin都被调用了EndOfStream,则向Filter graph manager发送一个EC_COMPLETE事件。仅当Filter graph中的所有Stream都发送了EC_COMPLETE事件,Filter graph manager才会将这个事件发送给应用程序。

在我们定制的Filter中,如果接收到了上一级Filter传过来的EndOfStream,则说明上面的数据已经全部传送完毕,Receive方法不须再接收数据。如果我们对数据进行了缓冲,则应确认缓冲中的所有数据都被处理完并往下发送了,然后再往下调用EndOfStream。Pull模式下,一般是Splitter filter或Parser filter发送EndOfStream,而且方向是往下的,Source filter上不会收到这样的通知。

10. BeginFlush、EndFlush、NewSegment问题

典型的情况,当进行MediaSeeking之后,会调用BeginFlush、EndFlush。一般在Input pin上实现这两个函数。

对于Filter开发者来说,Filter在被调用BeginFlush时需要做以下工作:

· 调用下一级Filter的BeginFlush,使其不再接收新的Sample;

· 拒绝接收上一级Filter的数据,包括Receive调用和EndOfStream调用;

· 如果上一级Filter正在阻塞等待空的Sample,此时需要让它脱离阻塞(通过析构Allocator);

· 确保数据流线程脱离阻塞状态。

Filter在被调用EndFlush时需要做以下工作:

· 确保所有等待缓存的Sample被丢弃;

· 确保Filter上已经缓存的数据被丢弃;

· 清除没有发出去的EC_COMPLETE事件(如果这是一个Rendered input pin);

· 调用下一级Filter的EndFlush。

还有一点:如果你必须在定制的Filter中为每个Sample打Time stamp,那么记住在MediaSeeking之后出去的Sample的Time stamp应该从0开始重打。

Segment是一段时间内具有相同的Playback rate的一组Sample,以NewSegment函数调用来表示这个Segment的开始。NewSegment一般在开始新的Stream的时候,或者用户进行了MediaSeeking之后,由Source filter(Push模式下)或Parser/Splitter filter(Pull模式下)发起,并往下层层调用,一直到Renderer filter。

在我们定制的Filter中可以利用NewSegment传递下来的信息,特别是对于Decoder。对于Audio renderer也是一个典型例子,它根据Playback rate和Audio实际的采样频率来对声卡产生输出。

11. Quality Control问题

Filter之间的数据传送,有时候过快,有时候过慢。DirectShow使用Quality Control来解决这个问题,即IQualityControl接口的两个函数(SetSink和Notify)。一般,Renderer filter在Filter上实现这个接口,而其他Filter在Output pin上实现这个接口。

上图为一般的Quality Control的处理过程。而能够调整发送速度的IQualityControl接口一般在Source filter(pull模式下为parser/splitter filter)上实现,Transform filter只是将Quality Message往上一级Filter传递。

应用程序可以实现自己的Quality Control Manager,然后通过调用SetSink方法设置给Filter。上述的处理过程就改变了,Quality Message直接发送给自定义的Manager。

12. 对运行过程中Media type改变的支持

我们可以从CBaseInputPin::Receive中可以看到,Input pin每次在接收Sample的之前,一般都会进行CheckStreaming(如果当前Filter已经Stop或正在Flush或发生了RuntimeError,则拒绝接收Sample),然后将当前的Sample属性保存到protected的m_SampleProps成员变量中。描述Sample属性的是一个AM_SAMPLE2_PROPERTIES的结构,它有一个标记来表明当前Sample的Media type是否已经改变。如果Media type改变了,则进行CheckMedaiType看我们的Filter是否仍然支持它。如果不支持,则发出一个RuntimeError,并发送EndOfStream。

一个健全的Filter应该能够对运行时Media type的改变做出处理。在我们的Receive方法实现中,我们可以通过if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED)来判断Meida type是否已经改变;如果改变,我们需要根据新的Media type进行必要的初始化。

一个典型的案例:当Camcorder输入时,Audio的Media type可能改变。比如,Filter连接时Media type使用了MEDIATYPE_PCM,而在运行时又换成了MEDIATYPE_WAVE;或者连接时Audio的采样频率时44.1K,而在运行时却变成了48K;或者Camcorder的带子上本身保存了混合的44.1K和48K的Audio。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/welcome_ck/archive/2004/12/24/228155.aspx

深入浅出DirectShow Filter相关推荐

  1. 深入浅出DirectShow Filter(转)

    1.    Filter概述 Filter是一个COM组件,由一个或多个Pin组成.Pin也是一个COM组件.Filter文件的扩展名为.ax,但也可以是.dll.Filter根据其包含Input p ...

  2. 注册DirectShow filter时应该注意中文路径

    注册DirectShow filter时应该注意中文路径 2010-01-15 16:39:57|  分类: C++|字号 订阅 昨天封装了一个DirectShow Source Filter,使用R ...

  3. DirectShow Filter 开发典型例子分析 ——字幕叠加 (FilterTitleOverlay)1

    本文分析一下<DirectShow开发指南>中的一个典型的Transform Filter的例子:字幕叠加(FilterTitleOverlay).通过分析该例子,我们可以学习到Direc ...

  4. DirectShow Filter的开发实践

    一.介绍 摄像头图像采集处理在业界有着多种成熟的方案.从老的DirectShow.Grabber技术,到新的Windows Media Foundation框架,网络上都有着丰富的参考资料.OpenC ...

  5. DirectShow Filter 基础与简单的示例程序

    DirectShow 是一个 Windows 平台上的流媒体框架,提供了高质量的多媒体流采集和回放功能. Filter 实质是一个 COM 组件,所以学习开发 Filter 前你应该对 COM 相关知 ...

  6. direct show

     写这篇笔记,就当是对这个期间学习 DirectShow 的一个总结,假如它可以给你带来一些收 获的话,那我会感到更高兴.还有我遇到的一些问题,希望和大家一起讨论. 一.基本概念 1.Direct ...

  7. 关于opencv打开摄像头成灰色问题的原因

    问题描述: capture可以打开,但是无法抓取frame. 原因: 查找资料看到,可能是因为驱动的原因导致.windows 下的摄像头的都用了Directshow,而opencv 的这两个函数都需要 ...

  8. 最简单的基于DirectShow的示例:获取Filter信息

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

  9. 安装DirectShow的Filter一些总结

    我们的软件是使用DirectShow开发的,需要兼容各种相机设备. 一个用户是使用第三方购买的相机并配有SDK,他的SDK重装系统没有了.我给他发了一份SDK并准备给他安装. 总结下遇到的一些低级错误 ...

最新文章

  1. Sci-Hub十岁生日解封,超233万新论文被放出!总数达到近8800万
  2. csgo陀螺仪是什么意思_CSGO:起源2将在五月中旬推出?B5无意间透露出这个消息...
  3. [转载]sql(hive)中变量的使用
  4. JSON基础与数据解析、JSON方法、AJAX初识
  5. vue 实现无限轮播_Vue 实现无缝轮播
  6. 金融统计分析与挖掘实战3.1-3.2
  7. 11300001数据集配置错误_Power BI 中的数据刷新(二)
  8. 浅谈SQL语言的特点
  9. ffmpeg下载安装教程及介绍
  10. 制作u盘运行kali linux系统,U盘的进阶用法----打造U盘启动盘+USB KALI Linux+普通U盘。...
  11. 聚醚/聚丙烯酰胺-竣甲基/聚丙烯酰胺/粒状聚N-异丙基丙烯酰胺壳聚糖水凝胶的制备方法
  12. Android全屏和强制横屏竖屏设置
  13. Dining (匹配,最大流)
  14. c++中怎么求二维数组的长度
  15. python -不敢表白,不好意思说出来,没关系,7行代码完成自动打印文字
  16. 利用xls下载链接下载资源
  17. Android培训班
  18. 洛克希德马丁可安装在卡车的小型核聚变反应堆10年内诞生
  19. 约翰-聂夫的投资原则
  20. 程序员如何提高自己的编程水平?

热门文章

  1. 如何调用封装工具类调用网上接口查询工作日
  2. 您是否忘记了敏捷价值?
  3. TI电机控制库 SVGEN_DQ and FC_PWM_DRV问题的一些理解
  4. Nordic系列芯片讲解十(修改nordic系列芯片的广播名)
  5. MD5或者SHA加密为32位字符的具体算法,值得学习,标准JAVA MD5方法
  6. 基于Halcon卡尺测量: Metrology批量测量矩形和圆
  7. 全新企业ERP/MES/B2ME系统的构建理念和产品研发模式
  8. linux播放器切换,让mplayer成为linux下的万能播放器(更新)
  9. 小程序客服消息接口php,小程序客服接入
  10. QML Canvas-雷达