2005年06月06日
在2000和xp下,隐藏进程,VC6.0测试通过

头文件:

//////////////////////////////////////
//HideProcess.h
BOOL HideProcess();

 

CPP源文件:
/////////////////////////////////////////////////////////////////////////////
//HideProcess.cpp
#include<windows.h>
#include<Accctrl.h>
#include<Aclapi.h>

#include"HideProcess.h"

#define NT_SUCCESS(Status)((NTSTATUS)(Status) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)

typedef LONG NTSTATUS;

typedef struct _IO_STATUS_BLOCK
{
    NTSTATUS Status;
    ULONG Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

#define OBJ_INHERIT                0×00000002L
#define OBJ_PERMANENT            0×00000010L
#define OBJ_EXCLUSIVE            0×00000020L
#define OBJ_CASE_INSENSITIVE    0×00000040L
#define OBJ_OPENIF                0×00000080L
#define OBJ_OPENLINK            0×00000100L
#define OBJ_KERNEL_HANDLE        0×00000200L
#define OBJ_VALID_ATTRIBUTES    0×000003F2L

typedef struct _OBJECT_ATTRIBUTES
{
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef NTSTATUS (CALLBACK* ZWOPENSECTION)(
    OUT PHANDLE SectionHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes
    );

typedef VOID (CALLBACK* RTLINITUNICODESTRING)(
    IN OUT PUNICODE_STRING DestinationString,
    IN PCWSTR SourceString
    );

RTLINITUNICODESTRING RtlInitUnicodeString;
ZWOPENSECTION ZwOpenSection;
HMODULE g_hNtDLL = NULL;
PVOID g_pMapPhysicalMemory = NULL;
HANDLE g_hMPM = NULL;
OSVERSIONINFO g_osvi;
//—————————————————————————
BOOL InitNTDLL()
{
    g_hNtDLL = LoadLibrary("ntdll.dll");

    if (NULL == g_hNtDLL)
        return FALSE;

    RtlInitUnicodeString = (RTLINITUNICODESTRING)GetProcAddress( g_hNtDLL,

"RtlInitUnicodeString");
    ZwOpenSection = (ZWOPENSECTION)GetProcAddress( g_hNtDLL, "ZwOpenSection");

    return TRUE;
}
//—————————————————————————
VOID CloseNTDLL()
{
    if(NULL != g_hNtDLL)
        FreeLibrary(g_hNtDLL);

    g_hNtDLL = NULL;
}
//—————————————————————————
VOID SetPhyscialMemorySectionCanBeWrited(HANDLE hSection)
{
    PACL pDacl                    = NULL;
    PSECURITY_DESCRIPTOR pSD    = NULL;
    PACL pNewDacl = NULL;
   
    DWORD dwRes = GetSecurityInfo(hSection, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL,

NULL, &pDacl, NULL, &pSD);

    if(ERROR_SUCCESS != dwRes)
    {

    if(pSD)
        LocalFree(pSD);
    if(pNewDacl)
        LocalFree(pNewDacl);
    }

    EXPLICIT_ACCESS ea;
    RtlZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
    ea.grfAccessPermissions = SECTION_MAP_WRITE;
    ea.grfAccessMode = GRANT_ACCESS;
    ea.grfInheritance= NO_INHERITANCE;
    ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
    ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
    ea.Trustee.ptstrName = "CURRENT_USER";

    dwRes = SetEntriesInAcl(1,&ea,pDacl,&pNewDacl);
   
    if(ERROR_SUCCESS != dwRes)
    {

    if(pSD)
        LocalFree(pSD);
    if(pNewDacl)
        LocalFree(pNewDacl);
    }
    dwRes = SetSecurityInfo

(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL);
   
    if(ERROR_SUCCESS != dwRes)
    {

    if(pSD)
        LocalFree(pSD);
    if(pNewDacl)
        LocalFree(pNewDacl);
    }

}
//—————————————————————————
HANDLE OpenPhysicalMemory()
{
    NTSTATUS status;
    UNICODE_STRING physmemString;
    OBJECT_ATTRIBUTES attributes;
    ULONG PhyDirectory;

    g_osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx (&g_osvi);

    if (5 != g_osvi.dwMajorVersion)
        return NULL;

    switch(g_osvi.dwMinorVersion)
    {
        case 0:
            PhyDirectory = 0×30000;
            break; //2k
        case 1:
            PhyDirectory = 0×39000;
            break; //xp
        default:
            return NULL;
    }

    RtlInitUnicodeString(&physmemString, L"\\Device\\PhysicalMemory");

    attributes.Length                    = sizeof(OBJECT_ATTRIBUTES);
    attributes.RootDirectory            = NULL;
    attributes.ObjectName                = &physmemString;
    attributes.Attributes                = 0;
    attributes.SecurityDescriptor        = NULL;
    attributes.SecurityQualityOfService    = NULL;

    status = ZwOpenSection(&g_hMPM, SECTION_MAP_READ|SECTION_MAP_WRITE, &attributes);

    if(status == STATUS_ACCESS_DENIED)
    {
        status = ZwOpenSection(&g_hMPM, READ_CONTROL|WRITE_DAC, &attributes);
        SetPhyscialMemorySectionCanBeWrited(g_hMPM);
        CloseHandle(g_hMPM);
        status = ZwOpenSection(&g_hMPM, SECTION_MAP_READ|SECTION_MAP_WRITE, &attributes);
    }

    if(!NT_SUCCESS(status))
        return NULL;

    g_pMapPhysicalMemory = MapViewOfFile(g_hMPM, FILE_MAP_READ|FILE_MAP_WRITE, 0, PhyDirectory,

0×1000);

    if( g_pMapPhysicalMemory == NULL )
        return NULL;

    return g_hMPM;
}
//—————————————————————————
PVOID LinearToPhys(PULONG BaseAddress, PVOID addr)
{
    ULONG VAddr = (ULONG)addr,PGDE,PTE,PAddr;
    PGDE = BaseAddress[VAddr>>22];

    if (0 == (PGDE&1))
        return 0;

    ULONG tmp = PGDE & 0×00000080;

    if (0 != tmp)
    {
        PAddr = (PGDE & 0xFFC00000) + (VAddr & 0×003FFFFF);
    }
    else
    {
        PGDE = (ULONG)MapViewOfFile(g_hMPM, 4, 0, PGDE & 0xfffff000, 0×1000);
        PTE = ((PULONG)PGDE)[(VAddr&0x003FF000)>>12];
       
        if (0 == (PTE&1))
            return 0;

        PAddr=(PTE&0xFFFFF000)+(VAddr&0×00000FFF);
        UnmapViewOfFile((PVOID)PGDE);
    }

    return (PVOID)PAddr;
}
//—————————————————————————
ULONG GetData(PVOID addr)
{
    ULONG phys = (ULONG)LinearToPhys((PULONG)g_pMapPhysicalMemory, (PVOID)addr);
    PULONG tmp = (PULONG)MapViewOfFile(g_hMPM, FILE_MAP_READ|FILE_MAP_WRITE, 0, phys &

0xfffff000, 0×1000);
   
    if (0 == tmp)
        return 0;

    ULONG ret = tmp[(phys & 0xFFF)>>2];
    UnmapViewOfFile(tmp);

    return ret;
}
//—————————————————————————
BOOL SetData(PVOID addr,ULONG data)
{
    ULONG phys = (ULONG)LinearToPhys((PULONG)g_pMapPhysicalMemory, (PVOID)addr);
    PULONG tmp = (PULONG)MapViewOfFile(g_hMPM, FILE_MAP_WRITE, 0, phys & 0xfffff000, 0×1000);

    if (0 == tmp)
        return FALSE;

    tmp[(phys & 0xFFF)>>2] = data;
    UnmapViewOfFile(tmp);

    return TRUE;
}
//—————————————————————————
long __stdcall exeception(struct _EXCEPTION_POINTERS *tmp)
{
   ExitProcess(0);
   return 1 ;
}
//—————————————————————————
BOOL YHideProcess()
{
//    SetUnhandledExceptionFilter(exeception);

    if (FALSE == InitNTDLL())
        return FALSE;

    if (0 == OpenPhysicalMemory())
        return FALSE;

    ULONG thread  = GetData((PVOID)0xFFDFF124); //kteb
    ULONG process = GetData(PVOID(thread + 0×44)); //kpeb

    ULONG fw, bw;
    if (0 == g_osvi.dwMinorVersion)
    {
        fw = GetData(PVOID(process + 0xa0));
        bw = GetData(PVOID(process + 0xa4));       
    }

    if (1 == g_osvi.dwMinorVersion)
    {
        fw = GetData(PVOID(process + 0×88));
        bw = GetData(PVOID(process + 0×8c));
    }
       
    SetData(PVOID(fw + 4), bw);
    SetData(PVOID(bw), fw);

    CloseHandle(g_hMPM);
    CloseNTDLL();

    return TRUE;
}

BOOL HideProcess()
{
 static BOOL b_hide = false;
 if (!b_hide)
 {
  b_hide = true;
  YHideProcess();
  return true;
 }
 return true;
}

然后在需要隐藏进程的时候#incoude"HideProcess.h",调用HideProcess()即可。


学习VC++时经常会遇到链接错误LNK2001,该错误非常讨厌,因为对于
编程者来说,最好改的错误莫过于编译错误,而一般说来发生连接错误时,
编译都已通过。产生连接错误的原因非常多,尤其LNK2001错误,常常使人不
明其所以然。如果不深入地学习和理解VC++,要想改正连接错误LNK2001非
常困难。
  初学者在学习VC++的过讨校龅降腖NK2001错误的错误消息主要为:
  unresolved external symbol “symbol”(不确定的外部“符号”)。
  如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或
标签,将产生此错误消息。一般来说,发生错误的原因有两个:一是所引用
的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本
的连接库。
  以下是可能产生LNK2001错误的原因:
  一.由于编码错误导致的LNK2001。
  1.不相匹配的程序代码或模块定义(.DEF)文件能导致LNK2001。例如,
如果在C++ 源文件内声明了一变量“var1”,却试图在另一文件内以变量
“VAR1”访问该变量,将发生该错误。
  2.如果使用的内联函数是在.CPP文件内定义的,而不是在头文件内定
义将导致LNK2001错误。
  3.调用函数时如果所用的参数类型同函数声明时的类型不符将会产生
LNK2001。
  4.试图从基类的构造函数或析构函数中调用虚拟函数时将会导致LNK2001。
  5.要注意函数和变量的可公用性,只有全局变量、函数是可公用的。
  静态函数和静态变量具有相同的使用范围限制。当试图从文件外部访问
任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001。
  函数内声明的变量(局部变量) 只能在该函数的范围内使用。
  C++ 的全局常量只有静态连接性能。这不同于C,如果试图在C++的
多个文件内使用全局变量也会产生LNK2001错误。一种解决的方法是需要时在
头文件中加入该常量的初始化代码,并在.CPP文件中包含该头文件;另一种
方法是使用时给该变量赋以常数。
  二.由于编译和链接的设置而造成的LNK2001
  1.如果编译时使用的是/NOD(/NODEFAULTLIB)选项,程序所需要的运行
库和MFC库在连接时由编译器写入目标文件模块, 但除非在文件中明确包含
这些库名,否则这些库不会被链接进工程文件。在这种情况下使用/NOD将导
致错误LNK2001。
  2.如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC
时将得到“unresolved external on _WinMain@16”的LNK2001错误信息。
  3.使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内,
源文件中对“func”的引用,在目标文件里即对“__imp__func” 的引用。
如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行连接,将在__imp__func上发
生LNK2001;如果不使用/MD选项编译,在使用MSVCxx.LIB连接时也会发生LNK2001。
  4.使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001。
  5.当编译调试版的应用程序时,如果采用发行版模态库进行连接也会产
生LNK2001;同样,使用调试版模态库连接发行版应用程序时也会产生相同的
问题。
  6.不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可
能包含早先的版本没有的符号和说明。
  7.在不同的模块使用内联和非内联的编译选项能够导致LNK2001。如果
创建C++库时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头
文件里却关闭了函数内联(没有inline关键字),这时将得到该错误信息。
为避免该问题的发生,应该在相应的头文件中用inline关键字标志内联函数。
  8.不正确的/SUBSYSTEM或/ENTRY设置也能导致LNK2001。
  其实,产生LNK2001的原因还有很多,以上的原因只是一部分而已,对初
学者来说这些就够理解一阵子了。但是,分析错误原因的目的是为了避免错
误的发生。LNK2001错误虽然比较困难,但是只要注意到了上述问题,还是能
够避免和予以解决的。

1.使用COM库函数(最麻烦,要求对COM原理有深的理解)
2.使用MFC OLE的ClassWizard
3.使用#import指令(一般都用这种,利用了只能指针_com_ptr_t,它能自动执行COM的QueryInterface,AddRef和Release函数)
  不需要你在工程中对组件进行维护.
下面详细说明各种用法: 

      第一种方法:

  • 创建工程,并初始化COM库.在InitInstance的开始和返回前分别添加:

CoInitialize(NULL);

CoUninitialize();

  • 包含组件头文件 :#include "..\COMSample.h"    

#include"..\COMSample_i.c"

.h文件包含了接口的C++定义,并说明IID,CLSID的符号化常量   .c文件包含了IID,CLSID和LIBID的GUID定义

       第二种方法:

  • 创建工程,初始化COM. 在应用程序类的InitInstance中添加AfxOleInit();
  • 创建组件包装类:View->ClassWizard单击"AddClass",选择From a Type Library(来自类型库),在..\COMSample.tlb
  • 释放组件对象:ReleaseDispatch();

     第三种方法:

  • 创建工程,初始化COM,在InitInstance中调用AfxOleInit();
  • 利用#inport指令引入类型库.在stdafx.h中添加:#import "..\COMSample.tlb"\ rename_namespace("…")    using namespace …
  • 定义智能指针变量


2005年05月31日

最近开始学习PHP 因为以前主要是在Asp.Net下写WEB应用程序,刚开始接触PHP的mysql函数的时候,感觉有些不习惯.试着按照ADO.Net的一些形式对mysql函数做了一个简单的封装.
目前主要有三个类mysqlclient_connection / mysqlclient_command / mysqlclient_dataReader:

  • mysqlclient_connection 负责与mysql server建立连接
  • mysqlclient_command 为该类提供mysqlclient_connection和SQL Query后,执行mysql_query函数.
  • mysqlclient_dataReader 为该类提供mysql_query返回的结果集,读取结果集的数据和字段信息. 关于类的代码如下:
    	class mysqlclient_connection
    	{
    		private $status;
    		private $handle;
    		private $server;
    		private $database;
    		private $username;
    		private $password;
    		public function __construct($server,$database,$username,$password)
    		{
    			$status = "closed";
    			$handle = 0;
    			$thi-->server = $server;
    			$this->database = $database;
    			$this->username = $username;
    			$this->password = $password;
    		}
    
    		public function open()
    		{
    			$this->handle = mysql_connect($this->server,$this->username,$this->password) or die("connect mysql faild.");
    			if($this->handle != 0)
    			{
    				//echo "$this->database";
    				mysql_select_db($this->database,$this->handle) or ("database ".$database." is not exists or reject visited");
    			}
    			else
    			{
    				die("handle == 0");
    			}
    			$this->status = "open";
    			return true;
    		}
    
    		public function close()
    		{
    			if($this->status == "open")
    			{
    				mysql_close($this->handle) or die("falid close this connection");
    				$this->status = "closed";
    				return true;
    			}
    			else
    			{
    				die("this connection is closed");
    			}
    		}
    
    		public function getHandle()
    		{
    			return $this->handle;
    		}
    
    		public function __destruct()
    		{
    			if($this->status == "open")
    			{
    				mysql_close($this->handle) or die("faild close this connection");
    				$this->status = "closed";
    				//return true;
    			}
    		}
    	}
    
    	class mysqlclient_command
    	{
    		private $commandString;
    		private $activeConnection;
    
    		public function __construct($commandString)
    		{
    			$this->commandString = $commandString;
    		}
    
    		public function  setActiveConnection($connection)
    		{
    			$this->activeConnection = $connection;
    		}
    
    		public function executeDataReader()
    		{
    			if($this->activeConnection->getHandle() == 0)
    			{
    				die("this command does not have a active connection");
    			}
    			//mysql_select_db("mxb",$this->activeConnection->getHandle()) or die("selected faild again");
    			$result = mysql_query($this->commandString,$this->activeConnection->getHandle()) or die($this->commandString . $this->activeConnection->getHandle().mysql_error());
    			return $result;
    		}
    
    		public function __destruct()
    		{
    			//
    		}
    	}
    
    	class mysqlclient_dataReader
    	{
    		private $result;
    		private $rowsCount;
    		private $fieldsCount;
    		private static $currentPos;
    		public function __construct()
    		{
    			mysqlclient_dataReader::$currentPos = 0;
    		}
    
    		public function setResult($result)
    		{
    			$this->result = $result;
    			$this->rowsCount = mysql_num_rows($this->result);
    			$this->fieldsCount = mysql_num_fields($this->result);
    		}
    
    		public function nextRecord()
    		{
    			if(mysqlclient_dataReader::$currentPos < $this->rowsCount)
    			{
    				$tempRow = mysql_fetch_array($this->result);
    				mysqlclient_dataReader::$currentPos++;
    
    				return $tempRow;
    			}
    			else
    			{
    				return false;
    			}
    		}
    
    		public function getFieldsRow()
    		{
    			for($i=0;$i<$this->fieldsCount;$i++)
    			{
    				$field = mysql_fetch_field($this->result,$i);
    				$fieldsRow[] = $field;
    
    				return $fieldsRow;
    			}
    		}
    
    		public function __destruct()
    		{
    			//
    		}
    	}
    



    我自己写了一个测试类的简单的php页 列出指定表中的数据 code:

    			$connection = new mysqlclient_connection(SERVER,DATABASE,USERNAME,PASSWORD);
    			$command = new mysqlclient_command("select * from user");
    			//$comman-->activeConnection = $connection;
    			$command->setActiveConnection($connection);
    
    			$connection->open();
    			$result = $command->executeDataReader();
    			$reader = new mysqlclient_dataReader();
    			$reader->setResult($result);
    			while($row = $reader->nextRecord())
    			{
    				echo "
    
  • ".$row["id"]." : ".$row["name"]; } $connection->close(); ?>
  •  

    2005年5月25日 星期三 08时47分   晴

    VC利用boost库解析正则表达式
    最近做数据库涉及到解析sql语句,觉得最好的办法是写正则表达式解析,由于vc6没有解析函数,自己写又不甘心,后来从网上找到了boost库,解决了这个问题.
    boost下载地址:http://www.boost.org

    boost库安装比较麻烦,需要自己编译源文件,我整理了一下,如果仅仅需要做正则表达式,按下面的代码敲就行了.
    cmd
    vcvars32.bat
    cd D:\boost_1_32_0\libs\regex\build
    d:
    nmake -fvc6.mak
    nmake -fvc6.mak install

    注意,别看下载下来的数据包没有多大,解压缩之后达到了100多M,编译完之后为109M,占用131M,所以安装时一定注意空出足够的空间,敲入nmake -fvc6.mak后等待的时间比较长,屏幕上还会出现一大堆英语,可以不做考虑.按照步骤往下敲就行了.压缩包内文档很详细,参照文档继续就可以了.

    在VC6中集成:Tools->Options->Directories->Include files
    加入:D:\boost_1_32_0

    编写一个源程序测试一下:

    #include "stdafx.h"
    #include <cstdlib>
    #include <stdlib.h>
    #include <boost/regex.hpp>
    #include <string>
    #include <iostream>

    using namespace std;
    using namespace boost;

    regex expression("^select ([a-zA-Z]*) from ([a-zA-Z]*)");

    int main(int argc, char* argv[])
    {


            std::string in;
            cmatch what;
            cout << "enter test string" << endl;
            getline(cin,in);
            if(regex_match(in.c_str(), what, expression))
            {
                    for(int i=0;i<what.size();i++)
                            cout<<"str :"<<what[i].str()<<endl;
            }
            else
            {
                    cout<<"Error Input"<<endl;
            }
       return 0;
    }

    输入: select name from table
    输出: str:select name from table
             str:name
             str:table

     

    Directshow开发的基本技巧

     

    摘要:本篇文档主要讲述了Directshow开发的一些基本概念和技巧

     

     

    1视频播放(Video Rendering

     

    dshow的视频提交过滤器可以在窗口模式和无窗口模式下工作。在窗口模式下,过滤器创建一个自己的窗口,在里面播放视频。在无窗口模式下,过滤器直接将视频在应用程序提供的窗口上显示,过滤器本身不创建窗口。

    窗口模式

    在窗口模式下,视频提交过滤器创建一个窗口,然后将视频祯帖到窗口上,你可以将这个窗口帖到你的应用程序的窗口。

     Video Renderer只支持窗口模式,VMR-7 and VMR-9缺省的是窗口模式,也支持无窗口模式。

    为了在你的应用程序中显示视频,你可以将视频窗口设置成应用程序的子窗口。你可以通过

    IVideoWindow *pVidWin = NULL;

    pGraph->QueryInterface(IID_IVideoWindow, (void **)&g_pVidWin);

    pVidWin->put_Owner((OAHWND)hwnd);

    pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);

    RECT grc;

    GetClientRect(hwnd, &grc);

    pVidWin->SetWindowPosition(0, 0, grc.right, grc.bottom);

    结束时一定要清理现场

    pControl->Stop();
    
    
    
    pVidWin->put_Visible(OAFALSE);
    
    
    
    pVidWin->put_Owner(NULL);  
    
    
    
    
    
     
    
    

    无窗口模式

    当采用无窗口的模式时,就没有必要暴露IVideoWindow接口了。

    为了能够使用VMR的缺省行为,在构建Graph图之前必须要调整VMR

    1 创建一个过虑器图表管理器,

    2创建一个VMR,加入到graph中,

    3 调用VMRIVMRFilterConfig::SetRenderingMode方法设置VMRMode_Windowless标志。

    4调用IVMRWindowlessControl::SetVideoClippingWindow 给视频指定一个显示窗口。

    然后调用IGraphBuilder::RenderFile或者其他的方法来创建其他的Graph

    下面的代码显示了如何创建一个VMR,将其添加到Graph,如何设置无窗口模式

    HRESULT InitWindowlessVMR(

        HWND hwndApp,                  // Window to hold the video.

        IGraphBuilder* pGraph,         // Pointer to the Filter Graph Manager.

        IVMRWindowlessControl** ppWc,  // Receives a pointer to the VMR.   )

    {

        if (!pGraph || !ppWc) return E_POINTER;

        IBaseFilter* pVmr = NULL;

        IVMRWindowlessControl* pWc = NULL;

        // Create the VMR.

        HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL,

            CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr);

        if (FAILED(hr))

        {

            return hr;

        }

       

        // Add the VMR to the filter graph.

        hr = pGraph->AddFilter(pVmr, L"Video Mixing Renderer");

        if (FAILED(hr))

        {

            pVmr->Release();

            return hr;

        }

        // Set the rendering mode. 

        IVMRFilterConfig* pConfig;

        hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig);

        if (SUCCEEDED(hr))

        {

            hr = pConfig->SetRenderingMode(VMRMode_Windowless);

            pConfig->Release();

        }

        if (SUCCEEDED(hr))

        {

            // Set the window.

            hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc);

            if( SUCCEEDED(hr))

            {

                hr = pWc->SetVideoClippingWindow(hwndApp);

                if (SUCCEEDED(hr))

                {

                    *ppWc = pWc; // Return this as an AddRef’d pointer.

                }

                else

                {

                    // An error occurred, so release the interface.

                    pWc->Release();

                }

            }

        }

        pVmr->Release();

        return hr;

    }

    你也可以调用下面的函数

    IVMRWindowlessControl *pWc = NULL;

    hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc);

    if (SUCCEEDED(hr))

    {

        // Build the graph. For example:

        pGraph->RenderFile(wszMyFileName, 0);

        // Release the VMR interface when you are done.

        pWc->Release();

    }

    下面看看如何设置视频的位置

    有两个矩形需要考虑,一个是源矩形,一个是目的矩形。源矩形决定开始播放视频的位置,目的矩形决定在窗口显示视频的区域。VMR将源矩形按照目的矩形的大小进行扩展。

    IVMRWindowlessControl::SetVideoPosition可以设置两个矩形的大小,源矩形必须小于等于本地视频大小。你可以通过IVMRWindowlessControl::GetNativeVideoSize获取本地的视频区域大小。

    // Find the native video size.

    long lWidth, lHeight;

    HRESULT hr = g_pWc->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL);

    if (SUCCEEDED(hr))

    {

        RECT rcSrc, rcDest;

        // Set the source rectangle.

        SetRect(&rcSrc, 0, 0, lWidth/2, lHeight/2);

       

        // Get the window client area.

        GetClientRect(hwnd, &rcDest);

        // Set the destination rectangle.

        SetRect(&rcDest, 0, 0, rcDest.right/2, rcDest.bottom/2);

       

        // Set the video position.

        hr = g_pWc->SetVideoPosition(&rcSrc, &rcDest);

    }

    处理窗口消息

    因为VMR没有自己的窗口,所以当视频需要重画或者改变的时候你要通知它。

    1 当你接到一个WM_PAINT消息,你就要调用IVMRWindowlessControl::RepaintVideo来重画视频

    2 当你接到一个WM_DISPLAYCHANGE消息,你就要调用IVMRWindowlessControl::DisplayModeChanged.

    3 当你接到一个WM_SIZE消息时,重新计算视频的位置,然后调用SetVideoPostion

    下面的代码演示了WM_PAINT消息的处理

    void OnPaint(HWND hwnd)

    {

        PAINTSTRUCT ps;

        HDC         hdc;

        RECT        rcClient;

        GetClientRect(hwnd, &rcClient);

        hdc = BeginPaint(hwnd, &ps);

        if (g_pWc != NULL)

        {

            // Find the region where the application can paint by subtracting

            // the video destination rectangle from the client area.

            // (Assume that g_rcDest was calculated previously.)

            HRGN rgnClient = CreateRectRgnIndirect(&rcClient);

            HRGN rgnVideo  = CreateRectRgnIndirect(&g_rcDest); 

            CombineRgn(rgnClient, rgnClient, rgnVideo, RGN_DIFF); 

           

            // Paint on window.

            HBRUSH hbr = GetSysColorBrush(COLOR_BTNFACE);

            FillRgn(hdc, rgnClient, hbr);

     

     

            // Clean up.

            DeleteObject(hbr);

            DeleteObject(rgnClient);

            DeleteObject(rgnVideo);

     

     

            // Request the VMR to paint the video.

            HRESULT hr = g_pWc->RepaintVideo(hwnd, hdc); 

        }

        else  // There is no video, so paint the whole client area.

        {

            FillRect(hdc, &rc2, (HBRUSH)(COLOR_BTNFACE + 1));

        }

        EndPaint(hwnd, &ps);

    }

    尽管我们要自己处理onpaint消息,但是已经非常简单了。

    2 如何处理事件通知(Event Notification

       当一个Directshow的应用程序运行的时候,在 filter Graph内部就会发生各种各样的事件,例如,一个filter也许发生数据流错误。Filter通过给graph mangaer发送事件通知来和graph通信,这个事件通知包括一个事件码和两个事件参数。事件码表示发生事件的类型,两个参数用来传递信息。

    Filter发送的这些事件,其中的一部分可以被Manager直接处理,不通知应用程序,但有一部分事件,Manager将事件放入到一个队列中,等待应用程序处理。这里我们主要讨论在应用程序中经常遇到的三种事件

    EC_COMPLETE表明回放已经结束

    EC_USERABORT表明用户中断了回放。用户关闭视频播放窗口时,视频Render会发生这个事件

    EC_ERRORABORT表明出现了一个错误。

    应用程序可以通知filter graph manager,在某个指定的事件发生时,向指定的窗口发生一个指定的消息。这样应用程序就可以在消息循环中对发生的事件产生反应。

      首先定义消息,

    #define WM_GRAPHNOTIFY  WM_APP + 1

      然后向filter graph manager请求IMediaEventEx接口,然后调用IMediaEventEx::SetNotifyWindow方法来设置消息通知窗口

    IMediaEventEx *g_pEvent = NULL;

    g_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&g_pEvent);

    g_pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);

    然后在WindowProc函数增加一个处理WM_GRAPHNOTIFY消息的函数

    case WM_GRAPHNOTIFY:

        HandleGraphEvent();

        break;

    HandleGraphEvent()函数具体定义如下

    void HandleGraphEvent()

    {

        // Disregard if we don’t have an IMediaEventEx pointer.

        if (g_pEvent == NULL)

        {

            return;

        }

        // Get all the events

        long evCode;

        LONG_PTR param1, param2;

        HRESULT hr;

        while (SUCCEEDED(g_pEvent->GetEvent(&evCode, &param1, &param2, 0)))

        {

            g_pEvent->FreeEventParams(evCode, param1, param2);

            switch (evCode)

            {

            case EC_COMPLETE:  // Fall through.

            case EC_USERABORT: // Fall through.

            case EC_ERRORABORT:

                CleanUp();

                PostQuitMessage(0);

                return;

            }

        }

    }

    在释放IMediaEventEx指针前,要取消事件通知消息,代码如下

    // Disable event notification before releasing the graph.

    g_pEvent->SetNotifyWindow(NULL, 0, 0);

    g_pEvent->Release();

    g_pEvent = NULL;

     

     

     

     

    3如何枚举系统的设备和过虑器

      有时,应用程序需要查看系统中所有的filter。例如,视频应用程序需要列出系统中可用的捕捉设备。因为dshow基于com结构的,你在设计程序的时候是没法知道系统中正在使用的过滤器。Directshow提供了两种方法来枚举系统中注册的过虑器。

    1 系统设备枚举器

    系统设备枚举器提供了一个很好的方法根据种类来枚举系统中注册的过虑器。也许枚一种不同的硬件都会有自己的过虑器,或许所有的硬件设备共用同一个filter。这个对于采用WDM驱动程序的硬件很有用。

    系统设备枚举器根据不同的种类创建了一个枚举器,例如,音频压缩,视频捕捉。不同种类的枚举器对于每一种设备返回一个独立的名称(moniker)。种类枚举器自动将相关的即插即用,演播设备包括进来。

    按照下面的步骤使用设备枚举器

    1 创建枚举器组件,CLSIDCLSID_SystemDeviceEnum

    2 指定某一种类型设备,参数CLSID,通过ICreateDevEnum::CreateClassEnumerator获取某一种类的枚举器,这个函数返回一个IEnumMoniker接口指针,如果该种类的空或者不存在,这个方法就返回S_FALSE。因此,当你调用这个函数时一定要检查返回值是否为S_OK,而不要用SUCCEEDED宏。

    3 然后IEnumMoniker::Next枚举每一个moniker。这个方法返回一个IMoniker接口指针。

    4 要想知道设备的名称,可以通过下面的函数IMoniker::BindToStorage

    5 然后利用IMoniker::BindToObject生成绑定道设备上的filter。调用IFilterGraph::AddFilterfilter添加到Graph图中。


           1

    // Create the System Device Enumerator.

    HRESULT hr;

    ICreateDevEnum *pSysDevEnum = NULL;

    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,

        IID_ICreateDevEnum, (void **)&pSysDevEnum);

    if (FAILED(hr))

    {

        return hr;

    }

     

     

    // Obtain a class enumerator for the video compressor category.

    IEnumMoniker *pEnumCat = NULL;

    hr=pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat, 0);

     

     

    if (hr == S_OK)

    {

        // Enumerate the monikers.

        IMoniker *pMoniker = NULL;

        ULONG cFetched;

        while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)

        {

            IPropertyBag *pPropBag;

            hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,

                (void **)&pPropBag);//知道设备的名称

            if (SUCCEEDED(hr))

            {

                // To retrieve the filter’s friendly name, do the following:

                VARIANT varName;

                VariantInit(&varName);

                hr = pPropBag->Read(L"FriendlyName", &varName, 0);

                if (SUCCEEDED(hr))

                {

                    // Display the name in your UI somehow.

                }

                VariantClear(&varName);

     

     

                // To create an instance of the filter, do the following:

                IBaseFilter *pFilter;

                hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,

                    (void**)&pFilter); //生成一个filter绑定到设备上。

                // Now add the filter to the graph.

                //Remember to release pFilter later.

                pPropBag->Release();

            }

            pMoniker->Release();

        }

        pEnumCat->Release();

    }

    pSysDevEnum->Release();

    在上面我们IMoniker::BindToObject生成绑定道设备上的filter,当然我们还可以用另外的一种方法来生成绑定到设备上的filter

    利用IMoniker::GetDisplayName得到moniker的名字。然后你把moniker的名字做参数传递给IFilterGraph2::AddSourceFilterForMoniker,就可以创建一个绑定到设备的filter了。在上面我们是调用IMoniker::BindToObject生成filter的,还是上面的简单些。看看代码吧。

    LPOLESTR strName = NULL;

    IBaseFilter pSrc = NULL;

    hr = pMoniker->GetDisplayName(NULL, NULL, &strName);

    if (SUCCEEDED(hr))

    {

        // Query the Filter Graph Manager for IFilterGraph2.

        IFilterGraph2 *pFG2 = NULL;

        hr = pGraph->QueryInterface(IID_IFilterGraph2, (void**)&pFG2);

        if (SUCCEEDED(hr))

        {

            hr = pFG2->AddSourceFilterForMoniker(pMoniker, 0, L"Source", &pSrc);

            pFG2->Release();

        }

        CoTaskMemFree(strName);

    }

    // If successful, remember to release pSrc.

    2 Filter Mapper

      搜索系统中的filter的另一个方法就是采用Filer MapperFilter mapper是一个com对象,它按照一定的条件来搜索系统的filer,它比系统设备枚举器(System Device Enumerator)的效率要低一些。所以当你要枚举某特定种类的filter时,你应该使用系统设备枚举器,但是当你搜索支持某种媒体类型的filter时,同时也找不到清晰的filter,你应该使用filter mapper

    Filter Mapper 暴露一个IFilerMapper2接口,要想搜索一个接口,你可以调用该接口的IFilterMapper2::EnumMatchingFilters方法,这个方法需要传递一些参数来定义搜索条件,同时该方法返回一个适合条件的filter的枚举器,这个枚举器提供一个IEnumMoniker接口,并且对于每个适合的filter都提供一个单独的moniker

    下面的例子演示了,枚举所有的支持DV,并且至少有一个输出pinfilter,这个filter支持任何媒体类型。

    IFilterMapper2 *pMapper = NULL;

    IEnumMoniker *pEnum = NULL;

     

     

    hr =CoCreateInstance( CLSID_FilterMapper2,NULL, CLSCTX_INPROC, IID_IFilterMapper2,

                        (void **) &pMapper);

    if (FAILED(hr))

    {

        // Error handling omitted for clarity.

    }

     

     

    GUID arrayInTypes[2];

    arrayInTypes[0] = MEDIATYPE_Video;

    arrayInTypes[1] = MEDIASUBTYPE_dvsd;

     

     

    hr = pMapper->EnumMatchingFilters(

            &pEnum,

            0,                  // Reserved.

            TRUE,               // Use exact match?

            MERIT_DO_NOT_USE+1, // Minimum merit.

            TRUE,               // At least one input pin?

            1,                  // Number of major type/subtype pairs for input.

            arrayInTypes,       // Array of major type/subtype pairs for input.

            NULL,               // Input medium.

            NULL,               // Input pin category.

            FALSE,              // Must be a renderer?

            TRUE,               // At least one output pin?

            0,                  // Number of major type/subtype pairs for output.

            NULL,               // Array of major type/subtype pairs for output.

            NULL,               // Output medium.

            NULL);              // Output pin category.

     

     

    // Enumerate the monikers.

    IMoniker *pMoniker;

    ULONG cFetched; 

    //////////下面就是枚举filter了,就是系统枚举设备filter

    while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)

    {

        IPropertyBag *pPropBag = NULL;

        hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,

           (void **)&pPropBag);

     

     

        if (SUCCEEDED(hr))

        {

            // To retrieve the friendly name of the filter, do the following:

            VARIANT varName;

            VariantInit(&varName);

            hr = pPropBag->Read(L"FriendlyName", &varName, 0);

            if (SUCCEEDED(hr))

            {

                // Display the name in your UI somehow.

            }

            VariantClear(&varName);

     

     

            // To create an instance of the filter, do the following:

            IBaseFilter *pFilter;

            hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);

            // Now add the filter to the graph. Remember to release pFilter later.

       

            // Clean up.

            pPropBag->Release();

        }

        pMoniker->Release();

    }

    // Clean up.

    pMapper->Release();

    pEnum->Release();

    4如何枚举Graph图中的对象(filterpin

    有些时候,应用程序需要枚举graph中的filter或者是枚举filter所支持的pin。因此directshow提供了枚举graph filter中的com组件方法。

    1 枚举filter

    Filter图表管理器支持IFilterGraph::EnumFilters方法,来枚举graph图中的所有的filter。他返回一个IEnumFilters接口,利用这个接口就可以遍历graph中的所有的filter

    下面的代码演示了,如何遍历graph中的filter,并且显示filter的名字。

    HRESULT EnumFilters (IFilterGraph *pGraph)

    {

        IEnumFilters *pEnum = NULL;

        IBaseFilter *pFilter;

        ULONG cFetched;

        HRESULT hr = pGraph->EnumFilters(&pEnum);

        if (FAILED(hr)) return hr;

        while(pEnum->Next(1, &pFilter, &cFetched) == S_OK)

        {

            FILTER_INFO FilterInfo;

            hr = pFilter->QueryFilterInfo(&FilterInfo);

            if (FAILED(hr))

            {

                MessageBox(NULL, TEXT("Could not get the filter info"),

                    TEXT("Error"), MB_OK | MB_ICONERROR);

                continue;  // Maybe the next one will work.

            }

     

     

    #ifdef UNICODE

            MessageBox(NULL, FilterInfo.achName, TEXT("Filter Name"), MB_OK);

    #else

            char szName[MAX_FILTER_NAME];

            int cch = WideCharToMultiByte(CP_ACP, 0, FilterInfo.achName,

                MAX_FILTER_NAME, szName, MAX_FILTER_NAME, 0, 0);

            if (chh > 0)

                MessageBox(NULL, szName, TEXT("Filter Name"), MB_OK);

    #endif

            // The FILTER_INFO structure holds a pointer to the Filter Graph

            // Manager, with a reference count that must be released.

            if (FilterInfo.pGraph != NULL)

            {

                FilterInfo.pGraph->Release();

            }

            pFilter->Release();

        }

        pEnum->Release();

        return S_OK;

    }

    2 枚举pin

    Filter支持IBaseFilter::EnumPins方法,这个方法可以可以枚举filter所有的pin。它返回一个IEnumPins接口,IEnumPins::Next可以遍历pin的接口。

    下面的代码演示了如何如何查找一个输出和输入pin。利用PIN_DIRECTION参数来制定pin的类型(输入还是输出)。

    HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin)

    {

        IEnumPins  *pEnum = NULL;

        IPin       *pPin = NULL;

        HRESULT    hr;

     

     

        if (ppPin == NULL)

        {

            return E_POINTER;

        }

     

     

        hr = pFilter->EnumPins(&pEnum);

        if (FAILED(hr))

        {

            return hr;

        }

        while(pEnum->Next(1, &pPin, 0) == S_OK)

        {

            PIN_DIRECTION PinDirThis;

            hr = pPin->QueryDirection(&PinDirThis);

            if (FAILED(hr))

            {

                pPin->Release();

                pEnum->Release();

                return hr;

            }

            if (PinDir == PinDirThis) //如果类型符合

            {

                // Found a match. Return the IPin pointer to the caller.

                **ppPin = pPin;

                pEnum->Release();

                return S_OK;

            }

            // Release the pin for the next time through the loop.

            pPin->Release();

        }

        // No more pins. We did not find a match.

        pEnum->Release();

        return E_FAIL; 

    }

    利用这个方法可以很容易的就查找一个pin,然后调用IPin::ConnectedTo方法确定这个pin是否被连接,可以查找一个空闲的pin

    3 查找媒体类型

    每个pin都支持一个IPin::EnumMediaTypes方法,可以来枚举pin支持的媒体类型。它返回一个IEnumMediaTypes接口,这个接口的方法IEnumMediaTypes::Next返回一个指向AM_MEDIA_TYPE类型的指针。可以参考上面的代码来遍历pin所支持的媒体类型。

    5 Seeking Filter graph

    主要讲述了如何在一个媒体数据流中定位,任意指定开始播放的位置。

    1 检查是否支持seek

    Directshow通过IMediaSeeking接口支持seekingFilter graph管理器支持这个接口,但是实际seeking的功能是有graph中的filter来实现的。

    有一些数据是不能seek的,例如,你不可能seek从照相机中采集的活动的视频流。如果一个数据流可以被seek,但是,seek的类型还分以下几种类型,可以给你的数据流选择一种

    1 定位到数据流中的一个绝对位置

    2 返回数据流的持续时间

    3返回数据流中的当前播放位置

    4回放。

    IMediaSeeking接口定义了一套标志AM_SEEKING_SEEKING_CAPABILITIES,用来描述可能支持的seek功能。

    typedef enum AM_SEEKING_SeekingCapabilities {
    
    
    
        AM_SEEKING_CanSeekAbsolute        = 0x1,
    
    
    
        AM_SEEKING_CanSeekForwards        = 0x2,
    
    
    
        AM_SEEKING_CanSeekBackwards       = 0x4,
    
    
    
        AM_SEEKING_CanGetCurrentPos       = 0x8,
    
    
    
        AM_SEEKING_CanGetStopPos          = 0x10,
    
    
    
        AM_SEEKING_CanGetDuration         = 0x20,
    
    
    
        AM_SEEKING_CanPlayBackwards       = 0x40,
    
    
    
        AM_SEEKING_CanDoSegments          = 0x80,
    
    
    
        AM_SEEKING_Source                 = 0x100
    
    
    
    }   AM_SEEKING_SEEKING_CAPABILITIES;
    
    
    

     

     

    可以通过IMediaSeeking::GetCapabilities查看数据流支持的seek能力都有哪些。应用程序可以采取 &测试每一项。例如,下面的代码检查了graph是否可以seek 一个任意的位置

     DWORD dwCap = 0;

    HRESULT hr = pSeek->GetCapabilities(&dwCap);

    if (AM_SEEKING_CanSeekAbsolute & dwCap)

    {

        // Graph can seek to absolute positions.

    }

    2Setting and Retrieving the Position

     Filter graph包含两个位置,当前位置和停止位置,定义如下:

     1当前位置,当一个graph正处于运行的时候,当前位置就是当前的回放位置,相对于开始的位置而言。如果graph处于停止或者暂停状态的时候,当前位置就是数据流下次开始播放的位置点。

     2 停止位置,停止位置就是数据流将要停止的位置,当一个graph到达一个停止位置时,将没有数据流,filter graph管理器将会发送一个EC_COMPLETE事件。

     可以通过IMediaSeeking::GetPositions方法可以获取这些位置值。返回值都是相对于原始的开始位置。

     通过IMediaSeeking::SetPositions方法可以seek一个新的位置,见下面:

    #define ONE_SECOND 10000000

    REFERENCE_TIME rtNow  = 2 * ONE_SECOND,

                   rtStop = 5 * ONE_SECOND;

     

     

    hr = pSeek->SetPositions(

        &rtNow,  AM_SEEKING_AbsolutePositioning,

        &rtStop, AM_SEEKING_AbsolutePositioning

        );

    注:1秒是10,000,000参考时间单位。为了方便,这个例子将这个值定义为ONE_SECOND,如果你使用的dshow的基类,常量CUITS的值和这个值相等。

      RtNow参数指定新的当前位置,第二个参数用来标示如何来定位rtNow参数。在这个例子中,AM_SEEKING_AbsolutePositioning 标志表示rtNow指定的位置是一个绝对的位置。RtStop参数指定了停止时间,最后一个参数也指定了绝对位置。

      如果想指定一个相对的位置,可以指定一个AM_SEEKING_RelativePositioning参数,

    为了设置这个位置不能改变,可以指定一个AM_SEEKING_NoPositioning参数。此时,参考时间应该设置为NULL。下面的例子将位置向前seek 10秒,然后停止位置不变。

    REFERENCE_TIME rtNow = 10 * ONE_SECOND;
    
    
    

    hr = pSeek->SetPositions(

        &rtNow, AM_SEEKING_RelativePositioning,

        NULL, AM_SEEKING_NoPositioning

        );

     

     

    3Setting the Playback Rate

    调用IMediaSeeking::SetRate方法可以改变回放的速率。通过将新的速率设置成原来速率的倍数就可以设置新的速率,例如,pSeek->SetRate(2.0)
    
    
    

    将新的速率设置为原来速率的两倍。比率大于1说明回放的速度比原来的大,如果介于01之间,就比正常的速度慢。

     如果我们不考虑回放速率,当前位置和停止位置相对于开始位置都是不变的。举个例子,如果我们有一个可以播放20秒的文件,将当前时间设置为10秒就会将播放位置设置到中间,如果播放的速率提高要原来的2倍,如果停止时间是20秒,你将播放位置设置到原来的10秒处,结果现在只能播放5秒了,因为速度提高了两倍。

     

     

    4Time Formats For Seek Commands

      IMediaSeeking接口中的许多函数的参数都要求指定一个位置值,比如当前位置,或者停止位置,缺省的情况下这些参数是以of 100 nanoseconds为时间单位的,称为参考时间,任何支持seekfilter必须支持按参考时间来进行定位。一些filter也支持采取其他时间单位进行定位。例如,根据指定的桢的数量,或在数据流偏移的字节数进行定位。

      这种用来定位的时间单位称为时间格式,采用一个GUID来标示。Directshow定义了一系列的时间格式,详细地可以参考SDK。第三方也可以定义自己的时间格式。

      为了确定graph中的当前的filter是否支持特定的时间格式,可以调用

    IMediaSeeking::IsFormatSupported方法,如果filter支持该时间格式,该函数返回ok否则返回false或者一个错误码。如果filter支持某种指定的时间格式,可以调用IMediaSeeking::SetTimeFormat方法切换到其他的时间格式。如果SetTimeFormat方法成功,下面的seek命令就要使用新的时间格式。

      下面的代码检查graph是否支持用桢的数量进行定位,如果支持,定位到第20桢。

    hr = pSeek->IsFormatSupported(&TIME_FORMAT_FRAME);

    if (hr == S_OK)

    {

        hr = pSeek->SetTimeFormat(&TIME_FORMAT_FRAME);

        if (SUCCEEDED(hr))

        {

            // Seek to frame number 20.

            LONGLONG rtNow = 20;

            hr = pSeek->SetPositions(

                &rtNow, AM_SEEKING_AbsolutePositioning,

                0, AM_SEEKING_NoPositioning);

        }

    }

     

     

    6 如何设置Graph时钟(Setting Graph Clock

    当你构建了一个graph后,graph管理器会自动地给你的graph选择一个参考时钟的。Graph中的所有filter都同步于时钟。特别的,Renderer filter还要根据参考时钟的时间来决定每一个samplePresentation 时间。

      通常的情况下,应用程序是没有必要重新设置graph管理器选择好的参考时钟的。但是,如果你想修改参考时钟,你可以通过graph管理器提供的IMediaFilter::SetSyncSource方法来重新设置参考时钟。这个方法的参数是一个时钟的IReferenceClock接口指针。可以在graph停止的时候调用这个函数,下面的例子演示了如何指定一个时钟

    IGraphBuilder *pGraph = 0;

    IReferenceClock *pClock = 0;

     

     

    CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,

        IID_IGraphBuilder, (void **)&pGraph);

     

     

    // Build the graph.

    pGraph->RenderFile(L"C:\\Example.avi", 0);

     

     

    // Create your clock.

    hr = CreateMyPrivateClock(&pClock);

    if (SUCCEEDED(hr))

    {

        // Set the graph clock.

        IMediaFilter *pMediaFilter = 0;

        pGraph->QueryInterface(IID_IMediaFilter, (void**)&pMediaFilter);

        pMediaFilter->SetSyncSource(pClock);

        pClock->Release();

        pMediaFilter->Release();

    }

     这段代码假定CreateMyPrivateClock 是应用程序定义的一个函数,用来创建一个时钟,然后返回一个IReferenceClock接口。

      你也可以在graph没有设置时钟的情况下运行graph。当SetSyncSource 函数的参数为NULL的时候就给graph设置了一个空的参考时钟。如果graph没有时钟,graph将运行的快许多。因为renderer 不用再按照samplepresentation 时间了,只要sample到达了renderer filter,就可以立即被提交。所以,当你想处理数据尽可能快,而不是还要考虑预览的实际时间,你就可以给graph设置一个空的时间。

     

     

    摘要:这篇文章讨论了一个播放Filter需要处理的一些消息通知。只要正确的处理这些消息通知,才能够正确地设置Directshow播放视频的画面。

    1 开发一个可选择的视频播放filter
    Directshow提供了一个基于窗口的视频播放Filter,它也提供了一个全屏幕实时播放的filter。你可以利用Directshow的基类开发自己的可选择的视频播放filter。你可以利用CBaseRenderer and CBaseVideoRenderer类根据下面的一些经验指南就可以开发一个可选择的视频播放filter。
    在Directshow中主要有三种消息通知。
    1数据流的通知,
    这是数据流在graph图中传递的过程中,从一个filter到另一个filter的过程中发生的事件通知。例如,begin-flushing, end-flushing or end-of-stream事件通知,这些事件通知都是通过上游fitlter调用的下游filter的输入pin来通知下游filter的。比如IPin::BeginFlush。
    2 filter图表管理器发送的消息通知,
    这些都是filter给图表管理器发送的事件通知,比如EC_COMPLETE,这种消息一般都是通过图表管理器的IMediaEventSink::Notify发送或接收的。
    3 应用程序发送的消息通知
    应用程序通过调用图表管理器上的IMediaEvent::GetEvent方法就可以获得这些事件消息。一般来说,图表管理经常讲得到的消息传递给应用程序处理。
    2对End-of-stream and Flushing消息的处理
    当源filter发现没有数据传送的时候,它就会向下游发送一个end-of-stream通知,这个通知会沿着Graph图表中的filter一个一个的往下传递,最后到达Render Filter。这就导致Graph图表产生一个EC_COMPLETE消息。
    当Renderer的输入pin上的IPin::EndOfStream被上游的filter调用的时候,Render Filter就会接收到一个end-of-stream消息通知。Render Filter应该标记下这个消息通知,然后将已经接收到的数据处理完毕。当所有剩余的数据接收完毕,Render Filter就会给Graph图表管理器发送一个EC_COMPLETE消息。当Render Filter在处理完毕所有的数据时,你应该给graph图表管理器发送一次EC_COMPLETE消息。只有当Render Filter处于运行状态时才能给图表管理器发送EC_COMPLETE消息。如果一个Render 正处于paused状态时接收到源filter 发送的end-of-stream通知,只有当Filter Graph结束的时候Render 才能给图表管理器发送EC_COMPLETE消息。
    end-of-stream通知发出以后,如果上游filter再次调用Render Filter上的输入pin上的IMemInputPin::Receive or IMemInputPin::ReceiveMultiple方法时,Render就要拒绝它,此时就返回一个E_UNEXPECTED错误消息。
    当Filter 图表管理器停止的时候,Render应该将捕捉到的任何end-of-stream通知都应该被清除,当图表管理器再次启动的时候,Render不应该再给管理器发送任何通知。因为图表管理器在启动以前会Paused所有的Filter,这就会导致发生flushing。例如,如果Filter graph处于Pause状态收到一个end-of-stream通知,然后Filter Graph就停止了,当filter Graph再次运行的时候,Render 就不应该给管理器发送EC_COMPLETE消息了。如果没有发生seek,源filter会自动地在发生pause时给下游的filter发送一个end-of-stream通知,如果在filter Graph图表stop的时候发生seek,此时源filter也许正有数据要发送,所以它不会发送end-of-stream通知。
    Render filter经常依靠end-of-stream通知来发送EC_COMPLETE通知。例如,如果一个数据流已经结束发送(也就是end-of-stream消息已经发送出来),另一个窗口已经覆盖到视频窗口上,也产生了一系列的WM_PAINT消息。但是,当end-of-stream消息发出以后,Render就处于等待状态,但是Render也明白它不会再接收任何数据了,视频播放窗口就会出现黑屏幕。
    Flushing是Render应该处理的另外一种复杂的事件。Flushing消息是通过IPin上的两个方法BeginFlush and EndFlush.触发的。源filter在没有调用EndFlush,而仅仅调用了BeginFlush方法是不合法的,所以此时Flushing的状态是短暂和不连续的。但是在flushing状态下,Render要处理好数据以及接收的消息。
    在BeginFlush方法被调用之后所有接收到的数据都要立即被rejected,并且返回一个S_FASLE。并且捕捉到的end-of-stream也要立即清除。当Render接收到seek消息后,就立即处于flush状态。Flush确保在重新发送数据之前从filter Graph中清除所有的旧的数据。
    3如何处理状态的改变Handling State Changes and Pause Completion
    当一个Renderer filter的状态改变的时候,它的行为和其他的filter是一样的,但是也有以下的区别,当filter的状态变为pause的时候,Render filter 的数据就排成队列,等待下次播放,当一个video 播放filter 停止的时候,它也会保留这些队列中的数据,这就是一个例外,因为dshow规定,当一个graph停止的时候,它不应该保留任何资源。
    造成这种例外的原因是如果render filter保持资源,这样,当这个filter接收到一个WM_PAINT 消息时可以通过这个资源来重绘窗口。同样保持这个资源也可以满足一些方法的调用,比如CBaseControlVideo::GetStaticImage,,这个方法用来返回当前图像的一份拷贝。保持资源的另一个原因是,在资源保持的过程中,它所占用的内存块不会被回收,这样,再次开始数据传输时速度会比较快一点,因为不用分配内存了。
    在graph运行的期间,sample中的内容会被随时地提交sample内存也随时地被释放,但是,在停止运行的状态中,sample只能被提交,不能被释放,例如,在窗口绘制一幅静态的图画。音频流在停止的状态没法被提交,但是他们可以进行其他的动作,比如准备wave设备。Sample被提交的时间由sample的stream time和IMediaControl::Run方法调用时传递的参考时间综合以后得到的。当开始时间小于等于结束时间时,sample就应该被丢弃。
    当应用程序调用IMediaControl::Pause方法准备停止一个graph图时,只有当提交过滤器中有一个数据队列时才能够返回。为了确保此点,当一个render filter 没有等待提交的数据时,该方法就返回S_FALSE,如果有数据等待提交,返回S_OK。
    未来确保Render filter 有一个等待提交的数据,Filter图表管理器在停止一个graph时会检查方法的返回值,如果一个或者几个filter 还没有准备好,filter 图表管理器就会调用GetState方法来polls filter。GetState方法带有一个超时的参数,当GetState函数的等待的时间到期返回之前,如果filter还在等待数据的到来时,那么该函数返回VFW_S_STATE_INTERMEDIATE,如果filter已经有等到数据的时候,GetState返回s_ok。
    当一个filter在等待数据的时候,源filter会发送一个end of stream的通知,此时状态的转变完成。
    当一个graph中的所有的filter都有了等待提交的数据,那么整个graph就成为了pause状态。

    4如何处理终止态(Handling Termination)
    视频提交过滤器必须能够正确处理来自用户的终止数据流的事件。这就意味着要正确地隐藏窗口,并且知道当窗口重新显示的时候该怎么做。同时,当窗口销毁的时候,提交过滤器也要能够通知Filter 图表管理器来正确的释放资源。
    当用户关闭了视频窗口时,或者(用户按ALT+F4),一般的做法是将视频窗口隐藏同时给filter 图表管理器发送一个EC_USERABORT通知,这个通知最终会被发送到应用程序,然后应用程序就会停止播放视频。当发出EC_USERABORT通知后,所有发送给提交filter的数据都会被拒绝。
    当一个视频正在在播放的时候,如果用户此时按下ATL +F4,视频窗口就会暂时的隐藏起来,然后所有送往窗口的数据都会被拒绝。当窗口重新显示的时候,不会产生EC_REPAINT通知。
    当一个视频提交filter终止的时候,它要给filter图表管理器发送一个EC_WINDOW_DESTROYED消息通知。事实上,最好的处理这个消息的时机是在IBaseFilter::JoinFilterGraph方法调用时,而不是等到实际的窗口销毁时。Sending this notification enables the plug-in distributor in the Filter Graph Manager to pass on resources that depend on window focus to other filters (such as audio devices).

    5如何处理数据格式的动态改变
    视频提交filter一般只接受那些容易处理的数据格式,例如,一般只接受RGB格式的数据,因为这种数据格式和显示器格式相匹配。
    通常的话,上游的filter都是调用下游filter上的输入pin上的IPin::QueryAccept方法来查询,下游的filter是否接受新的数据格式,从而来动态的改变数据格式。一个render filter应该支持动态的修改数据格式,至少它应该允许上游的filter能够改变调色板。当上游filter改变媒体类型,它会在采用新格式的第一个smpale上贴上新的媒体类型。如果一个render filter还有一些老格式的数据没有提交完,它会等到这些老格式的数据提交完毕才改变媒体类型。
    解码器也会动态的改变数据格式,这样就要求render filter能够相应的跟着改变,例如,如果我们想要解码器提供一种和DirectDraw兼容的数据格式,当render paused的时候,它就开始通过QueryAccept向上游的filter 询问,解码器都支持什么数据格式,解码器一般不会将它支持的所有数据格式都列举出来,因此,render filter 就要提供一些解码器接口没有说明的数据格式。
    如果解码器可以接收要求的数据格式,它就会通过QueryAccept方法返回一个s_ok,于是Render filter就将新的媒体类型 attach to 上游内存分配器分配的下一个sample上。因此,render filter 就要提供一个内存分配器,这个提供一个私有的方法来将媒体类型贴到新的下一个samples上,在这个私有的方法中,调用IMediaSample::SetMediaType来设置媒体类型。
    Render filter 的输入pin应该在IMemInputPin::GetAllocator方法中返回render filter 的内存分配器,重载一下IMemInputPin::NotifyAllocator方法,如果上游的filter不使用render filter 提供的内存分配器,那么这个方法就会返回false。
    在一些解码器中,如果将biHeight设置为一个YUV类型的正数,那么就解码器就会产生上下颠倒的画面,这是不正确的,应该视为解码器的一个bug。
    当render filter 发现graph中的数据格式发生改变的时候,它都会发送一个EC_DISPLAY_CHANGED消息通知的。大多数的render filter 在连接的时候都会选择一个GDI支持的数据格式,如果用户改变了当前的显示模式而没有重新启动机器,那么render filter 发现自己正使用一种很糟糕的数据格式进行连接,那么它就会发送上面的消息通知。第一个参数就是需要重新连接的pin,管理器就会让graph图停止运行,重新连接pin。在随后的重新连接过程中,render filter 就会选择接受合适的数据格式。
    当render fitler发现调色板发生改变的时候,它要发送EC_PALETTE_CHANGED的消息通知给filter 图表管理器。
    最后,视频render filter 发现视频的尺寸发生改变,它会给图表管理器发送一个EC_VIDEO_SIZE_CHANGED消息通知。

    6如何处理永久性属性(Persistent Properties)
    所有通过IBasicVideo and IVideoWindow接口设置的属性都意味着在整个连接过程中是永久不不变的。因此,断开连接,重新连接都对窗口的大小,位置,样式等属性没有影响。但是,如果视频的尺寸大小发生改变的时候,render filter应该重新设置源或者目的的矩形大小。
    源或者目的的位置是通过IBasicVideo 设置的。
    IBasicVideo and IVideoWindow 提供了足够的接口方法可以让应用程序来一种永久的格式来保存或者存储经过接口的数据。
    7如何处理EC_REPAINT通知
    当一个render filter 暂停或者停止的时候,它发送一个EC_REPAINT消息。这个消息告诉filter 图表管理器render filter 需要数据。如果一个filter 管理器准备停止的时候接收到这个消息,它会首先暂停 filter graph,等到所有的filter 都接收到了数据(通过调用GetState),然后它再重新停止graph。当处于停止状态,一个render filter 应该保存一副图画,这样可以在处理WM_PAINT消息的时候来显示这幅图画。
    当一个render filter 在停止或者暂停的时候收到WM_PAINT 消息时,如果它没有任何的数据用来显示,它也会给 filter图表管理器发送一个EC_REPAINT消息。当处于暂停状态的filtr 图表管理器接收到一个EC_REPAINT消息时,图表管理器就会调用IMediaPosition::put_CurrentPosition方法,以当前的位置为参数,这个方法的调用就会导致源filter flush 图表管理器,然后通过图表管理器发送新的数据给render filter。
    Render filter一般只发送一次这样的消息通知,也就是说,如果一个render filter发送了一个ec_repaint消息,在数据到来之前它不应该再发送ec_repaint消息了,因此,通常的做法设置一个标志用来标示已经发送了一个ec_repaint消息,当render 接收到数据或者输入pin 被flushed以后,这个标志应该被重置。当然,如果输入pin接收到end-of-stream通知的时候,这个标志不重置。
    如果一个render filter 不控制它的EC_REPAINT消息的时候,那么整个filter图表管理器都会被EC_REPAINT消息淹没的。例如,如果一个render 没有图像显示的时候,如果一个窗口从render 窗口上拖动,那么render 窗口就会接收到大量的wm_paint消息的,只有第一个消息可以产生EC_REPAINT事件。
    Render filter 应该将它的输入pin作为 EC_REPAINT 消息的第一个参数,by doing this,rendfilter 首先向附加的输出pin(不知道这么说是否正确,原文the attached output pin will be queried for IMediaEventSink) 请求IMediaEventSink,接口,如果输出pin支持这个接口,那么Ec_REPAINT消息就首先通过这里发送出去。这样就使输出pin在graph收到消息前能够处理 repain事件。如果graph没有处于停止的状态,输出pin不会处理这个消息,因为没有空闲的内存数据块。
    如果输出pin不能够处理这个请求,或者graph正在运行,那么EC_REPAINT消息就会被丢弃。输出pin上的IMediaEventSink::Notify方法调用,返回S_OK表明输出pin可以正确的处理repaint消息。在graph的工作线程中会调用输出pin的,这样就避免了render直接调用输出pin,防止死锁。如果graph处于停止状态或者暂停状态,或者是输出pin不能处理这个请求,然后就会有缺省的处理过程来处理这个消息。
    8如何处理全屏幕显示
    IVideoWindow插件管理graph的全屏回放。它可以控制一个render filter 的窗口伸展成一个全屏来显示图像,或者直接用一个全屏的filter来直接回放。当一个filter从普通状态到全屏显示转化的过程都要发送EC_ACTIVATE消息,无论activated or deactivated.。也就是说,render filter 在接收到一个WM_ACTIVATEAPP一定要发送EC_ACTIVATE消息。
    当一个filter 处于全屏模式的时候,这些消息用来管理是进入还是退出全屏模式。
    当graph 接收到一个EC_ACTIVATE消息通知准备退出全屏模式,那么graph就发送一个EC_FULLSCREEN_LOST给应用程序,应用程序也许会利用这个消息来保存全屏按钮的状态。

    9消息通知小结Notifications
    1 EC_ACTIVATE
    说明;render filter 在接收到一个WM_ACTIVATEAPP一定要发送EC_ACTIVATE消息。
    2 EC_COMPLETE
    当所有的数据都提交完毕的时候,发送此消息
    3 EC_DISPLAY_CHANGED
    当显示的格式发生变化的时候,发送此消息
    4 EC_PALETTE_CHANGED
    当调色板发生变化的时候,发送此消息
    5 EC_REPAINT
    重画的时候,只发送一次
    6 EC_USERABORT
    当用户关闭的时候
    7 EC_VIDEO_SIZE_CHANGED
    当视频的尺寸发生变化的时候
    8 EC_WINDOW_DESTROYED
    当render filter 销毁的时候,发送此消息
    10Render中的源和目标矩形
    在VIDEOINFO, VIDEOINFOHEADER, and VIDEOINFOHEADER2三种媒体结构中有三个尺寸。
    这篇文档就是想解释一下这三个尺寸有何不同,以及他们是用来做什么的。
    首先,在这些结构中有一个bmiHeader数据成员,这个成员是BITMAPINFOHEADER结构,这个结构的定义如下
    typedef struct tagBITMAPINFOHEADER {
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
    } BITMAPINFOHEADER;
    这个结构有两个结构成员,biWidth和biHeight。
    第二,在这些结构中有一个rcSource数据成员,同时也有一个rcTarget成员,假如你有两个filter,A和B,假如这两个filter以某一种媒体数据类型 相连,A在左边,上游的filter,B在右边,下游的filter。
    在这两个filter间传递的buffer具有一定的尺寸,可以用bmiHeader.biWidth, bmiHeader.biHeight来标示。
    Filter A的输入视频流由rcSource 的大小控制,Filter应该将输入视频的一部分扩展填充到buffer中rcTarget区域中,填充的一部分的大小,是根据rcSource和数据媒体类型的大小比较结果而定的。也就是rcSource和(biWidth, biHeight) 比较。如果rcSource为空,Filter A就会将全部的输入pin拷贝到rcTarget,如果rcTarget为空,那么Filter A就会将视频填充到整个的输出buffer中。举例如下:
    假定Filter A接收的视频图像为160*120单位为象素,假定A和B连接的时候采用如下的数据类型
    (biWidth,biHeight):320, 240
    RcSource:(0,0,0,0)
    RcTarget(0,0,0,0)
    这就意味着Filter A就会将它接收到的视频数据x方向和y方向乘于2以后填充到320*240的输出bufer中。
    又假如Filter A接收的视频图像为160*120单位为象素,假定A和B连接的时候采用如下的数据类型
    (biWidth,biHeight):320, 240
    RcSource:(0,0,160,240)
    RcTarget(0,0,0,0)
    两个filter连接的buffer是320*240,因为指定的rcSource指定了buffer的左半部分,Filter A就将输入视频的左半部分或者(0,0,80,120)部分,然后将视频扩展到320*240(x方向*4,y方向*2)然后填充到320*240的输出buffer中。
    现在我们假定Filter A调用CBaseAllocator::GetBuffer,方法,这个方法的返回的sample会附着一个媒体类型,用来标示Filter B期望Filter A能够提供一个和前面的视频流具有不同size或者格式的数据。假定 新的媒体类型如下
    (biWidth, biHeight): 640, 480
    rcSource: (0, 0, 160, 120)
    rcTarget: (0, 0, 80, 60)
    这就意味着sample具有一个640*480的buffer,原来的rcSource适用于原来的(320, 240)的媒体类型不适用于新的媒体格式,因此,rcSource指定输入只使用左上角的四分之一,这一部分被放置到rcTarget的输出buffer的左上角(80,60),因为Filte A接收160*120的视频,输入视频的左上角正好是(80,60),和输出的位图一样大,不用扩展
    Filter A不会在输出buffer的其它部分放置数据,The rcSource member is bounded by the biWidth and biHeight of the original connected media type between filters A and B, and rcTarget is bounded by the new biWidth and biHeight of the media sample.上面的例子中,rcSource就在
    (0,0,320,240)范围内,rcTarget就在(0,0,640,480)范围内;


    作者简介:李强,目前暂时供职于山大联润信息科技有限公司,从事网络视频会议软件的开发,目前的感兴趣的方向,移动设备上多媒体的开发。aooang@hotmail.com 欢迎转载本文档

    摘要:
    我们一般不推荐自己开发音频或者视频捕捉过滤器,因为diectshow对于音视频的捕捉设备以经提供了支持。所以,这篇文档,对于某些用户需要从特定设备捕捉一些数据提供一些帮助。这篇文档主要包括以下内容。
    1捕捉filter 对pin的要求
    2如何完成一个预览pin
    3如何产生源数据

    1 对pin的要求Pin Requirements for Capture Filters
    Pin的名字
    你可以给你的filter起任何名字,如果你的pin的名字以~符号开头,那么当应用程序调用IGraphBuilder::RenderFile方法时,filter图表管理器不会自动render这个pin的。例如,如果一个filter具有一个捕捉pin和预览pin,相应的你给他们命名为”~Capture”和“Preview”。如果一个应用程序在graph中render这个filter ,那么预览pin就会自动和它缺省的render相连接,但是,capture pin上却不连接任何东西,这是一个合理的缺省行为。这个也可以应用到那些传输不准备被rendered数据的pin,也可以应用到需要属性设置的pin上。
    注:名字中含有~符号的pin是可以手动连接的。
    Pin的种类
    一个捕捉filter通常用一个捕捉pin,也许还有一个预览pin。一些捕捉filter除了这两种pin之外还有其他的pin,用来传递其他的数据,例如控制信息。每一个输出pin都必须暴露IKsPropertySet接口,应用程序通过这些接口来判断pin的种类,pin一般都会返回PIN_CATEGORY_CAPTURE or PIN_CATEGORY_PREVIEW。下面的例子演示了一个捕捉pin如何通过IKsPropertySet来返回pin的种类
    // Set: Cannot set any properties.
    HRESULT CMyCapturePin::Set(REFGUID guidPropSet, DWORD dwID,
    void *pInstanceData, DWORD cbInstanceData, void *pPropData,
    DWORD cbPropData)
    {
    return E_NOTIMPL;
    }

    // Get: Return the pin category (our only property).
    HRESULT CMyCapturePin::Get(
    REFGUID guidPropSet, // Which property set.
    DWORD dwPropID, // Which property in that set.
    void *pInstanceData, // Instance data (ignore).
    DWORD cbInstanceData, // Size of the instance data (ignore).
    void *pPropData, // Buffer to receive the property data.
    DWORD cbPropData, // Size of the buffer.
    DWORD *pcbReturned // Return the size of the property.
    )
    {
    if (guidPropSet != AMPROPSETID_Pin)
    return E_PROP_SET_UNSUPPORTED;
    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
    return E_PROP_ID_UNSUPPORTED;
    if (pPropData == NULL && pcbReturned == NULL)
    return E_POINTER;
    if (pcbReturned)
    *pcbReturned = sizeof(GUID);
    if (pPropData == NULL) // Caller just wants to know the size.
    return S_OK;
    if (cbPropData < sizeof(GUID)) // The buffer is too small.
    return E_UNEXPECTED;
    *(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
    return S_OK;
    }

    // QuerySupported: Query whether the pin supports the specified property.
    HRESULT CMyCapturePin::QuerySupported(REFGUID guidPropSet, DWORD dwPropID,
    DWORD *pTypeSupport)
    {
    if (guidPropSet != AMPROPSETID_Pin)
    return E_PROP_SET_UNSUPPORTED;
    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
    return E_PROP_ID_UNSUPPORTED;
    if (pTypeSupport)
    // We support getting this property, but not setting it.
    *pTypeSupport = KSPROPERTY_SUPPORT_GET;
    return S_OK;
    }

    2如何完成一个预览pin Implementing a Preview Pin (Optional)
    如果你的filter有一个预览pin,预览pin发送的数据是捕捉pin传递的数据的拷贝。预览pin发送的数据不会降低捕捉pin的桢率,捕捉pin比预览pin有优先权。
    捕捉pin和预览pin必须发送一个相同格式的数据。这样,他们连接都是通过同一种媒体数据类型,如果捕捉pin先连接,预览pin应该提供相同的媒体类型,对于其他类型的数据媒体,则拒绝。如果预览pin先连接,然后,如果捕捉pin以另一种媒体类型和其他pin连接,那么预览pin就应该用新的媒体类型重新连接,如果和filter的预览pin连接的下游filter拒绝新的数据类型,捕捉pin应该拒绝新的媒体类型。可以通过IPin::QueryAccept方法察看filter的预览pin连接的下游filter连接的数据媒体,然后通过IFilterGraph::Reconnect方法重新连接pin。
    这条规则也适用于图表管理器重新连接捕捉pin。
    下面的代码大体上描述了上面的过程
    // Override CBasePin::CheckMediaType.
    CCapturePin::CheckMediaType(CMediaType *pmt)
    {
    if (m_pMyPreviewPin->IsConnected())
    {
    // The preview pin is already connected, so query the pin it is
    // connected to. If the other pin rejects it, so do we.
    hr = m_pMyPreviewPin->GetConnected()->QueryAccept(pmt);
    if (hr != S_OK)
    {
    // The preview pin cannot reconnect with this media type.
    return E_INVALIDARG;
    }
    // The preview pin will reconnect when SetMediaType is called.
    }
    // Decide whether the capture pin accepts the format.
    BOOL fAcceptThisType = … // (Not shown.)
    return (fAcceptThisType? S_OK : E_FAIL);
    }

    // Override CBasePin::SetMediaType.
    CCapturePin::SetMediaType(CMediaType *pmt);
    {
    if (m_pMyPreviewPin->IsConnected())
    {
    // The preview pin is already connected, so it must reconnect.
    if (m_pMyPreviewPin->GetConnected()->QueryAccept(pmt) == S_OK)
    {
    // The downstream pin will accept the new type, so it’s safe
    // to reconnect.
    m_pFilter->m_pGraph->Reconnect(m_pMyPreviewPin);
    }
    else
    {
    return VFW_E_INVALIDMEDIATYPE;
    }
    }
    // Now do anything that the capture pin needs to set the type.
    hr = MyInternalSetMediaType(pmt);

    // And finally, call the base-class method.
    return CBasePin::SetMediaType(pmt);
    }

    CPreviewPin::CheckMediaType(CMediaType *pmt)
    {
    if (m_pMyCapturePin->IsConnected())
    {
    // The preview pin must connect with the same type.
    CMediaType cmt = m_pMyCapturePin->m_mt;
    return (*pmt == cmt ? S_OK : VFW_E_INVALIDMEDIATYPE);
    }
    // Decide whether the preview pin accepts the format. You can use your
    // knowledge of which types the capture pin will accept. Regardless,
    // when the capture pin connects, the preview pin will reconnect.
    return (fAcceptThisType? S_OK : E_FAIL);
    }

    3在源filter中产生数据 Producing Data in a Capture Filter
    状态改变
    一个捕捉filter在运行时会产生数据流。当filter paused的时候,不要发送数据,并且此时,图表管理器调用CBaseFilter::GetState方法会返回VFW_S_CANT_CUE,这个返回值告诉图表管理器,filter 正处于paused状态,停止发送数据。下面的代码显示如何派生GetState方法
    CMyVidcapFilter::GetState(DWORD dw, FILTER_STATE *pState)
    {
    CheckPointer(pState, E_POINTER);
    *pState = m_State;
    if (m_State == State_Paused)
    return VFW_S_CANT_CUE;
    else
    return S_OK;
    }
    控制不同的数据流
    一个捕捉filter应该支持IAMStreamControl接口,因此应用程序应该分别的打开和关闭每一个pin,例如,一个应用程序可以只预览而没有捕捉,然后可以转换到捕捉模式不用重新构建graph图表。你可以通过CBaseStreamControl类来实现这个接口
    时间戳
    当一个filter捕捉了一个sample,将在每一个sample上标上一个当前时间的时间戳。结束时间是开始时间加上持续时间。例如,如果一个filter每秒捕捉十个sample,and the stream time is 200,000,000 units when the filter captures the sample, the time stamps should be 200000000 and 201000000. (There are 10,000,000 units per second.) 可以通过IReferenceClock::GetTime方法获得当前的参考时间,通过IMediaSample::SetTime给sample设置时间戳。
    相连的sample上的时间戳必须是递增的,即使filter pauses也是这样,如果一个filter运行,停止,然后又运行,重新开始后产生的sample上的时间戳必须比停止前的sample上的时间戳要大一些。
    预览pin上的sample没有时间戳,但是,预览pin一般比捕捉pin到达render pin稍晚,因此,render filter将预览pin上的sample视作迟到的sample,为了赶上其他的时间进度,也许会丢失一些数据,

    作者简介:李强,目前暂时供职于山大联润信息科技有限公司,从事网络视频会议软件的开发,目前的感兴趣的方向,移动设备上多媒体的开发。aooang@hotmail.com 欢迎转载本文档,希望保留作者简介,以示对作者劳动的尊重。


    摘要:


    本篇文档我们将要讲述如何给一个filter创建一个属性页,通过CBasePropertyPage基类。这篇文档的实例代码演示了创建属性页的步骤,这里我们假设我们要创建属性页的视频filter支持饱和度属性页,这个属性页有一个滑动条,用户可以通过这个滑动条来控制饱和度。
    第一步,设置属性的机理
    Filter必须支持一种和属性页沟通的方式,通过属性页可以设置或者获取filter的属性,下面是可能的三种方式
    1暴露一个接口
    2通过IDispatch支持自动化属性
    3暴露IPropertyBag 接口,并定义一系列的属性
    下面的例子利用了一个普通的COM接口,叫做ISaturaton,这并不是一个真正的com接口,只是我们用来在这里举例的,你也可以自己定义任何的com对象。
    首先我们在一个头文件中声明接口的ID和定义。

    // Always create new GUIDs! Never copy a GUID from an example.
    DEFINE_GUID(IID_ISaturation, 0x19412d6e, 0x6401,
    0x475c, 0xb0, 0x48, 0x7a, 0xd2, 0x96, 0xe1, 0x6a, 0x19); interface ISaturation : public IUnknown
    {
    STDMETHOD(GetSaturation)(long *plSat) = 0;
    STDMETHOD(SetSaturation)(long lSat) = 0;
    };

    你也可以用IDL定义接口,并用MIDL编译器创建头文件,然后在Filter上实现这个接口,这个例子采用“Get”,“Set”方法来设置饱和度的值,注意,修改这个m_lSaturation的值的时候一定要进行保护

      class CGrayFilter : public ISaturation, /* Other inherited classes. */
    {
    private:
    CCritSec m_csShared; // Protects shared data.
    long m_lSaturation; // Saturation level.
    public:
    STDMETHODIMP GetSaturation(long *plSat)
    {
    if (!plSat) return E_POINTER;
    CAutoLock lock(&m_csShared);
    *plSat = m_lSaturation;
    return S_OK;
    }
    STDMETHODIMP SetSaturation(long lSat)
    {
    CAutoLock lock(&m_csShared);
    if (lSat < SATURATION_MIN || lSat > SATURATION_MAX)
    {
    return E_INVALIDARG;
    }
    m_lSaturation = lSat;
    return S_OK;
    }
    };

    当然你实现接口的一些细节可能和上面的代码不一致。反正你自己实现就是了

    第二步,实现ISpecifyPropertyPages接口

    做完了上一步,下面就要在你个filter中实现ISpecifyPropertyPages接口,这个接口只有一个方法,GetPages,这个方法返回filter所支持的所有的属性页的CLSID。在这个例子里,Filter只支持一个属性页,
    首先产生一个CLSID,并在头文件声明

    // Always create new GUIDs! Never copy a GUID from an example.
    DEFINE_GUID(CLSID_SaturationProp, 0xa9bd4eb, 0xded5,
    0x4df0, 0xba, 0xf6, 0x2c, 0xea, 0x23, 0xf5, 0x72, 0x61);

    然后要实现ISpecifyPropertyPages接口的GetPages方法:

    class CGrayFilter : public ISaturation,
    public ISpecifyPropertyPages,
    /* Other inherited classes. */
    {
    public:
    STDMETHODIMP GetPages(CAUUID *pPages)
    {
    if (pPages == NULL) return E_POINTER;
    pPages->cElems = 1;
    pPages->pElems = (GUID*)CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL)
    {
    return E_OUTOFMEMORY;
    }
    pPages->pElems[0] = CLSID_SaturationProp;
    return S_OK;
    }
    }; /* ... */ }

    第三步,支持QueryInterface
    为了暴露Filter的接口,照着下面的步骤作哦

    1 在你的filter中包含DECLARE_IUNKNOWN宏的声明:
    Public:
    DECLARE_IUNKNOWN;
    2 重载CUnknown::NonDelegatingQueryInterface 方法来检查两个接口的IIDs。
    STDMETHODIMP CGrayFilter::NonDelegatingQueryInterface(REFIID riid,
    void **ppv)
    {
    if (riid == IID_ISpecifyPropertyPages)
    {
    return GetInterface(static_cast<ISpecifyPropertyPages*>(this),
    ppv);
    }
    if (riid == IID_ISaturation)
    {
    return GetInterface(static_cast<IYuvGray*>(this), ppv);
    }
    return CBaseFilter::NonDelegatingQueryInterface(riid, ppv);
    }

    第四步,创建属性页
    到这一步,filter已经支持一个属性页的所需要的东西了,下一步就是要实现属性页本身了。
    首先创建一个对话框的资源,然后以这个对话的资源声明一个类,要从CBasePropertyPage. 派生,

    图1
    下面的代码显示了部分的声明,包含了我们在后面将要用到的部分变量。

    class CGrayProp : public CBasePropertyPage
    {
    private:
    ISaturation *m_pGray; // Pointer to the filter's custom interface.
    long m_lVal // Store the old value, so we can revert.
    long m_lNewVal; // New value.
    public:
    /* ... */
    };

    看看构造函数吧

      CGrayProp::CGrayProp(IUnknown *pUnk) : 
    CBasePropertyPage(NAME("GrayProp"), pUnk, IDD_PROPPAGE, IDS_PROPPAGE_TITLE),
    m_pGray(0)
    { }

    下面,你还要记得重载CBasePropertyPage 的几个方法哦
    OnConnect,当属性页创建的时候,会调用这个方法,通过这个方法将IUnknown指针付给Filter。
    OnActivate 当对话框创建的时候被调用
    OnReceiveMessage 当对话框接收到窗口消息时被调用
    OnApplyChanges当用户单击OK或者Apply 按钮来确认对属性进行更新时,调用
    OnDisconnect 当用户取消Property sheet时调用

    第五步,保存filter的一个指针
    通过重载CBasePropertyPage::OnConnect方法将一个指针保存到filter,下面的例子演示了如何通过方法传递过来的参数查询filter支持的接口

      HRESULT CGrayProp::OnConnect(IUnknown *pUnk)
    {
    if (pUnk == NULL)
    {
    return E_POINTER;
    }
    ASSERT(m_pGray == NULL);
    return pUnk->QueryInterface(IID_ISaturation,
    reinterpret_cast<void**>(&m_pGray));
    }

    第六步,初始化对话框
    通过重载CBasePropertyPage::OnActivate方法来初始化一个对话框,在这个例子里,属性页使用了滑动条,所以,在初始化的第一步就是要初始化控件动态库,然后再初始化slider。

    HRESULT CGrayProp::OnActivate(void)
    {
    INITCOMMONCONTROLSEX icc;
    icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icc.dwICC = ICC_BAR_CLASSES;
    if (InitCommonControlsEx(&icc) == FALSE)
    {
    return E_FAIL;
    } ASSERT(m_pGray != NULL);
    HRESULT hr = m_pGray->GetSaturation(&m_lVal);
    if (SUCCEEDED(hr))
    {
    SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETRANGE, 0,
    MAKELONG(SATURATION_MIN, SATURATION_MAX)); SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETTICFREQ,
    (SATURATION_MAX - SATURATION_MIN) / 10, 0); SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETPOS, 1, m_lVal);
    }
    return hr;
    }

    第七步,处理窗口消息
    重载CBasePropertyPage::OnReceiveMessage方法来处理用户的输入等消息。如果你不想处理消息,你只需简单调用父类的OnReceiveMessage 即可。
    无论何时用户改变了属性,都会做下面的事情
    1 将属性页的m_bDirty设置为TRUE;
    2调用属性框的IPropertyPageSite::OnStatusChange方法,并传递一个PROPPAGESTATUS_DIRTY,这个标志用来通知property frame应该将Apply按钮可用,
    CBasePropertyPage::m_pPageSite变量保存着一个IPropertyPageSite接口
    为了简化步骤,你可以在你的属性页中添加下面的代码

    private:
    void SetDirty()
    {
    m_bDirty = TRUE;
    if (m_pPageSite)
    {
    m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
    }
    }

    当用户改变了属性的时候,在OnReceiveMessage方法中调用上面的函数。

      BOOL CGrayProp::OnReceiveMessage(HWND hwnd,
    UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    switch (uMsg)
    {
    case WM_COMMAND:
    if (LOWORD(wParam) == IDC_DEFAULT)
    {
    // User clicked the 'Revert to Default' button.
    m_lNewVal = SATURATION_DEFAULT;
    m_pGray->SetSaturation(m_lNewVal); // Update the slider control.
    SendDlgItemMessage(m_Dlg, IDC_SLIDER1, TBM_SETPOS, 1,
    m_lNewVal);
    SetDirty();
    return (LRESULT) 1;
    }
    break; case WM_HSCROLL:
    {
    // User moved the slider.
    switch(LOWORD(wParam))
    {
    case TB_PAGEDOWN:
    case SB_THUMBTRACK:
    case TB_PAGEUP:
    m_lNewVal = SendDlgItemMessage(m_Dlg, IDC_SLIDER1,
    TBM_GETPOS, 0, 0);
    m_pGray->SetSaturation(m_lNewVal);
    SetDirty();
    }
    return (LRESULT) 1;
    }
    } // Switch.

    // Let the parent class handle the message.
    return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);
    }

    第八步,处理属性的改变
    重载CBasePropertyPage::OnApplyChanges方法来提交属性页的改变,如果用户单击了确定,或者应用按钮,OnApplyChanges方法都会调用到

      HRESULT CGrayProp::OnApplyChanges(void)
    {
    m_lVal = m_lNewVal;
    return S_OK;
    }

    第九步,断开属性页连接
    重载CBasePropertyPage::OnDisconnect方法来释放你在OnConnect方法中请求的所有的接口,如果用户没有更新属性,而是单击了取消按钮,你还要将属性的原始值保存下来。当用户单击取消按钮,但是没有相应的响应这个消息的方法,所以,你要检查用户是否调用了OnApplyChanges方法,看看例子也好:

    HRESULT CGrayProp::OnDisconnect(void)
    {
    if (m_pGray)
    {
    // If the user clicked OK, m_lVal holds the new value.
    // Otherwise, if the user clicked Cancel, m_lVal is the old value.
    m_pGray->SetSaturation(m_lVal);
    m_pGray->Release();
    m_pGray = NULL;
    }
    return S_OK;
    }

    第十步,支持com的注册
    最后一步就是要支持com的注册,因此 属性框才能够创建你属性页的实例,首先在全局数组g_Templates添加一个类厂模板的说明。这个全局的数组是你的DLL中创建的所有的com对象都要用到的。

    const AMOVIESETUP_FILTER FilterSetupData = 
    {
    /* Not shown ... */
    }; CFactoryTemplate g_Templates[] =
    {
    // This entry is for the filter.
    {
    wszName,
    &CLSID_GrayFilter,
    CGrayFilter::CreateInstance,
    NULL,
    &FilterSetupData
    },
    // This entry is for the property page.
    {
    L"Saturation Props",
    &CLSID_SaturationProp,
    CGrayProp::CreateInstance,
    NULL, NULL
    }
    };

    如果你用下面的方式声明全局数组,数组的大小就会自动地得到修改
    int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);
    同时,还要在属性页类中添加一个CreateInstance方法

      static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr) 
    {
    CGrayProp *pNewObject = new CGrayProp(pUnk);
    if (pNewObject == NULL)
    {
    *pHr = E_OUTOFMEMORY;
    }
    return pNewObject;
    }

    摘要:
    关于开发自己的Filter,我以前写过一篇文章,《利用Directshow开发自己的filter》,里面详细介绍了开发filter一些步骤,这里我想介绍一些filter的基础知识,可以让你更好的理解filter。本篇文档主要包括下面一些内容

    • 1filter的连接
      2filter间的数据流动
      3pin连接时数据格式的动态改变
      4Threads and Critical Sections
      5质量控制管理
      6Directshow和com

    1filter的连接
    Pin的连接
    应用程序通过调用filter 图表管理器的方法来连接filter,并不是来调用filter或者pin本身的函数。应用程序可以调用IFilterGraph::ConnectDirect or IGraphBuilder::Connect来指定不同的filter直接连接,也可以通过IGraphBuilder::RenderFile间接连接。
    只有两个filter都在graph里,连接才能成功。应用程序可以通过IFilterGraph::AddFilter将filter 添加graph中,当一个filter被添加到graph中时,filter图表管理器通过IBaseFilter::JoinFilterGraph来通知filter。
    Pin连接的大致过程如下:
    1图表管理器首先调用输出pin上的IPin::Connect,然后传递一个指针给输入pin。
    2如果输出pin接受连接的邀请,它就调用输入pin上的IPin::ReceiveConnection。
    3如果输入pin也接受连接邀请,那么连接成功,pin之间的连接ok。
    当filter处于活动状态的时候,许多pin可以断开连接和重新连接。这种类型的连接称为动态连接。当然,大多数的filter并不支持动态连接。
    Filter通常采用从上游到下游的连接顺序。也就是说filter上的输入pin总是比输出pin先连接。Filter应该支持这种连接顺序。然而有许多filter支持相反的连接顺序,输出pin先连接,输入pin后连接。例如:在连接MUX filter的输入pin之前一定要将MUX filter的输出pin和writer filter连接起来。
    当pin的Connect or ReceiveConnection方法被调用的时候,pin必须检查一下自己是否支持这个连接。通常要进行下列检查:
    1 检查媒体类型是否匹配。
    2 就内存的分配达成一致。
    3请求其他pin的其他接口。
    媒体类型匹配
    当一个filter 图表管理器调用IPin::Connect方法时,可能有下面的几种媒体类型。
    1 完整类型
    如果媒体类型每一个部分都定义的很完成,那么pin就严格按照定义的类型类型进行连接。如果不匹配,连接失败。
    2 部分媒体类型
    如果媒体类型的机构中,major type, subtype, or format type的值为GUID_NULL,这个值是一个通配符号。任何类型都可以匹配。
    3没有媒体类型
    如果filter图表管理器传递过来一个NULL的指针,这个pin就可以和任意的类型的媒体类型匹配。
    一般在连接过程中,都有一个完整的媒体类型。图表管理器传递媒体类型的目的是为了限制连接类型。
    一般来说,都是输出pin通过调用输入pin IPin::ReceiveConnection提供一个媒体类型。输入pin可以拒绝也可以接受这个媒体类型。这个过程一直重复,直到输入pin接受了一个类型,或者输出pin枚举完了它支持的所有的媒体类型,连接失败。
    输出pin通过调用输入pin上的IPin::EnumMediaTypes枚举输入pin所支持的媒体类型。
    看看如何匹配媒体类型的吧。
    if ((pmt->formattype == FORMAT_VideoInfo) &&
    (pmt->cbFormat > sizeof(VIDEOINFOHEADER) &&
    (pbFormat != NULL))
    {
    VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat;
    // Now you can dereference pVIH.
    }
    Pin连接中的内存分配
    当两个pin连接起来后,他们需要一种机制来交换媒体数据。大多数数据交换采用的局部内存交换机制。所有的媒体数据都在主内存中。DirectShow为局部存储器传输定义了两种机制:推模式(push model)和拉模式(pull model)。在推模式中,源过滤器生成数据并提交给下一级过滤器。下一级过滤器被动的接收数据,完成处理后再传送给再下一级过滤器。在拉模式中,源过滤器与一个分析过滤器相连。分析过滤器向源过滤器请求数据后,源过滤器才传送数据以响应请求。推模式使用的是IMemInputPin接口,拉模式使用IAsyncReader接口,推模式比拉模式要更常用。
    在局部存储器传输中,负责分配内存的对象称为allocator。每个allocator都支持一个IMemAllocator接口。所有的pin都共享一个allocator。
    每个pin都提供一个allocator,但是输出pin选择使用哪个allocator。
    输出pin可以设置allocator的属性。比如,分配内存的大小,
    在IMemInputPin连接中,allocator工作过程如下
    1 首先,输出pin调用IMemInputPin::GetAllocatorRequirements,这个方法检查输入pin对内存的要求,比如内存的队列,一般来说,输出pin要满足输入pin对内存的要求。
    2 输出pin然后调用IMemInputPin::GetAllocator.,这个方法从输入pin请求一个allocator,
    3 输出pin选择一个allocator,可以是输入pin提供,也可以是自己生产的。
    4输出pin调用IMemAllocator::SetProperties来设置allocator的属性。
    5然后输出pin通过IMemInputPin::NotifyAllocator来通知输入pin,选择的allocator。
    6输入pin通过IMemAllocator::GetProperties来检查是否能够接受allocator的属性。
    7当数据流开始和停止的时候,输出pin负责提交allocator。
    在IAsyncReader连接过程如下:
    1 输入pin调用输出pin上的IAsyncReader::RequestAllocator,输入pin确定内存的属性,并提供一个allocator。
    2 输出pin选择一个allocator,
    3 输入pin检查

    如何提供一个自定义的allocator
    这里只讲一下IMemInputPin连接,IAsyncReader类似。
    首先,定义一个C++类,你的allocator应该从一个标准的allocator类中派生,比如CBaseAllocator or CMemAllocator,你也可以自己创建一个新的allocator类,如果你是新建的类,你必须支持IMemAllocator接口。
    下面看看在输入pin和输出pin中如何使用你定义的allocator。
    在输入pin中提供allocator
    在输入pin中提供allcator,必须重载CBaseInputPin::GetAllocator方法。在这个方法里,首先检查m_pAllocator是否可用,如果为非空,就表明allocator已经被选中,所以直接返回这个allocator指针即可,如果m_pAllocator为空,表明allocator还没有被选中,所以,就要返回输入pin的allocator,因此,创建一个allcaotor的实例,返回IMemAllocator接口。
    看下面的代码把
    STDMETHODIMP CMyInputPin::GetAllocator(IMemAllocator **ppAllocator)
    {
    CheckPointer(ppAllocator, E_POINTER);
    if (m_pAllocator)
    {
    // We already have an allocator, so return that one.
    *ppAllocator = m_pAllocator;
    (*ppAllocator)->AddRef();
    return S_OK;
    }
    // No allocator yet, so propose our custom allocator. The exact code
    // here will depend on your custom allocator class definition.
    HRESULT hr = S_OK;
    CMyAllocator *pAlloc = new CMyAllocator(&hr);
    if (!pAlloc)
    {
    return E_OUTOFMEMORY;
    }
    if (FAILED(hr))
    {
    delete pAlloc;
    return hr;
    }
    // Return the IMemAllocator interface to the caller.
    return pAlloc->QueryInterface(IID_IMemAllocator, (void**)ppAllocator);
    }
    当输出pin选择一个allocator,它就调用输入pin的IMemInputPin::NotifyAllocator,因此,要重载CBaseInputPin::NotifyAllocator方法来检查allocator的属性。
    在输出pin中如何提供一个定制的Allocator
    在输出pin中提供一个allcator,要重载CBaseOutputPin::InitAllocator
    HRESULT MyOutputPin::InitAllocator(IMemAllocator **ppAlloc)
    {
    HRESULT hr = S_OK;
    CMyAllocator *pAlloc = new CMyAllocator(&hr);
    if (!pAlloc)
    {
    return E_OUTOFMEMORY;
    }
    if (FAILED(hr))
    {
    delete pAlloc;
    return hr;
    }
    // Return the IMemAllocator interface.
    return pAlloc->QueryInterface(IID_IMemAllocator, void**)ppAllocator);}
    }
    缺省情况下CBaseOutputPin首先从输入pin中申请一个allocator,
    2filter间的数据流动
    1 传递Samples
    本文讲述了如何传递一个sample,包括两种模式下,推模式下采用IMemInputPin的方法,在拉模式下调用IAsyncReader的方法。
    推模式
    输出pin通过调用IMemInputPin::Receive或者IMemInputPin::ReceiveMultiple方法来传递一个sample。在Receive和ReceiveMultiple方法里,输入pin可以阻塞数据流。如果输入pin阻塞,那么IMemInputPin::ReceiveCanBlock必须返回S_OK。如果pin保证不会阻塞,那么ReceiveCanBlock方法要返回S_FALSE,返回S_OK并不表明Receive方法阻塞,只是表明可能阻塞。
    尽管Receive可以阻塞一直等待某种资源变的可用,但是它不能通过阻塞来等待数据流的到来。因为如果上游的filter正在等待下游的filter正在等待下游的filter释放资源,就会造成死锁。如果一个filter拥有多个输入pin,那么其中的一个pin可以等待另外的一个pin接收数据。例如AVI Mux filter就是通过这种方法来同步音频和视频流的。
    如果有以下原因,pin可能拒绝sample。
    1pin正在flushing
    2pin没有连接
    3filter停止
    4发生了其他错误
    如果输入pin拒绝了sample,那么Receive方法就要返回S_FALSE,或者其它的错误码,如果Receive没有返回S_OK,那么上游的fitler就会停止发送sample。
    前面三种错误都是可以预见的错误,第四种是不可预见的错误,即使pin正处于接收数据流德状态,当这种错误发生时,接受pin就拒绝接受sample,发送pin就给下游的连接pin发送一个结束发送数据流的通知,并且给Filter 图表管理器发送一个EC_ERRORABORT事件通知。
    在directshow的基类中CBaseInputPin::CheckStreaming方法用来检查通常的数据流错误,比如flushing, stopped, and so forth。派生类要检查所发生的错误。在发生错误的时候,CBaseInputPin::Receive方法将发送一个结束数据流的通知和一个EC_ERRORABORT事件通知。
    在拉模式下,IAsyncReader接口中,输入pin将通过以下方法从输出pin中请求samples。
    ? IAsyncReader::Request
    ? IAsyncReader::SyncRead
    ? IAsyncReader::SyncReadAligned
    Reques方法是异步的,输入pin调用IAsyncReader::WaitForNext来等待请求数据传递结束。另外两个方法是同步。
    2数据处理
    //略
    3数据流结束的通知
    当一个源filter结束发送数据流时,它调用和它连接的filter的输入pin的IPin::EndOfStream,然后下游的filter再依次通知与之相连的filter。当EndOfStream方法一直调用到renderer filter的时候,最后的一个filter就给filter图表管理器发送一个EC_COMPLETE事件通知。如果renderer有多个输入pin,当所有的输入pin都接收到end of stream通知的时候,它才会给filter图表管理器发送一个EC_COMPLETE事件通知。

    Filter必须在其他函数调用之后调用EndOfStream函数,比如IMemInputPin::Receive.。
    在一些情况下,下游的filter可能比源filter更早的发现数据流的结束。在这种情况下,下游filter发送 结束stream的通知,同时, IMemInputPin::Receive函数返回S_FALSE直到图表管理器停止。这个返回值提示源filter停止发送数据。
    对EC_COMPLETE事件的缺省处理
    缺省的情况下,filter图表管理器并不将EC_COMPLETE事件通知发送给应用程序,当所有的数据流都发送了EC_COMPLETE事件通知后,它才给应用程序发送一个EC_COMPLETE事件通知。所以,应用程序只有在所有的数据流停止的时候才能接收到这个通知。

    filter图表管理器通过计算支持seeking接口的filter,并且具有一个renderer pin,没有相应的输出pin,就可以确定数据流的数目。Filter图表管理器通过下面的方法来决定一个pin是否是个renderer 。
    1 pin的IPin::QueryInternalConnections方法通过nPin参数返回0;
    2 filter保露一个IAMFilterMiscFlags接口,并且返回一个AM_FILTER_MISC_FLAGS_IS_RENDERER标志。
    在拉模式下的数据流结束通知
    在IAsyncReader连接中,源filter并不发送数据流结束的通知,相应的发送数据流结束的通知是有renderer filter发出的。
    4New Segments(本节翻译的不好,我自己都不理解,乱七八糟)
    一个段就是一组media samples,这些sample具有共同的开始时间,结束时间,播放速率。
    The IPin::NewSegment 方法用来通知一个new segments的开始。源filter通过这种方法来通知下游的filter segment的开始时间和播放速率。例如,如果源filter在数据流中改变了新的开始点,它就用新的时间做参数来通知下游的filter。
    下游的filter在处理sample的时候需要segment。例如,在桢间压缩的时候,if the stop time falls on a delta frame, the source filter may need to send additional samples after the stop time. This enables the decoder to decode the final delta frame.为了确定正确的结束桢,解码器指向色gement的停止时间。另外一个例子,在音频播放的过程中,播放filter利用segment的速度和音频sample速度来产生正确的输出。
    在推模式中,源filter 产生一个新的segment,并初始化。在拉模式,这个工作是由剖析器(parser)来完成的。两种情况下,filter都调用下游filter的输入pin上的NewSegment,一直到达renderfilter。当filters调用数据流时候,必须序列化NewSegment。
    当每一个新的segment,数据流的时间都被重新设置为零,当segment从零开始的时候,samples 重新贴上了time标签。
    5 Flushing
    当graph运行的时候,在整个graph中会有大量的数据流动。同时也有一些数据排在队列里等到传递。当graph移动这些未决的数据,并在该内存块中写入新的数据是需要一定的时间的。例如,在seek命令后,源filter在生成新的sample,这些是需要一定时间的。为了减小延迟,下游的filter在seek命令必须丢掉以前的sample。这个抛弃sample的过程就叫flushing。
    当事件改变了数据的流向时,这可以使garph响应的更及时一些。
    推模式和拉模式在处理flushing的时候有点不同。我们先讨论一下推模式,然后再讨论拉模式。
    下面两种情况下发生flushing
    1 源filter调用下游filter输入pin的IPin::BeginFlush方法,然后下游的filter就开始拒绝从上游filter接收数据流。然后它开始抛弃它正在处理的samples,继续调用下游filter的IPin::BeginFlush方法
    2 当源filter准备好新的数据时,它调用输入pin的IPin::EndFlush方法,这就告诉下游的filter可以接收新的samples,然后继续调用下游的filter的IPin::EndFlush。
    在BeginFlush方法中,输入pin进行了下列工作
    1 首先调用下游filter的输入pin上的Calls BeginFlush方法
    2拒绝处理数据流,包括Receive和endofstream方法
    3 取消那些正在阻塞等待filter释放allocator的等待,
    4 如果filter正处于阻塞数据状态,那么filter就退出阻塞。例如当停止的时候Renderer filter
    就阻塞,此时,filter就要取消阻塞。
    在EndFlush方法中,输入pin做了下列工作
    1等待所有正在队列中的samples被抛弃
    2 释放存放数据的buffer,这一步也可能在BeginFlush方法里,但是,beginflush方法streaming 线程是不同步的。Filter在BeginFlush和EndFlush方法之间不能够处理如何数据
    3清除所有的EC_COMPLETE通知
    4调用下游filter的EndFlush方法
    此时,filter可以再次接收sample。
    在拉模式中,parser Filter初始化flushing,而不是由source filter,它不仅调用了下游filterIPin::BeginFlush and IPin::EndFlush方法,它又调用了源filter输出pin上的IAsyncReader::BeginFlush and IAsyncReader::EndFlush,如果此时,源filter有未决的read请求,它就抛弃
    6Seeking
    Filter通过IMediaSeeking接口支持seeking。应用程序从filter 图表管理器请求IMediaSeeking接口,然后通过这个接口,执行seek命令。Filter图表管理器传递到graph里所有的renderer filter。每个renderer 通过上游filter的输出pin来传递seek命令,直到某一个filter可以执行这个seek命令。一般来说,源filter,或者parser filter可以执行seek 命令。
    当一个filter执行seek命令的时候,它就flushes所有的未决的数据,这是为了减少seek命令的迟延。当一个seek命令后,stream time设置为零。
    下面的图表演示了seek过程

    图1
    如果一个parser filter 的输出pin不止一个的话,它就指定一个pin来接收seek commands,其他的pin当接收到seek 命令时,就拒绝或者忽略seek 命令。这样,parser就保持了所有的数据流同步。
    IMediaPosition接口(略)
    3pin连接时数据格式的动态改变
    当两个filter连接的时候,他们会就某种媒体类型达成协议。这种数据类型用来描述上游filter将传递什么格式的数据。大多数情况下,在连接的持续过程中,这个媒体类型是不变的,但是,directshow也支持动态改变媒体类型。
    Directshow定义了一些机制来支持动态改变媒体类型。关键在于filter graph的状态和将要改变的类型。
    如果graph处于停止状态,pin可以重新连接,重新就某个媒体类型达成协议。
    一些filter还支持动态的连接,
    当graph处于活动状态,并且不支持动态的重新连接的时候,有三种机制支持媒体类型的改变。
    QueryAccept (Downstream) 适用于输出pin向下游的filter提出改变媒体类型,并且新的媒体格式的大小不超过原来的buffer。
    QueryAccept (Upstream) 适用于输入pin首先向上游的filter提出媒体类型的改变,新的媒体类型格式可以和原来的大小一致,也可以大于。
    ReceiveConnection适用于输出pin首先提出改变媒体类型,但是新的媒体类型的格式大于原来的buffer。
    4 Threads and Critical Sections
    本章讲述一下dshow filters的线程和临界区,这样,你在开发filter的过程中就可以避免死锁和系统崩溃。
    1 The Streaming and Application Threads
    任何一个Directshow的应用程序中都至少包括两个重要的线程,一个是应用程序线程,一个或者多个的Streaming线程。在streaming线程中,samples不断的传递,然后在应用程序中,**的状态不断的变化。Streaming的主线程一般都是由源filter或者parser Filter来创建,其他的filter可以创建用来传递samples的工作线程,所有的这些线程都称为streming线程。
    下面我们看看应用程序线程和streaming线程经常用到的
    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.
    当我们的用户应用程序在等待用户输入的同时,数据流可以通过streaming线程在graph图中流动。在多线程中,filter在暂停的时候,创建了资源,然后在Streaming方法中使用这些资源,当这个filter停止的时候,就销毁这些资源,这样就很危险,如果你不小心,streaming线程有可能使用了已经被销毁的资源。解决的方法就是通过临界区来保护这些资源,来同步这些线程。
    一个filter需要一个临界区来保护filter的状态。CBaseFilter类有一个成员变量来保护这个filter的状态,CBaseFilter::m_pLock。这个临界区称为filter 锁。同时,每一个输入pin都需要一个临界区来保护streaming线程使用的资源,这些临界区成为streaming锁。你必须在你派生的pin类中(输入pin)中来设置这些变量。使用一个CCritSec类很容易实现临界区。这个CCritSec类中包含一个CRITICAL_SECTION窗口对象。可以使用CAutoLock类来锁住这个临界区。
    当一个filter停止或者flushes的时候,就必须同步应用线程和streaming线程。为了避免死锁,你必须要解锁streaming线程。
    Streaming线程加锁的原因主要有以下;
    1 当streaming线程通过IMemAllocator::GetBuffer方法来请求samples的时候,如果此时所有的allocators 分配的samples都在使用,那么此时线程就要等到
    2 等到其他的filter从streaming线程方法返回,比如,Receive
    3 在streaming方法中等待其他的资源的释放。
    4 renderer Filter正在Receive方法等待,如果filter停止,就处于死锁状态了
    因此,当filter停止或者flushes时候,必须做下列事情
    1 一定要释放它正在占用的任何资源
    2尽可能快地从streaming方法中返回,如果这个方法正在等到某种资源,就放弃等待,立即返回
    3在Receive方法开始停止接受samples,这样就不会再请求新的资源了
    4 Stop方法一定要decommit所有filter的内存分配器。(当然了,CBaseInputPin会自动地处理这个事情)
    Flushing和stoping方法在应用程序中都会发生。IMediaControl::Stop方法可以使filter停止运行。Filter管理器一般让stop命令从render filter开始,逆流向上到源filter逐渐停止。通过CBaseFilter::Stop方法,在这个方法返回的时候,filter的状态就转变为停止状态。
    Filter在seek命令时一般都会Flushing。Flush命令一般都从源filter或者parser filter开始,然后向下游传递执行。Flushing有两个状态,IPin::BeginFlush方法通知一个filter开始抛弃所有的正在接受到的数据IPin::EndFlush方法通知filterflush结束,可以开始再次的接收数据。Flushing之所以有两个阶段,因为 Beginflush方法是由应用程序调用的,同时有可能还在传递数据。这样,在调用beginflush方法后,还有数据在filter中流动,此时filter就要抛弃这些数据。在调用endflush后,就能保证filter接受的数据都是新的,可以处理了。
    下面的一些临界区的例子,演示了如何保护filter的重要的方法,比如Pause,receive等
    2 Pausing
    在filter的状态发生变化的时候,必须要加锁,在Pause方法中,要创建filter需要的资源
    HRESULT CMyFilter::Pause()
    {
    CAutoLock lock_it(m_pLock);

    /* Create filter resources. */

    return CBaseFilter::Pause();
    }
    CBaseFilter::Pause方法设置filter处于State_Paused)状态,然后调用和filter相连接的pin上的CBasePin::Active方法,Active方法通知pin:filter开始处于激活状态了。如果pin需要创建资源,那么就要派生Active方法了
    HRESULT CMyInputPin::Active()
    {
    // You do not need to hold the filter lock here. It is already held in Pause.

    /* Create pin resources. */

    return CBaseInputPin::Active()
    }
    3 Receiving and Delivering Samples
    下面的伪代码演示了如何派生输入pin的Receive方法
    HRESULT CMyInputPin::Receive(IMediaSample *pSample)
    {
    CAutoLock cObjectLock(&m_csReceive);

    // Perhaps the filter needs to wait on something.
    WaitForSingleObject(m_hSomeEventThatReceiveNeedsToWaitOn, INFINITE);

    // Before using resources, make sure it is safe to proceed. Do not
    // continue if the base-class method returns anything besides S_OK.
    hr = CBaseInputPin::Receive(pSample);
    if (hr != S_OK)
    {
    return hr;
    }

    /* It is safe to use resources allocated in Active and Pause. */

    // Deliver sample(s), via your output pin(s).
    for (each output pin)
    pOutputPin->Deliver(pSample);

    return hr;
    }
    Receive方法设置了一个streaming锁,不是filter锁。Filter在开始处理数据之前可能需要等待其他事件发生,这里就调用了WaitForSingleObject。当然并不是所有的filter都需要这样做。CBaseInputPin::Receive方法验证一些基本的streaming条件,如果filter停止旧返回VFW_E_WRONG_STATE,如果filter flushing该方法返回s_FALSE;如果没有返回S_OK就表示Receive方法应该立即返回,不能处理sample。
    在sample被处理后,就调用CBaseOutputPin::Deliver.方法向下传递。依次类推。数据开始传递下去。
    4 Delivering the End of Stream
    当一个输入pin接收到一个数据流停止的通知后,它就会向下传播下去。下游的可以从这个输入pin接收数据的filter也应该接收到数据流停止的通知。如果filter还有未决的数据没有处理,filter应该在它传递停止通知之前一定要将数据传递下去,在end tream消息发布以后,不能再向下游filter发送数据了
    HRESULT CMyInputPin::EndOfStream()
    {
    CAutoLock lock_it(&m_csReceive);

    /* If the pin has not delivered all of the data in the stream
    (based on what it received previously), do so now. */

    // Propagate EndOfStream call downstream, via your output pin(s).
    for (each output pin)
    {
    hr = pOutputPin->DeliverEndOfStream();
    }
    return S_OK;
    }
    CBaseOutputPin::DeliverEndOfStream调用了与输出pin连接的输入pin的IPin::EndOfStream方法来通知下游的filter数据流即将停止了。
    5 Flushing Data
    下面的伪代码演示IPin::BeginFlush方法
    HRESULT CMyInputPin::BeginFlush()
    {
    CAutoLock lock_it(m_pLock);

    // First, make sure the Receive method will fail from now on.
    HRESULT hr = CBaseInputPin::BeginFlush();

    // Force downstream filters to release samples. If our Receive method
    // is blocked in GetBuffer or Deliver, this will unblock it.
    for (each output pin)
    {
    hr = pOutputPin->DeliverBeginFlush();
    }

    // Unblock our Receive method if it is waiting on an event.
    SetEvent(m_hSomeEventThatReceiveNeedsToWaitOn);

    // At this point, the Receive method can’t be blocked. Make sure
    // it finishes, by taking the streaming lock. (Not necessary if this
    // is the last step.)
    {
    CAutoLock lock_2(&m_csReceive);

    /* Now it’s safe to do anything that would crash or hang
    if Receive were executing. */
    }
    return hr;
    }
    当开始Flushing的时候,BeginFlush方法使用了filter锁。如果使用streaming锁不是很安全,因为flush是发生在应用程序中,有可能此时streaming 线程正处于Receive方法中。Pin要保证Receive方法没有被阻塞,否则,后续调用Receive方法都会失败。
    CBaseInputPin::BeginFlush设置了一个标志位CBaseInputPin::m_bFlushing.,如果这个标志为TRUE,Receive方法就会失败。
    在下游filter传递Beginflush方法的时候,pin一定要保证下游的filter释放他们的samples并且从Receive方法中返回。这就保证了输入pin不会阻塞在GetBuffer和Receive方法中。如果你的pin的Receive方法正在等待某种资源,那么GeginFlush方法就通知设置某些特定事件来结束这种等待,这就保证Receive方法能够立即返回,m_bFlushing标志就阻止Receive方法调用。
    对于一些filter来说,这些都是必须要做的,EndFlush方法通知filter可以重新接收数据了。Endflush方法使用的是filter锁。然后向下传播。
    HRESULT CMyInputPin::EndFlush()
    {
    CAutoLock lock_it(m_pLock);
    for (each output pin)
    hr = pOutputPin->DeliverEndFlush();
    return CBaseInputPin::EndFlush();
    }
    CBaseInputPin::EndFlush重新设置m_bFlushing标志为FALSE。这样Receive方法就可以开始接收数据了,必须在最后调用这个方法。
    6 Stopping
    Stop方法必须unblock Receive方法并且decommit filter的内存分配器。这样就使GetBuffer返回。Stop方法使用了filter锁,然后调用了CBaseFilter::Stop,这个stop方法调用了filter pins上的CBasePin::Inactive方法。
    HRESULT CMyFilter::Stop()
    {
    CAutoLock lock_it(m_pLock);
    // Inactivate all the pins, to protect the filter resources.
    hr = CBaseFilter::Stop();

    /* Safe to destroy filter resources used by the streaming thread. */

    return hr;
    }
    Override the input pin’s Inactive method as follows:

    HRESULT CMyInputPin::Inactive()
    {
    // You do not need to hold the filter lock here.
    // It is already locked in Stop.

    // Unblock Receive.
    SetEvent(m_hSomeEventThatReceiveNeedsToWaitOn);

    // Make sure Receive will fail.
    // This also decommits the allocator.
    HRESULT hr = CBaseInputPin::Inactive();

    // Make sure Receive has completed, and is not using resources.
    {
    CAutoLock c(&m_csReceive);

    /* It is now safe to destroy filter resources used by the
    streaming thread. */
    }
    return hr;
    }
    7 Getting Buffers
    如果你有一个内存分配器来分配资源,那么GetBuffer方法采用streaming锁。
    HRESULT CMyInputAllocator::GetBuffer(
    IMediaSample **ppBuffer,
    REFERENCE_TIME *pStartTime,
    REFERENCE_TIME *pEndTime,
    DWORD dwFlags)
    {
    CAutoLock cObjectLock(&m_csReceive);

    /* Use resources. */

    return CMemAllocator::GetBuffer(ppBuffer, pStartTime, pEndTime, dwFlags);
    }
    8 Streaming Threads and the Filter Graph Manager
    当filter图表管理器停止graph,它要等待所有的streaming线程停止,
    9 Summary of Filter Threading
    Streaming线程调用的函数
    IMemInputPin::Receive
    IMemInputPin::ReceiveMultiple
    IPin::EndOfStream
    IPin::NewSegment
    IMemAllocator::GetBuffer

    看看应用程序调用的函数把
    状态改变
    IBaseFilter::JoinFilterGraph, IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IQualityControl::SetSink.
    参考时钟
    IMediaFilter::GetSyncSource, IMediaFilter::SetSyncSource.
    Pin的操作
    IBaseFilter::FindPin, IPin::Connect, IPin::ConnectedTo, IPin::ConnectionMediaType, IPin::Disconnect, IPin::ReceiveConnection.
    内存的分配
    IMemInputPin::GetAllocator, IMemInputPin::NotifyAllocator.
    Flushing
    IPin::BeginFlush, IPin::EndFlush.
    5质量控制管理
    DirectShow Base Classes提供了一些缺省的方法来控制视频的质量。质量消息从renderer开始,这个renderer基类为CBaseVideoRenderer,这个基类有下列一些行为
    1 当视频render接收到sample的时候,它就将sample上的时间戳和当前的参考时间比较
    2 视频render产生一个质量消息,在基类中,质量消息的比例值被限制在500(50%)~~2000(200%),超出这个范围,质量就变得不好
    3缺省的时候,render就给上游的filter输出pin发送一个质量消息,应用程序可以同过setsink方法来override这种行为。
    当消息被逆流传递到上一个filter时会发生什么呢?一般来说,上游的filter都是一个transform 过滤器。这种transformfilter都有一个CTransformInputPin and CTransformOutputPin。
    1 CTransformOutputPin::Notify方法调用CTransformFilter::AlterQuality
    2transformfilte可以通过重载AlterQuality方法来处理质量消息,缺省的情况下,AlterQuality忽略质量消息
    3如果AlterQuality不处理质量消息,输出pin就调用输入pin上的CBaseInputPin::PassNotify,
    4PassNotify方法就将质量消息传递给上游的其他的filter。
    假定没有传输filter来处理质量消息,质量消息就最后到达了源filter。在基类中,CBasePin::Notify 返回 E_NOTIMPL。源filter如何处理质量消息取决于源filter的nature。一些filter可以调整他们发送sample的速率。

    图2
    6Directshow和com
    微软的Directshow是基于COM的,如果你开发自己的filter,你一定实现一个com对象。Directshow的基类提供了一个框架可以实现com接口,你不一定适用基类,但是使用基类可以减轻你的开发任务。下面我们讲讲dshow和com关系
    我们假设你了解如何开发com的客户端,也就是说你了解IUnknown方法,但是并不详细了解如何开发一个com对象,Directshow将如何开发com的一切细节都替你处理好了。如果你有开发com的经验,你应该读一下Using CUnknown一章。
    Com是一个协议,它规定了所有组件必须遵循的规则,如何应用这些规则就留给开发者了。在Directshow中,所有的对象都是从一系列的基类中继承而来,这些基类的函数和构造器将com对象的构造工作都基本做完了,例如引用计数,接口等功能。你将你的filter从基类中继承,你就继承了基类的函数,同时你的filter就是一个com对象了。为了好好继承基类,你必须要了解基类是如何实现com对象的。
    本章要求你了解一下三个内容
    How IUnknown Works
    How to Create a DLL.
    How to Register DirectShow Filters.

    1 How IUnknown Works
    通过IUnknown接口的方法应用程序可以请求对象的接口,管理组件的引用计数。
    引用计数
    引用是个内部变量,通过addref方法可以是变量增加,通过release方法可以使该变量值减少。基类管理者这个引用计数,并且是多线程同步。
    接口请求
    接口请求也是简单易懂的。调用者传递两个参数,一个是接口的ID,和接口的指针的地址。如果组件支持该接口,就将该指针指向接口,引用计数增加1,返回S_OK,否则,将该指针设置为NULL,返回E_NOINTERFACE。下面的伪代码演示了QueryInterface函数
    if (IID == IID_IUnknown)
    set pointer to (IUnknown *)this
    AddRef
    return S_OK

    else if (IID == IID_ISomeInterface)
    set pointer to (ISomeInterface *)this
    AddRef
    return S_OK

    else if …

    else
    set pointer to NULL
    return E_NOINTERFACE
    两个组件的querinterface函数的区别就在于IIDS的列表不同,对于组件支持的每一个接口,都要检查IID是否正确。
    聚合和委托
    组件的聚合对于调用者必须是透明的,因此,聚合体必须只能暴露一个IUnknown接口,被聚合组件的接口听从外部的组件的调用,否则客户端就看到两个不同的IUnknown接口,如果组件不被聚合,他们都有自己的实现。
    为了支持聚合,组件必须增加一个额外的间接的指针,用来分别指向外部接口和内部接口,nondelegating IUnknown接口就可以完成这项工作。
    外部的接口是公开的,还叫做IUnknown,内部的接口叫做INonDelegatingUnknown.,这个名字不是com定义的,因为它不是一个公开的接口
    当客户端创建了一个组件的实例,它调用IClassFactory::CreateInstance方法,一个参数就指向组件的IUnknown接口,组件就利用这个参数来保存一个变量用来标示需要采用哪个IUnknown接口,看看下面的代码
    CMyComponent::CMyComponent(IUnknown *pOuterUnkown)
    {
    if (pOuterUnknown == NULL)
    m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this;
    else
    m_pUnknown = pOuterUnknown;

    [ ... more constructor code ... ]
    }
    委托的IUnknown接口调用的它的副本,如下
    HRESULT QueryInterface(REFIID iid, void **ppv)
    {
    return m_pUnknown->QueryInterface(iid, ppv);
    }
    Using CUnknown
    Directshow在CUnknown.基类中实现了IUnkown接口,directshow的其他基类也多是从CUnknown类中继承过来的,所以,你也可以从这个类派生一个新类或者从其他基类派生你自己的类。
    INonDelegatingUnknown
    CUnknown类还实现了一个INonDelegatingUnknown接口,这个接口是用来管理引用计数的。大多数情况下,你的派生类可以直接这个接口的两个管理引用计数的函数而不用做任何的修改。但是你要记住,当引用计数等于0的时候,CUnknown就要销毁自己。有时候你必须要派生CUnknown::NonDelegatingQueryInterface函数,因为在基类中,如果它发现请求接口的ID不是IID_IUnknown的时候就返回E_NOINTERFACE,在你的派生类中,你就要自己来测试你所支持的接口ID,看下面的例子
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
    {
    if (riid == IID_ISomeInterface)
    {
    return GetInterface((ISomeInterface*)this, ppv);
    }
    // default
    return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
    GetInterface将接口指针设置为所请求的接口,返回s_ok,增加引用计数,缺省的情况下,要调用你继承类的NonDelegatingQueryInterface
    IUnknown
    日前所述,任何一个组件的IUnknown接口的处理流程都是一样的。因为它仅仅是根据客户端的请求返回正确的接口即可。因此,为了方便,在Combase.h头文件中声明了一个宏DECLARE_IUNKNOWN,这种宏中定义了三个内联函数,如下
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv) {
    return GetOwner()->QueryInterface(riid,ppv);
    };
    STDMETHODIMP_(ULONG) AddRef() {
    return GetOwner()->AddRef();
    };
    STDMETHODIMP_(ULONG) Release() {
    return GetOwner()->Release();
    };
    CUnknown::GetOwner函数返回拥有这个IUnknown接口的组件,对于聚合组件,ower指的外部的组件。否则,指的就是自己。在你自己的类中的公共部分,包含DECLARE_IUNKNOWN宏声明。
    类的构造
    你自己的类的构造函数一定要从基类的构造函数继承过来,如下
    CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr)
    : CUnknown(tszName, pUnk, phr)
    {
    /* Other initializations */
    };
    构造函数一般都有三个参数,这三个参数传递到CUnknown类的构造函数中,其实你的构造函数基本不用作任何工作,一切都在CUnknown函数的构造函数给你做好了。
    TszName指定组件的名字
    pUnk指向一个IUnknown指针
    pHr指向一个HRESULT值,表示成功还是失败。
    下面的例子演示了,如何派生你自己的类,这个类支持Iunknown接口还有一个ISomeInterface接口
    class CMyComponent : public CUnknown, public ISomeInterface
    {
    public:

    DECLARE_IUNKNOWN;

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
    {
    if( riid == IID_ISomeInterface )
    {
    return GetInterface((ISomeInterface*)this, ppv);
    }
    return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }

    CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr)
    : CUnknown(tszName, pUnk, phr)
    {
    /* Other initializations */
    };

    // More declarations will be added later.
    };
    这个例子假定了一下情况
    1, CUnknown类实现了IUnknown接口,如何新的组件从CUnknown基类继承过来,那么同时也就继承了基类所支持的接口。你可以从其他继承于CUnknown的类派生你自己的类,而不一定要从CUnknown派生
    2, DECLARE_IUNKNOWN宏将IUnknown接口的三个函数声明为内联函数
    3, CUnknown类提供了INonDelegatingUnknown.的实现
    4, 如果新的组件支持的接口不仅仅是IUnknown,那么就一定要重新继承INonDelegatingUnknown.函数,测试新接口的ID,如上代码
    5, 派生类的构造函数触发了CUnknown的构造函数
    下一步就是使得应用程序创建组件的一个实例,这就要求了解DLLs和类厂,以及构造函数的关系,请看How to Create a DLL.
    2How to Create a DLL.
    在客户端创建一个com对象的实例以前,它首先通过CoGetClassObject方法创建一个对象类厂的实例。然后客户端调用类厂的IClassFactory::CreateInstance方法。类厂自动创建一个组件然后返回一个指向组件对象接口的指针。CoCreateInstance方法是上面两步的综合。
    下面的图演示了对象创建

    图3
    CoGetClassObject方法调用了DLL中的DllGetClassObject方法,这个方法创建了一个类厂对象,并且返回一个类厂对象的接口。Directshow已经替你完成了DllGetClassObject方法 为了了解他们是如何工作,你必须了解directshow是如何实现类厂的。
    类厂也是一个com对象,它可以用来创建其他的com组件。每个类厂只能用来创建特定的com组件对象。在directshow中,每一个类厂都是C++类CClassFactory的一个实例,类厂是通过一个叫做类厂模板CFactoryTemplate,来实现的。每个类厂类都有一个指向类厂模板的指针,类厂模板包含了要创建的组件的信息,比如CLSID,和一个指向创建对象函数的指针。
    DLL中定义了一个全局的类厂模板的数组,每一个动态的DLL中都有这么一个全局的模版数组,当DllGetClassObject函数创建组件对象的时候,它就搜索全局数组里的匹配的CLSID,如果它找到匹配的数组,它就创建一个包含一个指向匹配模板指针的类厂对象,当客户端调用IClassFactory::CreateInstance 方法的时候,类厂就调用模版中的函数来创建组件对象,下面的DllGetClassObject函数是我从dshow的基类中找到的,我们仔细看看吧
    STDAPI
    DllGetClassObject(
    REFCLSID rClsID,
    REFIID riid,
    void **pv)
    {
    if (!(riid == IID_IUnknown) && !(riid == IID_IClassFactory)) {
    return E_NOINTERFACE;
    }

    // 首先在类厂模板数组中查找相应的CLSID
    // class id
    for (int i = 0; i < g_cTemplates; i++) {
    const CFactoryTemplate * pT = &g_Templates[i];
    if (pT->IsClassID(rClsID)) {
    //如果找到,就用这个类厂模板作参数生成一个类厂对象
    // found a template – make a class factory based on this
    // template

    *pv = (LPVOID) (LPUNKNOWN) new CClassFactory(pT);
    if (*pv == NULL) {
    return E_OUTOFMEMORY;
    }
    ((LPUNKNOWN)*pv)->AddRef();
    return NOERROR;
    }
    }
    return CLASS_E_CLASSNOTAVAILABLE;
    }

    图4
    下面我们看看类厂模板数组
    类厂模板数组包含以下变量
    const WCHAR * m_Name; // Name
    const CLSID * m_ClsID; // CLSID
    LPFNNewCOMObject m_lpfnNew; // Function to create an instance of the component
    LPFNInitRoutine m_lpfnInit; // Initialization function (optional)
    const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter; // Set-up information (for filters)
    两个函数指针m_lpfnNew and m_lpfnInit使用如下的定义
    typedef CUnknown *(CALLBACK *LPFNNewCOMObject)
    (LPUNKNOWN pUnkOuter, HRESULT *phr);
    Typedef void (CALLBACK *LPFNInitRoutine)
    (BOOL bLoading, const CLSID *rclsid);
    第一个用来创建对象的实例函数,第二个函数是初始化函数,如果你定义了这个函数,在动态库DLL的进入点函数就会调用这个初始化函数。
    假设你定义了一个DLL,这个DLL包含了一个叫做CMYComponent组件,它继承与CUnknown,你必须在你的DLL定义下面的几个东西
    1 一个创建你的组件的实例的公有函数。
    2一个全局的类厂模板数组,名字一定要命名为g_Templates,这个数组包含创建组件的类厂模板
    3一个叫做全局变量g_cTemplates,这个变量用来表示类厂模板数组的大小。
    下面是示例代码
    // Public method that returns a new instance.
    CUnknown * WINAPI CMyComponent::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
    {
    CMyComponent *pNewObject = new CMyComponent(NAME("My Component"), pUnk, pHr );
    if (pNewObject == NULL) {
    *pHr = E_OUTOFMEMORY;
    }
    return pNewObject;
    }

    CFactoryTemplate g_Templates[1] =
    {
    {
    L"My Component", // Name
    &CLSID_MyComponent, // CLSID
    CMyComponent::CreateInstance, // Method to create an instance of MyComponent
    NULL, // Initialization function
    NULL // Set-up information (for filters)
    }
    };
    int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
    类厂的CreateInstance方法调用组件的构造函数,并返回一个指向新类实例的一个指针。参数punk指向IUnknown接口指针,看看我从基类里找到的Createinstance函数的代码
    STDMETHODIMP
    CClassFactory::CreateInstance(
    LPUNKNOWN pUnkOuter,
    REFIID riid,
    void **pv)
    {
    CheckPointer(pv,E_POINTER)
    ValidateReadWritePtr(pv,sizeof(void *));

    /* Enforce the normal OLE rules regarding interfaces and delegation */

    if (pUnkOuter != NULL) {
    if (IsEqualIID(riid,IID_IUnknown) == FALSE) {
    return ResultFromScode(E_NOINTERFACE);
    }
    }

    /* Create the new object through the derived class’s create function */

    HRESULT hr = NOERROR;
    CUnknown *pObj = m_pTemplate->CreateInstance(pUnkOuter, &hr);

    if (pObj == NULL) {
    if (SUCCEEDED(hr)) {
    hr = E_OUTOFMEMORY;
    }
    return hr;
    }

    /* Delete the object if we got a construction error */

    if (FAILED(hr)) {
    delete pObj;
    return hr;
    }

    /* Get a reference counted interface on the object */

    /* We wrap the non-delegating QI with NDAddRef & NDRelease. */
    /* This protects any outer object from being prematurely */
    /* released by an inner object that may have to be created */
    /* in order to supply the requested interface. */
    pObj->NonDelegatingAddRef();
    hr = pObj->NonDelegatingQueryInterface(riid, pv);
    pObj->NonDelegatingRelease();
    /* Note that if NonDelegatingQueryInterface fails, it will */
    /* not increment the ref count, so the NonDelegatingRelease */
    /* will drop the ref back to zero and the object will "self-*/
    /* destruct". Hence we don’t need additional tidy-up code */
    /* to cope with NonDelegatingQueryInterface failing. */

    if (SUCCEEDED(hr)) {
    ASSERT(*pv);
    }

    return hr;
    }
    我们发现,类厂就是通过全局变量g_Templates and g_cTemplates来创建组件的,所以,g_Templates and g_cTemplates的名字不能改变
    下面看看dll的导出函数
    DLL Functions
    一个动态库必须导出如下的函数,才能够注册,注销,加载。
    ? DllMain: The DLL entry point. The name DllMain is a placeholder for the library-defined function name. The DirectShow implementation uses the name DllEntryPoint. For more information, see the Platform SDK. Dshow没有DllMain,改用了DllEntryPoint。进入点函数
    ? DllGetClassObject: Creates a class factory instance. Described in the previous sections.
    ? DllCanUnloadNow: Queries whether the DLL can safely be unloaded.
    ? DllRegisterServer: Creates registry entries for the DLL.
    ? DllUnregisterServer: Removes registry entries for the DLL.
    当然了,前面的三个函数,directshow已经替我们完成了,如果你的类厂模板数组中的m_lpfnInit指针有一个初始化函数,那么在DLL的入口函数就会被调用。
    你自己必须要提供DllRegisterServer and DllUnregisterServer函数来实现组件的注册和反注册,但是Directshow提供了提供了一个函数叫做AMovieDllRegisterServer2已经替你做完了必要的工作,所以你要做的就很简单了,只需如下就可以了
    STDAPI DllRegisterServer()
    {
    return AMovieDllRegisterServer2( TRUE );
    }

    STDAPI DllUnregisterServer()
    {
    return AMovieDllRegisterServer2( FALSE );
    }
    当然,在DllRegisterServer and DllUnregisterServer 函数中,你可以根据需要定制自己的注册信息,如果你的组件包含一个过滤器,你就要自己来做一下额外的工作了,具体的你可以下节How to Register DirectShow Filters.
    在你的module-definition (.def) file文件中,除了进入点函数,你要导出下面的函数,实例如
    EXPORTS
    DllGetClassObject PRIVATE
    DllCanUnloadNow PRIVATE
    DllRegisterServer PRIVATE
    DllUnregisterServer PRIVATE
    你可以用Regsvr32.exe来注册你的组件

    3How to Register DirectShow Filters.
    Directshow的filter一般都注册在两个地方
    1 包含filter的DLL一般都注册为filter的COM 服务器,当用户调用CoCreateInstance来创建一个filter的时候,微软的COM库就从这个注册表的入口加载DLL。
    2 另外,filter可以注册到filter 种类里,这样,System Device Enumerator and the Filter Mapper就可以找到filter了。
    第二种注册不是必须的,只要filter注册成为com服务器,一个应用程序就可以创建一个filter 并将它加入到 filter Graph中,但是,如果你想要你的filter 可以被System Device Enumerator and the Filter Mapper发现,你必须注册到filter 种类里。
    Com服务器的入口注册有下列以些键值
    HKEY_CLASSES_ROOT
    CLSID
    Filter CLSID
    REG_SZ: (Default) = Friendly name

    InprocServer32
    REG_SZ: (Default) = File name of the DLL
    REG_SZ: ThreadingModel = Both
    注册成filter 种类里需要下面的键值
    HKEY_CLASSES_ROOT
    CLSID
    Category
    Instance
    Filter CLSID
    REG_SZ: CLSID = Filter CLSID
    REG_BINARY: FilterData = Filter information
    REG_SZ: FriendlyName = Friendly name
    Category is the GUID of a filter category.
    所有的filter信息在注册表的filter种类都如下所示
    HKEY_CLASSES_ROOT\CLSID\{DA4E3DA0-D07D-11d0-BD50-00A0C911CE86}\Instance
    1 声明filter信息Declaring Filter Information
    第一步要声明filter的信息,directshow定义了如下的结构来声明filter ,pin和media Types
    Structure Description
    AMOVIESETUP_FILTER Describes a filter.
    AMOVIESETUP_PIN Describes a pin.
    AMOVIESETUP_MEDIATYPE Describes a media type.
    这些结构是必须的。
    AMOVEIESETUP_FILTER结构包含一个指针指向AMOVIESETUP_PIN结构数组,这两个结构中都有一个指针指向AMOVEIESETUP_MEDIATYPE。这些结构提供了足够的信息可以让IFilterMapper2指针找到filter 的位置。但是,这并不能完全描述一个filter,例如,如果一个filter创建了一个pin的多个实例,你只需要声明一个AMOVIESETUP_PIN结构即可。同样,一个filter 没有必须支持注册的所有的媒体类型,也没有必要注册所有的媒体类型。
    在你的DLL中声明一些全局的Set_up结构变量,如下
    static const WCHAR g_wszName[] = L"Some Filter";

    AMOVIESETUP_MEDIATYPE sudMediaTypes[] = {
    { &MEDIATYPE_Video, &MEDIASUBTYPE_RGB24 },
    { &MEDIATYPE_Video, &MEDIASUBTYPE_RGB32 },
    };

    AMOVIESETUP_PIN sudOutputPin = {
    L"", // Obsolete, not used.
    FALSE, // Is this pin rendered?
    TRUE, // Is it an output pin?
    FALSE, // Can the filter create zero instances?
    FALSE, // Does the filter create multiple instances?
    &GUID_NULL, // Obsolete.
    NULL, // Obsolete.
    2, // Number of media types.
    sudMediaTypes // Pointer to media types.
    };

    AMOVIESETUP_FILTER sudFilterReg = {
    &CLSID_SomeFilter, // Filter CLSID.
    g_wszName, // Filter name.
    MERIT_NORMAL, // Merit.
    1, // Number of pin types.
    &sudOutputPin // Pointer to pin information.
    };
    Filter的名字被声明成静态全局变量,因为它有可能在其它地方用到。
    2 声明类厂模板数组Declaring the Factory Template
    CFactoryTemplate g_Templates[] = {
    {
    g_wszName, // Name.上面定义的全局变量
    &CLSID_SomeFilter, // CLSID.
    CSomeFilter::CreateInstance, // Creation function.
    NULL,
    &sudFilterReg // Pointer to filter information.
    }
    };
    int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
    3生成DllRegisterServer
    最后一步是生成DllRegisterServer函数,包含组件的DLL必须导出这个函数,这个函数在安装的时候被调用,或者当用户运行Regsvr32.exe时调用到。
    简单的实现如下
    STDAPI DllRegisterServer(void)
    {
    return AMovieDllRegisterServer2(TRUE);
    }
    AMovieDllRegisterServer2对于g_Templates数组中的所有组件都创建注册表入口,但是这个函数有一些限制,第一,它将所有的filter都注册到"DirectShow Filters"类下(CLSID_LegacyAmFilterCategory),其实并非所有的filter都属于这个种类。例如,捕捉filter和压缩filter就有他们自己的种类,第二,如果你的filte支持一个硬件设备,你必须要注册额外的两个信息the medium and the pin category.,但是AMovieDLLRegisterServer2并不支持,pin 的种类定义了一个pin的函数方法。Mediums和硬件的驱动有关。
    如果你要注册filter的种类,medium或者pin的种类,你可以在DllRegisterServer()中调用IFilterMapper2::RegisterFilter,这个方法有个REGFILTER2结构,包含了filter的信息。
    为了支持复杂的情况,REGFILTER2结构支持两种不同格式pin的注册,dwVersion表示两种格式
    如果dwVersion为1,pin的类型就是AMOVIESETUP_PIN
    如果dwVersion为2,拼得类型就是REGFILTERPINS2.
    REGFILTERPINS2.结构中包含mediums和pin的categories
    下面的例子演示了,如何在DllRegistServer中调用IFilterMapper2::RegisterFilter
    REGFILTER2 rf2FilterReg = {
    1, // Version 1 (no pin mediums or pin category).
    MERIT_NORMAL, // Merit.
    1, // Number of pins.
    &sudPins // Pointer to pin information.
    };

    STDAPI DllRegisterServer(void)
    {
    HRESULT hr;
    IFilterMapper2 *pFM2 = NULL;

    hr = AMovieDllRegisterServer2(TRUE);
    if (FAILED(hr))
    return hr;

    hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
    IID_IFilterMapper2, (void **)&pFM2);

    if (FAILED(hr))
    return hr;

    hr = pFM2->RegisterFilter(
    CLSID_SomeFilter, // Filter CLSID.
    g_wszName, // Filter name.
    NULL, // Device moniker.
    &CLSID_VideoCompressorCategory, // Video compressor category.
    g_wszName, // Instance data.
    &rf2FilterReg // Pointer to filter information.
    );
    pFM2->Release();
    return hr;
    }
    4filter注册指南
    Filter的注册信息决定了,在filter Graph管理器中如何Intelligent Connect.。因此,你必须要遵从下列的规则,使得你的filter能够正常运行。
    1 你是否需要在注册表中保存你的filter数据,对于许多filter来说,没有必要让filter Mapper和System Device Enumerator来发现你的filter,只要你注册了你的filter,你的应用程序通过
    ConCreateInstance方法来创建你的filter,此时,忽略了类厂模板中的AMOVIESETUP_FILTER结构,缺点是,在GraphEdit中看不到你的filter。
    2选择正确的filter 种类,缺省的Directshow Filters可能适用于大多数的filter,但是如果你的filter有特殊的用处,你要选择一个恰当的种类
    3避免在pin的AMOVIESETUP_MEDIATYPE结构中使用MEDIATYPE_None, MEDIASUBTYPE_None, or GUID_NULL,IFilterMapper2会将这些视做通配符。
    4下面是一些建议的最小******不明白
    Type of filter Recommended merit
    Default renderer MERIT_PREFERRED. For standard media types, however, a custom renderer should never be the default.
    Non-default renderer MERIT_DO_NOT_USE or MERIT_UNLIKELY
    Mux MERIT_DO_NOT_USE
    Decoder MERIT_NORMAL
    Spitter, parser MERIT_NORMAL or lower
    Special purpose filter; any filter that is created directly by the application MERIT_DO_NOT_USE
    Capture MERIT_DO_NOT_USE
    "Fallback" filter; for example, the Color Space Converter Filter
    MERIT_UNLIKELY

    5不要将接受24位RGB的filter注册到Directshow filter,你的filter将会干扰Color Space Converter filter.工作
    5 反注册
    为了反注册filter,
    要提供一个DllUnregisterServer方法,在这个方法中,调用AMovieDllRegisterServer2,注意传递参数,FASLE,如果你是使用IFilterMapper2::RegisterFilter注册的你的filter,那么你必须要用IFilterMapper2::UnregisterFilter方法来反注册你的filter。如下
    STDAPI DllUnregisterServer()
    {
    HRESULT hr;
    IFilterMapper2 *pFM2 = NULL;

    hr = AMovieDllRegisterServer2(FALSE);
    if (FAILED(hr))
    return hr;

    hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
    IID_IFilterMapper2, (void **)&pFM2);

    if (FAILED(hr))
    return hr;

    hr = pFM2->UnregisterFilter(&CLSID_VideoCompressorCategory,
    g_wszName, CLSID_SomeFilter);

    pFM2->Release();
    return hr;
    }

    作者简介:李强,目前暂时供职于山大联润信息科技有限公司,从事网络视频会议软件的开发,目前的感兴趣的方向,移动设备上多媒体的开发。aooang@hotmail.com 欢迎转载本文档 。