点对点视频会议程序VideoNet开发例解
Easy Digital Camera Connection
利用VC++实现局域网实时视频传输
VC++6.0平台下利用DirectShow技术实现DV设备的视频处理
CAviCap and CFrameGrabber - wrappers for AVICap Window
Tracking an object from a live video input
http://tag.csdn.net/tag/directshow/2.html CSDN上很多例子
http://search.csdn.net/search/DirectShow/4/dev/
在VC中调用DirectShow全屏播放视频
Directshow(SDK)学习笔记九_视频捕捉
用DirectShow实现QQ的音视频聊天功能
用.NET 2.0压缩/解压功能处理大型数据
DirectShowNet Library
http://directshownet.sourceforge.net/
室外5公里的802.11B无线远程连接
用Delphi开发视频聊天软件
基于VFW的视频应用程序开发
Visual C++视频会议开发技术与实例(附光盘)
我去年做了一段时间局域网上的实时视频传输,如果是服务器对多用户进行实时的视频传输,以我的经验:
1.服务器软件用多线程:
(1)主线程:读出(看你的图象具体怎么上PC机了)一帧视频数据,送给拆分线程。
(2)拆分线程:接到一帧视频,开始拆包、做帧标记、打序列号,送给发送线程。
(3)发送线程:用RTP socket把封装好的数据包发给客户端。此socket是点对多点、单向
有根方式的组播套接字,实际上是基于UDP派生的,但他用到了RTP和RTCP(实时传输
协议和实时传输控制协议),如果你传输的不是实时数据,直接用UDP就行了。
2.客户端软件结构一般用多线程,线程间用事件对象进行同步,而共享数据区用临界区对象进
行同步。
(1)主线程:接收网络数据包的线程,优先级最高,可以尽量保证不丢数据,也采用RTP协
议,用网络事件来触发。主线程接收到视频数据包后,将数据放入一个链表中,然后
用事件对象触发组装线程。
(2)组装线程:从链表中读出数据包,要进行帧确认、排序等工作,当把一帧图象的所有
包都取到时,再调用组装模块(可以是一个函数),将这些数据包组装成完整的一个
帧,然后送到解压线程。
(3)若干解压播放线程。主要考虑到如果你客户端软件想同时播放多画面,比如说4画面图
象,就要用4个解压播放线程。
(4)至于图象存储,要看你的客户需要怎么存了,如果是手工存当然不需要单开线程,如果
是规定定时存或在某个事件发生时自动存盘,就需要单开一个线程,由定时器到时消息
或此事件发生来触发。
将每帧视频数据按某种编码格式编码(如mpg4, h263等)压缩通过rtp传输到另一机子上,用rtcp控制调整传输速率。
你可以先用udp传一下看看。
http://www.dlc.sjtu.edu.cn/newspost/newsdetail.asp?newsno=1546
目前,互联网上用于多媒体数据流的技术协议有实时传输协议RTP、实时传输控制协议RTCP、实时流协议RTSP、资源预订协议RSVP等。RTP主要处理一对一、一对多的多媒体数据流传输任务,可以按照UDP、TCP及ATM等协议传输数据,负责提供时间信息和控制流同步。RTCP的作用则在于与RTP一起解决非常关键的流量控制和拥塞控制问题,两种协议配合使用可以大大提高传输效率,所以是在线实时数据传送的主要方式。RTSP协议定义一对多程序有效地通过IP网络传送多媒体数据的方式,在体系结构中位于RTP和RTCP之上。与HTTP协议相比,RTSP的特点在于客户端和服务器端都可以发出请求,是一种双向的传输协议。另外,由于多媒体数据的流式传输对网络延时非常敏感,所以需要预先为流媒体的传输预留一部分网络带宽,这项功能可以通过资源预订协议RSVP获得实现。
文件格式
与传输协议的相对统一不同,流媒体文件的格式现在相当繁杂,如视频文件就有MPEG、AVI、RM、DVI、QuickTime等多种格式,其它还有ASF、应用于Flash动画的SWF等格式。不过总的来说,在互联网上得到广泛应用的还是微软的ASF、RealNetworks的Real system和苹果公司的QuickTime。ASF在微软深厚的市场应用和技术实力支持下,不仅可以以任何编码方式进行压缩和解压缩,还适应于所有的底层网络传输协议,所以推出后迅速成为主要的流媒体文件格式。RealSystem则包括了RealAudio、RealVideo、RealFlash和RealPresentation四种文件格式,分别用于制作不同类型的流媒体文件。RealSystem是几乎每个网络用户都曾经接触过的流媒体技术,性能表现非常稳定,大量的流媒体服务网站都采用了这项技术,中国网络用户看到的多数在线影视节目也都是RealSystem格式的。QuickTime和RealSystem一样,也是出现较早的流媒体文件格式之一,目前已经发展到支持实时和快速启动两种类型的数据流,应用方式相当灵活。这三种流媒体技术都有厂商提供的成熟的支持系统,系统中一般包括流媒体文件的制作、发布、客户端播放等多种工具,便于技术的推广应用。
不过互联网上的主流技术不一定能充分满足广电行业的需求,一些专家在谈及广电领域的流媒体应用时,就更为看好MPEG-4。MPEG-4的突出优点是具有交互功能,可以使影视节目具有类似网站和视频游戏的交互性,而这正是新一代广电系统的发展方向之一。再加上无线视频传输通常采用MPEG-4格式,所以MPEG-4的发展潜力是相当可观的。
include <dshow.h>
#include <streams.h>
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
class CMemStream : public CAsyncStream
{
public:
CMemStream(LPBYTE pbData, LONGLONG llLength, DWORD dwKBPerSec = INFINITE) :
m_pbData(pbData),
m_llLength(llLength),
m_rllPosition(0),
m_dwKBPerSec(dwKBPerSec)
{
m_dwTimeStart = timeGetTime();
clearPlayBuf();
m_packetsize = 1024*32;
m_packetnum = 32; //1M
m_playbufsize = m_packetsize * m_packetnum;
m_playbuf = new BYTE[m_playbufsize];
m_llLength = 4000000000000;
m_fileheadsize = m_packetsize * 10;
hMutex = CreateMutex(NULL,FALSE,"protect buf");
m_bheadover = FALSE;
m_breadnum = 0;
hLogFile = fopen("log.txt","w+");
}
~CMemStream()
{
clearPlayBuf();
delete m_playbuf;
fclose(hLogFile);
}
HRESULT Read(PBYTE pbBuffer,
DWORD dwBytesToRead,
BOOL bAlign,
LPDWORD pdwBytesRead)
{
CAutoLock lck(&m_csLock);
DWORD dwReadLength;
dwReadLength = dwBytesToRead;
//当没有处理完播放头的时候处于等待状态
if(!m_bheadover)
{
while(m_a_fw < m_fileheadsize)
{
fprintf(hLogFile,"Wait Head\n");
Sleep(100);
}
}
m_bheadover = TRUE;
while(CanR()==false)
{
fprintf(hLogFile,"CanR=false");
Sleep(100);
}
fprintf(hLogFile,"\n");
m_rllPosition = m_a_fr % m_playbufsize;
//fprintf(hLogFile,"Read:m_rllPosition=%d\n",m_rllPosition);
//fprintf(hLogFile,"Read:m_wllPosition=%d\n\n",m_wllPosition);
//读取缓冲区数据并播放
WaitForSingleObject(hMutex,INFINITE);
CopyMemory((PVOID)pbBuffer,
(PVOID)(m_playbuf+m_rllPosition),
dwReadLength);
ReleaseMutex(hMutex);
m_a_fr += dwReadLength;
*pdwBytesRead = dwReadLength;
return S_OK;
}
bool Write(PBYTE buf)
{
if(CanW()==false)
{
fprintf(hLogFile,"CanW=false");
return false;
}
fprintf(hLogFile,"\n");
m_wllPosition = m_a_fw % m_playbufsize;
//fprintf(hLogFile,"Write:m_rllPosition=%d\n",m_rllPosition);
//fprintf(hLogFile,"Write:m_wllPosition=%d\n\n",m_wllPosition);
//添加新的数据进缓冲区
WaitForSingleObject(hMutex,INFINITE);
CopyMemory((PVOID)(m_playbuf+m_wllPosition),
(PVOID)buf,
m_packetsize);
ReleaseMutex(hMutex);
m_a_fw += m_packetsize;
return true;
}
ULONG getPacketSize()
{
return m_packetsize;
}
HRESULT SetPointer(LONGLONG llPos)
{
if (llPos < 0 || llPos > m_llLength) {
return S_FALSE;
} else {
//关键,过滤器要对流预读,会有一次跳回0.
//害的我调试了两天,发发牢骚,哈哈~
m_a_fr = llPos;
return S_OK;
}
}
void setFileHeadSize(ULONG fileheadsize)
{
if(fileheadsize > m_packetsize *10)
{
m_fileheadsize = fileheadsize;
}else{
m_fileheadsize = m_packetsize*10;
}
}
LONGLONG Size(LONGLONG *pSizeAvailable)
{
LONGLONG llCurrentAvailable =
Int32x32To64((timeGetTime() - m_dwTimeStart),m_dwKBPerSec);
*pSizeAvailable = min(m_llLength, llCurrentAvailable);
return m_llLength;
}
void clearPlayBuf()
{
m_rllPosition = 0;
m_wllPosition = 0;
m_a_fr = 0;
m_a_fw = 0;
}
bool CanR()
{
if ((m_a_fw - m_a_fr) >= m_packetsize)
{
return true;
}else{
return false;
}
}
bool CanW()
{
if ((m_a_fw - m_a_fr) <= (m_playbufsize - m_packetsize))
{
return true;
}else{
return false;
}
}
DWORD Alignment()
{
return 1;
}
void Lock()
{
m_csLock.Lock();
}
void Unlock()
{
m_csLock.Unlock();
}
private:
CCritSec m_csLock;
const PBYTE m_pbData;
LONGLONG m_llLength;
DWORD m_dwKBPerSec;
DWORD m_dwTimeStart;
PBYTE m_playbuf; //自定义缓冲区
ULONG m_playbufsize; //自定义缓冲区大小
ULONG m_packetsize; //一帧的大小
ULONG m_packetnum; //缓冲区中帧的数量
ULONG m_wllPosition; //相对写地址
ULONG m_rllPosition; //相对读地址
LONGLONG m_a_fr; //绝对读地址
LONGLONG m_a_fw; //绝对写地址
HANDLE hMutex; //关键段保护
ULONG m_fileheadsize; //要预读的文件头大小
BOOL m_bheadover; //时候读完头
ULONG m_breadnum; //流指针跳回0的次数
FILE *hLogFile;
};
class CMemReader : public CAsyncReader
{
public:
STDMETHODIMP Register()
{
return S_OK;
}
STDMETHODIMP Unregister()
{
return S_OK;
}
CMemReader(CMemStream *pStream, CMediaType *pmt, HRESULT *phr) :
CAsyncReader(NAME("Mem Reader"), NULL, pStream, phr)
{
m_mt = *pmt;
}
};
当前,在Windows 平台下开发视频应用程序一般采用两种方式:一种是基于视频采集卡所附带的二次软件开发包SDK(Software development kit)进行。这种方式的优点是应用方便,容易上手,缺点是对硬件的依赖性较强,灵活性差,且功能参差不齐,不能充分满足各种视频应用程序的开发需要;
另一种方式是基于VFW(Video for Windows)进行的。VFW 是Microsoft公司为开发Windows平台下的视频应用程序提供的软件工具包,提供了一系列应用程序编程接口(API),用户可以通过它们很方便地实现视频捕获[1]、视频编辑及视频播放等通用功能,还可利用回调函数开发更复杂的视频应用程序。它的特点是播放视频时不需要专用的硬件设备,而且应用灵活,可以满足视频应用程序开发的需要。Windows操作系统自身就携带了VFW,系统安装时,会自动安装VFW的相关组件。VC++自4.0以来就支持VFW,大大简化了视频应用程序的开发。目前,PC机上多媒体应用程序的视频部分,大都是利用VFW API开发的。
1 VFW 的体系结构
VFW以消息驱动方式对视频设备进行存取,可以很方便地控制设备数据流的工作过程。目前,大多数的视频采集卡驱动程序都支持VFW接口,它主要包括多个动态连接库,通过这些组件间的协调合作,来完成视频的捕获、视频压缩及播放功能。VFW体系结构如图1所示。
1)VICAP.DLL:主要实现视频捕获功能,包含了用于视频捕获的函数,为音像交错AVI (Audio video interleaved)格式文件和视频、音频设备程序提供一个高级接口。
2)MSVIDEO.DLL:能够将视频捕获窗口与获驱动设备连接起来,支持ICM视频编码服务。
3)MCIAVI.DRV:包含MCI(Media control interface)命令解释器,实现回放功能。
4)AVIFILE.DLL:提供对AVI文件的读写操作等文件管理功能。
5)ICM ( Installable compression manager ):即压缩管理器,提供对存储在AVI文件中视频图像数据的压缩、解压缩服务。
6)ACM ( Audio Compression Manager ):即音频压缩管理器,提供实时音频压缩及解压缩功能。
Capture Application |
Playback Application |
Edit Application |
AVICAP.DLL AVICAP |
MSVIDEO.DLL MCIWnd |
ACM |
MCIAVI.DRV MCI Command Interpreter |
MSVIDEO.DLL Video in ICM Channel |
AVIFILE.DLL File / Stream Handler |
MSVIDEO.DLL Drawdib ICM |
Capture Application |
Playback Application |
Edit Application |
AVICAP.DLL AVICAP |
MSVIDEO.DLL MCIWnd |
ACM |
MCIAVI.DRV MCI Command Interpreter |
MSVIDEO.DLL Video in ICM Channel |
AVIFILE.DLL File / Stream Handler |
MSVIDEO.DLL Drawdib ICM |
Capture Application |
Playback Application |
Edit Application |
AVICAP.DLL
AVICAP |
MSVIDEO.DLL
MCIWnd |
ACM |
MCIAVI.DRV
MCI Command Interpreter |
MSVIDEO.DLL
Video in ICM Channel |
AVIFILE.DLL
File / Stream Handler |
MSVIDEO.DLL
Drawdib ICM |
2 视频捕获
视频数据的实时采集,主要通过AVICAP模块中的消息、宏函数、结构以及回调函数来完成。视频捕获的一般过程如下:
2.1 建立捕获窗口
利用AVICAP 组件函数 capCreateCaptureWindow() 建立视频捕获窗口,它是所有捕获工作及设置的基础,其主要功能包括:① 动态地同视频和音频输入器连接或断开;② 设置视频捕获速率;③ 提供视频源、视频格式以及是否采用视频压缩的对话框;④ 设置视频采集的显示模式为Overlay或为Preview; ⑤ 实时获取每一帧视频数据;⑥ 将一视频流和音频流捕获并保存到一个AVI文件中; ⑦ 捕获某一帧数字视频数据,并将单帧图像以DIB格式保存;⑧ 指定捕获数据的文件名,并能将捕获的内容拷贝到另一文件。
2.2 登记回调函数[2]
登记回调函数用来实现用户的一些特殊需要。在以一些实时监控系统或视频会议系统中,需要将数据流在写入磁盘以前就必须加以处理,达到实时功效。应用程序可用捕获窗来登记回调函数,以便及时处理以下情况:捕获窗状态改变、出错、使用视频或音频缓存、放弃控制权等,相应的回调函数分别为 capStatusCallback(), capErrorCallback(), capVideoStreamCallback(), capWaveStreamCallback(),capYieldCallback()。
2.3 获取捕获窗口的缺省设置
通过宏capCaptureGetSetup(hWndCap,&m_Parms,sizeof(m_Parms))来完成。
2.4 设置捕获窗口的相关参数
通过宏capCaptureSetSetup(hWndCap,&m_Parms,sizeof(m_Parms))来完成。
2.5 连接捕获窗口与视频捕获卡
通过宏capDriveConnect(hWndCap,0)来完成。
2.6 获取采集设备的功能和状态
通过宏capDriverGetCaps(hWndCap,&m_CapDrvCap,sizeof(CAPDRIVERCAPS))来获取
视频设备的能力,通过宏capGetStatus(hWndCap,&m_CapStatus,sizeof(m_CapStatus))
来获取视频设备的状态。
2.7 设置捕获窗口显示模式
视频显示有Overlay(叠加)和Preview(预览)两种模式。在叠加模式下,捕获视频数据布展系统资源,显示速度快,视频采集格式为YUV格式,可通过capOverlay(hWndCap,TRUE)来设置;预览模式下要占用系统资源,视频由系统调用GDI函数在捕获窗显示,显示速度慢,它支持RGB视频格式。
2.8 捕获图像到缓存或文件并作相应处理
若要对采集数据进行实时处理,则应利用回调机制,由capSetCallbackOnFrame(hWndCap, FrameCall-
backProc)完成单帧视频采集;由capSetCallbackOnVideoStream(hWndCap, VideoCallbackProc)完成视频流采集。如果要保存采集数据,则可调用capCaptureSequence(hWnd);要指定文件名,可调用capFileSetCap-
ture(hwnd, Filename)。
2.9 终止视频捕获 断开与视频采集设备的连接
调用capCatureStop(hWndCap)停止采集,调用capDriverDisconnect(hWndCap), 断开视频窗口与捕获驱动程序的连接。
3 视频编辑和播放
利用VFW,不仅可以实现视频流的实时采集,还提供了编辑和播放功能,主要通过AVIFILE、ICM、ACM、MCIWnd 等组件之间的协作来完成。
1) AVIFileInit();//初始化;
2) AVIFileOpen(); //打开一个AVI文件并获文件的句柄;
3) AVIFileInfo(); //获取文件的相关信息,如图像的Width和Height等;
4) AVIFileGetStream(); //建立一个指向需要访问的数据流的指针;
5) AVIStreamInfo(); //获取存储数据流信息的AVISTREAMINFO结构;
6) AVIStreamRead(); //读取数据流中的原始数据, 对AVI文件进行所需的编辑处理;
7) AVIStreamRelease(); //释放指向视频流的指针;
8) AVIFileRelease();AVIFileExit(); //释放AVI文件。
若数据是压缩过的,则用AVIStreamGetFrameOpen(),AVIStreamGetFrame()和AVIStreamGetFrameClose()来操作,可以完成对视频流的逐帧分解。
3.2 视频播放
对于实现视频流的播放,VFW提供了MCIWnd窗口类[4],主要用于创建视频播放区,控制并修改MCI窗口当前加载媒体的属性。一个由函数、消息和宏组成的库与MCIWnd相关联,通过它们可以进行AVI文件操作,很方便地使应用程序完成视频播放功能。
1)MCIWndCreate(); //注册MCIWnd窗口类,创建MCIWnd窗口,并指定窗口风格;
2)AVIFileInit(); //初始化;
3) AVIFileOpen(); //打开AVI文件;
4) AVIFileGetStream(); //获得视频流;
5)运用相关函数进行各种播放任务:MCIWndPlay()正向播放AVI文件内容,MCIWndPlayReverse()反向播放,MCIWndResume() 恢复播放,MCIWndPlayPause()暂停播放,MCIWndStop()停止播放等等。
6) AVIStreamRelease(); //释放视频流;
7)AVIFileRease();AVIFileExit(); //断开与AVI文件的连接,释放视频源。
由以上步骤可以看出,视频播放是视频编辑其中的一种操作。