引言

经过几天的努力终于将VFW视频采集与显示功能完整实现了,不得不说网上对这方面完整的详细讲解文章是在太少了。所以就要本人来好好总结一下让后来者不再像我一样折腾好久。在本文中我将详细讲解VFW视频采集过程的实现,以及采集后视频的显示方法。

VFW简介

虽然这是篇技术博文,但是我觉得用一个东西,那么关于它的概述还是不能少,所以特从百度上copy了下VFW的概念描述,如果读者不想看可以直接去观看正文部分。

VFW(Video for Windows)是Microsoft推出的关于数字视频的一个软件开发包,VFW的核心是AVI文件标准。AVI(Audio Video Interleave)文件中的音、视频数据帧交错存放。围绕AVI文件,VFW推出了一整套完整的视频采集、压缩、解压缩、回放和编辑的应用程序接口(API)。它引进AVI的文件标准,该标准未规定如何对视频进行捕获、压缩及播放,仅规定视频和音频该如何存储在硬盘上,在AVI文件中交替存储视频帧和与之相匹配的音频数据。VFW给程序员提供.VBX和AVICap窗口类的高级编程工具,使程序员能通过发送消息或设置属性来捕获、播放和编辑视频剪辑。现在用户不必专门安装VFW了,Windows95本身包括了Video for Windows1.1,当用户在安装Windows时,安装程序会自动地安装配置视频所需的组件,如设备驱动程序、视频压缩程序等。 由于AVI文件格式推出较早且在数字视频技术中有广泛的应用,所以VFW仍然有很大的实用价值,而且进一步发展的趋势。

VFW主要由以下六个模块组成:

(1)AVICAP.DLL:包含了执行视频捕获的函数,它给AVI文件I/O和视频、音频设备驱动程序提供一个高级接口;

(2)MSVIDEO.DLL:用一套特殊的DrawDib函数来处理屏幕上的视频操作;

(3)MCIAVI.DRV:此驱动程序包括对VFW的MCI命令的解释器

(4)AVIFILE.DLL:支持由标准多媒体I/O(mmio)函数提供的更高的命令来访问.AVI文件;

(5)压缩管理器(ICM):管理用于视频压缩-解压缩的编解码器(CODEC);

(6)音频压缩管理器ACM:提供与ICM相似的服务,不同的是它适于波形音频。

Visual C++在支持VFW方面提供有vfw32.lib、 msacm32.lib 、winmm.lib等类似的库。特别是它提供了功能强大、简单易行、类似于MCIWnd的窗口类AVICap。AVICap为应用程序提供了一个简单的、基于消息的接口,使之能访问视频和波形音频硬件,并能在将视频流捕获到硬盘上的过程中进行控制

在VC++开发环境中调用VFW和使用其它开发包没有什么不同,只是需要将VFW32.lib文件加入工程中,但在开放视频捕捉与压缩管理程序时需要其它软件硬件设置。VFW为AVI文件提供了丰富的处理函数和宏定义,AVI文件的特点在于它是典型的数据流文件,它由视频流音频流、文本流组成。所以对AVI文件的处理主要是处理文件流。

正文

关于VFW采集的过程我大致归纳如下几个步骤:

一、capCreateCaptureWindow 创建视频采集窗口,注意此处创建的窗口并不是MFC中的窗口。

//创建视频采集窗口(注意此窗口与我们所说的显示窗口不同),并设置预览窗口
    //IDC_VIDEO_LOCAL 参数是采集窗口的ID,此处直接使用的是显示窗口的id
    m_CapWnd = capCreateCaptureWindow(TEXT("My Video Capture"), WS_CHILD | WS_VISIBLE, 0, 0, LocalRect.Width(),
        LocalRect.Height(), LocalWnd->GetSafeHwnd(), IDC_VIDEO_LOCAL);

二、设置回调函数,在VFW中可以设置的回调函数有以下几种,可以根据程序需要设置:

1、BOOL capSetCallbackOnCapControl(hwnd, fpProc ); 此宏可以设置用于精确控制采集的开始和结束的控制函数,此回调函数原型为:

LRESULT CALLBACK capControlCallback(HWND hWnd, int nState );

hWnd参数为第一步创建的采集窗口句柄, nState 当前Capture的状态,可取值为CONTROLCALLBACK_PREROLL(等待Capture开始) 和CONTROLCALLBACK_CAPTURING(Capture正在采集),程序要控制Capture的开启和关闭时通过对当前Capture状态返回适当的值,当为CONTROLCALLBACK_PREROLL是返回TRUE则代表要开启Capture的捕捉,返回FALSE代表中止Capture,当为CONTROLCALLBACK_CAPTURING时,返回TRUE表示要继续采集,返回FALSE表示要停止采集。

2、BOOL capSetCallbackOnError(hwnd, fpProc );此宏用于设置当Capture采集过程中出错的时候反馈给程序处理的回调函数,回调函数原型为:

LRESULT CALLBACK capErrorCallback(  HWND hWnd,    int nID,      LPCSTR lpsz );

hWnd参数为第一步创建的采集窗口句柄,nID 为当前出错的错误ID标识,lpsz 代表出错原因的一个文本描述内容

3、BOOL capSetCallbackOnFrame(hwnd, fpProc ); 此宏用于设置当Capture采集过程中每采集到一帧图像的时候,反馈给程序处理的回调函数,回调函数原型为:

LRESULT (CALLBACK* CAPVIDEOCALLBACK) (HWND hWnd, LPVIDEOHDR lpVHdr);

hWnd参数为第一步创建的采集窗口句柄,lpVHdr 为采集到的一帧数据,LPVIDEOHDR 结构体定义如下:

typedef struct videohdr_tag {
    LPBYTE      lpData;        //指向采集到的数据buffer
    DWORD       dwBufferLength; //buffer的长度
    DWORD       dwBytesUsed; //buffer实际使用的Byte数
    DWORD       dwTimeCaptured; //从开始采集到当前帧采集时经过的毫秒数
    DWORD       dwUser; //用户通过 capSetUserData 设定的自定义参数
    DWORD       dwFlags; //当前帧的标识,可取如下值
    /*
    VHDR_DONE        Done bit 
    VHDR_PREPARED    Set if this header has been prepared  
    VHDR_INQUEUE    Reserved for driver  
    VHDR_KEYFRAME    Key Frame  
    */
    DWORD_PTR   dwReserved[4];  //保留给驱动使用的空间
} VIDEOHDR, NEAR *PVIDEOHDR, FAR * LPVIDEOHDR;

4、BOOL capSetCallbackOnStatus(hwnd, fpProc ); 此宏用于设置监控Capture状态改变的回调函数,函数原型为:

LRESULT CALLBACK capStatusCallback(  HWND hWnd,    int nID,      LPCSTR lpsz );

hWnd参数为第一步创建的采集窗口句柄,nID 定义的状态消息值,关于当前更新的状态的一个文本描述。

5、BOOL capSetCallbackOnVideoStream(hwnd, fpProc ); 此宏设置当Capture采集到一个Video buffer(其实也是一帧数据)数据后反馈给程序的功能函数,函数原型为:

LRESULT CALLBACK capVideoStreamCallback(  HWND hWnd,  LPVIDEOHDR lpVHdr  );

hWnd参数为第一步创建的采集窗口句柄,lpVHdr 为采集到的一帧数据,具体意义可以参看第三个

6、BOOL capSetCallbackOnWaveStream(hwnd, fpProc );此宏设置当采集到一个Audio音频数据buffer时,反馈给程序的函数,函数原型为:

LRESULT CALLBACK capWaveStreamCallback(  HWND hWnd,  LPWAVEHDR lpWHdr );

hWnd参数为第一步创建的采集窗口句柄,lpWHdr 为采集到Audio音频数据,其结构体定义如下:

typedef struct { 
    LPSTR      lpData; //指向采集的音频数据buffer
    DWORD      dwBufferLength; //buffer长度
    DWORD      dwBytesRecorded; //采集到的数据Byte数
    DWORD_PTR  dwUser; //用户通过capSetUserDate自定义的数据
    DWORD      dwFlags; //
    DWORD      dwLoops; //播放时长
    struct wavehdr_tag * lpNext; 
    DWORD_PTR reserved; //保留字段
} WAVEHDR;

7、BOOL capSetCallbackOnYield(hwnd, fpProc ); 此宏用于设置一个回调函数当每采集一帧图像时反馈给程序,回调函数原型为:

LRESULT CALLBACK capYieldCallback(  HWND hWnd  );

hWnd参数为第一步创建的采集窗口句柄

三、capGetDriverDescription 获得当前可用的Capture设备驱动的版本信息。我们可以通过此函数枚举当前系统中可用的驱动。方法如下:

BOOL VFWAPI capGetDriverDescription(  
    WORD wDriverIndex,//要获取的设备驱动的索引值,取值范围为0-9   
    LPSTR lpszName, //指向保存获取到的设备名字buffer    
    INT cbName,//设备名字buffer的长度           
    LPSTR lpszVer,//指向保存获取到的设备版本信息buffer        
    INT cbVer //设备版本信息buffer长度          
);

我们可以通过循环枚举索引值为 0-9 时函数的返回值,如果返回为真则此索引对应的设备存在,并可以获得设备的描述信息。

四、capDriverConnect 连接指定索引值的设备驱动

五、配置Capture采集参数,可设置参数所需函数如下(注意要先连接后配置参数):

1、BOOL capCaptureSetSetup(hwnd,   psCapParms,   wSize ); 用于设置视频流采集过程的配置参数。hwnd 采集窗口句柄, psCapParms 为配置结构体 CAPTUREPARMS 其结构体定义如下,wSize 为psCapParms结构体的大小:

typedef struct { 
    DWORD dwRequestMicroSecPerFrame;//请求的帧率,默认为66667,即每秒15帧。
    BOOL  fMakeUserHitOKToCapture; //如果为TRUE,将显示一个对话框帮助用户快速地进行捕捉设置,默认为false
    UINT  wPercentDropForError; //在捕捉过程中允许弃帧的最大百分比
    BOOL  fYield; //如果为TRUE,将产生一个后台线程来进行视频捕捉
    DWORD dwIndexSize; //表示AVI文件最大的索引入口数
    UINT  wChunkGranularity; //以字节为单位表示AVI文件的大小
    BOOL  fUsingDOSMemory; //未使用
    UINT  wNumVideoRequested; //分配视频缓冲区的最大数量
    BOOL  fCaptureAudio; //为TRUE,表示音频被捕捉,默认值依赖于安装的音频设备
    UINT  wNumAudioRequested; //表示分配的音频缓冲区的最大数量
    UINT  vKeyAbort; //表示终止捕捉的虚拟键
    BOOL  fAbortLeftMouse; //为TRUE,表示单击鼠标左键停止捕捉
    BOOL  fAbortRightMouse; //为TRUE,表示单击鼠标右键停止捕捉
    BOOL  fLimitEnabled; //为TRUE,表示设置捕捉时间限制
    UINT  wTimeLimit; //以秒为单位设置捕捉的超时时间
    BOOL  fMCIControl; //为TRUE,控制MCI(媒体设备接口)兼容的视频源
    BOOL  fStepMCIDevice; //为TRUE,使用MCI设备使用步进帧进行捕捉,为FALSE,使用MCI设备进行时时捕捉,如果fMCIControl成员为FALSE,该成员被忽略
    DWORD dwMCIStartTime; //以毫秒为单位标识MCI设备视频捕捉序列的起始位置,如果fMCIControl成员为FALSE,该成员被忽略
    DWORD dwMCIStopTime; //以毫秒为单位标识MCI设备视频捕捉序列的停止位置,如果fMCIControl成员为FALSE,该成员被忽略
    BOOL  fStepCaptureAt2x; //为TRUE,捕捉的视频帧使用两个分辨率,它可以使用软件在某个分辨率的基础上改写像素,将其该为高清晰度的图像
    UINT  wStepCaptureAverageFrames; //在捕捉时每帧图像使用的时间大小
    DWORD dwAudioBufferSize; //音频缓冲区大小
    BOOL  fDisableWriteCache;//未使用 
    UINT  AVStreamMaster; //确定在写入AVI文件时,音频流是否控制时钟
} CAPTUREPARMS;

2、BOOL capSetVideoFormat( hwnd,  psVideoFormat,    wSize );  设置Video每帧图像的格式,hwnd为采集窗口句柄,psVideoFormat为 BITMAPINFO 结构体,wSize为BITMAPINFO 结构体的大小。

typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER bmiHeader; //位图图像格式
    RGBQUAD bmiColors[1]; //调色板,
} BITMAPINFO;

typedef struct tagBITMAPINFOHEADER{
    DWORD  biSize;  //BITMAPINFOHEADER 结构体的大小
    LONG   biWidth; //图像的宽度,按像素
    LONG   biHeight; //图像的高度,按像素
    WORD   biPlanes; //设备的面数,必须设置为1
    WORD   biBitCount; //每个像素所包含的bit数
    DWORD  biCompression; //图像的压缩格式
    DWORD  biSizeImage; //图像的大小
    LONG   biXPelsPerMeter; //图像的水平分辨率,单位为每米的像素个数
    LONG   biYPelsPerMeter; //图像的垂直分辨率,单位为每米的像素个数
    DWORD  biClrUsed; //图像所用到的颜色数,为0则为biBitCount对应的数目
    DWORD  biClrImportant; //图像中重要的颜色数,为0则所有颜色都重要
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

3、BOOL capSetAudioFormat(  hwnd,  psAudioFormat,    wSize); 设置采集的音频数据格式 hwnd为采集窗口句柄,psAudioFormat为 WAVEFORMATEX 或 PCMWAVEFORMAT 结构体,wSize为psAudioFormat 结构体的大小。

typedef struct { 
    WORD  wFormatTag;  //音频数据格式
    WORD  nChannels; //音频的声道数,为1为单声道,2为立体声
    DWORD nSamplesPerSec; //每秒的采样频率
    DWORD nAvgBytesPerSec; //每秒的数据传输频率
    WORD  nBlockAlign; //一个块的大小,采样bit的字节数乘以声道数
    WORD  wBitsPerSample; //每个采样数据的比特数
    WORD  cbSize; //一般为0
} WAVEFORMATEX;

typedef struct { 
    WORD  wFormatTag; //音频数据格式
    WORD  nChannels; //音频的声道数,为1为单声道,2为立体声
    DWORD nSamplesPerSec;  //每秒的采样频率
    DWORD nAvgBytesPerSec;  //每秒的数据传输频率
    WORD  nBlockAlign; //一个块的大小,采样bit的字节数乘以声道数
} WAVEFORMAT;

typedef struct { 
    WAVEFORMAT wf; 
    WORD       wBitsPerSample; //每个采样数据的比特数
} PCMWAVEFORMAT;

4、BOOL capSetScrollPos(  hwnd,    lpP );设置视频帧的客户区卷轴位置  hwnd 为采集窗口句柄,lpP 为卷轴位置的指针

上面4中参数设备函数,都有相应的参数获取函数,在编写程序时,我们可以先获取设备原先设定的参数,然后在修改我们关心的参数在进行设置,相应的获取函数只要将对应函数的Set换成Get就可以了。

六、预览配置,在设置好预览后我们运行程序就能在我们capCreateCaptureWindow 中指定的窗口中预览到采集到视频数据了。

1、BOOL capPreviewRate( hwnd,    wMS );  设置预览时的采集频率, hwnd为采集窗口句柄,wMS为设定的频率

2、BOOL capPreviewScale(  hwnd,    f ); 设置预览时图像是否 可伸缩,即根据显示窗口大小显示,hwnd为采集窗口句柄,f 为BOOL值,为true代表图像可伸缩

3、BOOL capPreview(  hwnd,    f      );设置是否使用预览模式,hwnd为采集窗口句柄,f为bool值,为true则使用预览模式

七、一帧图像的显示。如果想要处理每一帧采集到的图像,那么程序必须调用 capSetCallbackOnVideoStream或capSetCallbackOnFrame设置回调函数。在设置的回调函数中将图像数据发送给图像显示的窗口,在这里要注意的是,我们在设置参数的时候在capSetVideoFormat函数中设置的图像压缩格式如果为BI_RGB那么采集的图像就为RGB数据,RGB的位数为设定的biBitCount。在显示窗口中进行如下操作:

1、用GetDC获得显示窗口的显示设备句柄

CDC* pDC = ShowWnd->GetDC();

2、创建窗口显示设备句柄的兼容句柄

CDC* pDC = ShowWnd->GetDC();
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);

3、创建一个设备相关的bitmap

CBitmap bmp;
bmp.CreateCompatibleBitmap(pDC, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight);
CBitmap *pOldBmp = MemDC.SelectObject(&bmp);

4、将采集到的位图数据发送给设备,然后将其刷在屏幕上

::SetDIBitsToDevice(MemDC.GetSafeHdc(),0, 0, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight, 0, 0, 0, 
    bmpInfo.bmiHeader.biHeight, VideoDate, &bmpInfo, DIB_RGB_COLORS);
pDC->BitBlt(0, 0, WndRect.Width()-6, WndRect.Height(), &MemDC, 0, 0, SRCCOPY);

八、拍照功能实现。其实所谓的拍照只是将采集到的一帧图像数据写入一个文件中,只是在图像数据前还要加上一些图像格式和参数的说明参数。具体实现如下:

1、设置图像文件参数说明结构体 BITMAPFILEHEADER , 和 图像格式信息结构体 BITMAPINFOHEADER

typedef struct tagBITMAPFILEHEADER { 
    WORD    bfType;        //图像的类型,必须为BM即 0x4d42即十进制的19778
    DWORD   bfSize;     //图像文件的大小 即 图像数据大小 + 54(两个参数说明结构体的大小)
    WORD    bfReserved1; //保留字段必须为0
    WORD    bfReserved2; //保留字段必须为0
    DWORD   bfOffBits; //从文件开始到 图像数据的偏移 
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
    DWORD  biSize; //结构体的大小
    LONG   biWidth; //图像的宽度
    LONG   biHeight; //图像的高度
    WORD   biPlanes; //图像目标设备的面数,必须为1
    WORD   biBitCount; //每个像素的bit数
    DWORD  biCompression; //图像压缩格式
    DWORD  biSizeImage; //图像数据大小
    LONG   biXPelsPerMeter; //图像的水平分辨率
    LONG   biYPelsPerMeter; //图像的垂直分辨率
    DWORD  biClrUsed; //图像所使用到的颜色数
    DWORD  biClrImportant; //图像中重要的颜色数
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

注意这两个结构体的参数一定要根据你采集图像前设置的图像格式来设置,或者直接用当时设置的数据。设置好这两个结构体后就将他们写入文件,然后将采集到的图像数据也写入文件就形成了一张照片了。

CFile filePic(PicPath_one, CFile::modeCreate|CFile::modeWrite);//存放报警图片
filePic.Write(pBmpfilehd,14);
filePic.Write(&m_BmpInfo.bmiHeader, 40);
filePic.Write(m_pImageTmp->lpData, m_pImageTmp->dwBytesUsed);

九、采集结束,关闭设备。这个操作 可以通过capSetCallbackOnCapControl设置的回调函数实现,也可以直接调用 capCaptureStop、capCaptureAbort停止采集,然后再将原先设置的功能回调函数给关闭,即将函数指针传递NULL值。然后capDriverDisconnect断开和设备的连接。

源程序下载

笔者自己编写的一个测试源程序 http://pan.baidu.com/share/link?shareid=190628&uk=2735225556 中下载 VideoPlay–视频语音采集完整版.rar

本文,笔者耗时几个小时认真编写,如果有什么错误,欢迎讨论。如若转载请标明出处 www.xzben.com

from:http://xzben.com/window-%E4%B8%8B-vfw-%E8%A7%86%E9%A2%91%E9%87%87%E9%9B%86%E4%B8%8E%E6%98%BE%E7%A4%BA/

Window 下 VFW 视频采集与显示的更多相关文章

  1. 基于Camera Link和PCIe DMA的多通道视频采集和显示系统

    基于Camera Link和PCIe DMA的多通道视频采集和显示系统 在主机端PCIe驱动的控制和调度下,视频采集与显示系统可以同时完成对多个Camera Link接口视频采集以及Camera Li ...

  2. dsp下基于双循环缓冲队列的视频采集和显示记录

    对最近在设计的视频采集和显示缓冲机制做一个记录,以便以后使用. 视频采集和显示缓冲机制,其实是参考了Linux下v4L2的驱动机制,其采用输入多缓冲frame,输出多缓冲的切换机制.简单的就是ping ...

  3. 基于PCIe的多路视频采集与显示子系统

    基于PCIe的多路视频采集与显示子系统 1        概述 视频采集与显示子系统可以实时采集多路视频信号,并存储到视频采集队列中,借助高效的硬实时视频帧出入队列管理和PCIe C2H DMA引擎, ...

  4. 嵌入式LINUX环境下视频采集知识

    V4L2是Linux环境下开发视频采集设备驱动程序的一套规范(API),它为驱动程序的编写提供统一的接口,并将所有的视频采集设备的驱动程序都纳入其的管理之中.V4L2不仅给驱动程序编写者带来极大的方便 ...

  5. 用DirectShow实现视频采集-流程构建

    DirectShow作为DirectX的一个子集,它为用户提供了强大.方便的多媒体开接口,并且它拥有直接操作硬件的能力,这使得它的效率远胜于用GDI等图形方式编写的多媒体程序.前面一篇文章已经对Dir ...

  6. 基于PCIe DMA的8通道视频采集&显示IP,兼容V4L2

    基于PCIe DMA的8通道视频采集&显示IP,兼容V4L2 Video Capture&Display IP for V4L2 在主机端视频设备内核驱动V4L2 的控制和调度下,Vi ...

  7. 基于FPGA的LCD+CMOS视频采集显示使用小结

    基于FPGA的LCD+CMOS视频采集显示 液晶显示器采用扫描模式,RGB888 电源采用:+5V供电 usb供电有时候会出现供电不足的问题 显示器接口有两种选择:16bit或24bit  分别对应 ...

  8. 每天进步一点点------入门视频采集与处理(显示YUV数据)

    做视频采集与处理,自然少不了要学会分析YUV数据.因为从采集的角度来说,一般的视频采集芯片输出的码流一般都是YUV数据流的形式,而从视频处理(例如H.264.MPEG视频编解码)的角度来说,也是在原始 ...

  9. 入门视频采集与处理(学会分析YUV数据)

    做视频采集与处理,自然少不了要学会分析YUV数据.因为从采集的角度来说,一般的视频采集芯片输出的码流一般都是YUV数据流的形式,而从视频处理(例如H.264.MPEG视频编解码)的角度来说,也是在原始 ...

随机推荐

  1. CSS 的命名和书写

    CSS书写顺序 1.位置属性(position, top, right, z-index, display, float等) 2.大小(width, height, padding, margin) ...

  2. Git PHP提交

    做了个小的DEMO,可以查看: https://github.com/feixiang/webgit.git 这几天一直在郁闷的事情. Git在shell里面执行得好好的,apache运行用户也改成了 ...

  3. 2013 ACM-ICPC长沙赛区全国邀请赛—Special equations

    ……但是没仔细看,直接跳过了 这题直接枚举就可以过了 ;}

  4. Android 调节当前Activity的屏幕亮度

    调节的关键代码: WindowManager.LayoutParams layoutParams = getWindow().getAttributes(); layoutParams.screenB ...

  5. Project Euler 98:Anagramic squares 重排平方数

    Anagramic squares By replacing each of the letters in the word CARE with 1, 2, 9, and 6 respectively ...

  6. 用JUnit4进行单元测试

    转载:http://tonl.iteye.com/blog/1948869 参考: http://thihy.iteye.com/blog/1771826 http://developer.51cto ...

  7. Xamarin.Android 入门之:Listview和adapter

    一.引言 不管开发什么软件,列表的使用是必不可少的,而本章我们将学习如何使用Xamarin去实现它,以及如何使用自定义适配器.关于xamarin中listview的基础和适配器可以查看官网https: ...

  8. 深度分析Java的ClassLoader机制(源码级别)

    写在前面:Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoa ...

  9. [Linux 命令]df -h

    查看目前磁盘空间和使用情况 以更易读的方式显示

  10. 简化PHP开发的10个工具

    本文介绍了可以帮助简化 PHP 开发的11个项目,包括框架,类库,工具,代码. 1. CakePHP Development Framework CakePHP 是一个 PHP 的快速开发框架.它提供 ...