各位高手如何用程序实现将实时媒体流保存下来! ( 积分: 200 )

  • 主题发起人 主题发起人 chenbing
  • 开始时间 开始时间
C

chenbing

Unregistered / Unconfirmed
GUEST, unregistred user!
如何用程序实现将实时媒体流保存下来,然后保存成媒体文件以后播放。
例如保存网络电视的实时数据,以后观看而不是在线观看。
 
如何用程序实现将实时媒体流保存下来,然后保存成媒体文件以后播放。
例如保存网络电视的实时数据,以后观看而不是在线观看。
 
用时光平移实现。
 
供参考

http://www.delphibbs.com/delphibbs/dispq.asp?LID=116436
标题: 想知道视频铺捉、实时压缩的技术细节。 (200分)
------------------------------转自网络
目 录
一. 视频捕获快速入门 2
二.基本的捕获设置 3
1.设置捕获速度: 3
2.设置终止捕获 4
3.捕获的时间限制 4
三.关于捕获窗口 4
1.创建一个AVICAP捕获窗口 5
2.将一个捕获窗口连接至捕获设备 5
3. 父窗口与子窗口的交互 5
4.捕获窗口的状态 6
四.视频捕获驱动和音频驱动 6
1.视频捕获驱动的性能: 6
2.视频对话框: 6
3.PREVIEW 和 OVERLAY模式: 7
4.视频格式 7
5.视频捕获设置 7
6.声频格式 8
五.使用视频捕获 8
1.创建捕获窗口(CREATING A CAPTURE WINDOW) 8
2.连接到捕获驱动(CONNECTING TO A CAPTURE DRIVER) 9
3.列举所有已安装的捕获驱动(ENUMERATING INSTALLED CAPTURE DRIVERS) 9
4.得到捕获驱动的性能(OBTAINING THE CAPABILITIES OF A CAPTURE DRIVER) 9
5.得到捕获窗口的状态(OBTAINING THE STATUS OF A CAPTURE WINDOW) 10
6.显示对话框设置视频特征(DISPLAYING DIALOG BOXES TO SET VIDEO CHARACTERISTICS) 10
7.得到和设置视频格式(OBTAINING AND SETTING THE VIDEO FORMAT) 11
8. 预览视频(PREVIEWING VIDEO) 12
9.将视频设置为OVERLAY模式(ENABLING VIDEO OVERLAY) 12
10.命名捕获文件(NAMING THE CAPTURE FILE) 12
11.格式化声频捕获(FORMATTING AUDIO CAPTURE) 12
12.改变视频捕获设置(CHANGING A VIDEO CAPTURE SETTING) 13
13.捕获数据(CAPTURING DATA) 13
14.增加一个信息块(ADDING AN INFORMATION CHUNK) 14
15.在程序中加入一个回调函数(ADDING CALLBACK FUNCTIONS TO AN APPLICATION) 14
16.创建一个状态回调函数(CREATING A STATUS CALLBACK FUNCTION) 16
17.创建一个错误回调函数( CREATING AN ERROR CALLBACK FUNCTION) 17
18.创建一个框架回调函数(CREATING A FRAME CALLBACK FUNCTION) 18
六.将四个标准对话框改成函数调用形式 18
AUDIOFORMAT对话框 19
VIDEOFORMAT对话框 19
VIDEOSOURCE对话框 20
VIDEO COMPRESSION对话框 20


前 言
视频捕获是指由专用的视频采集卡捕获声频和视频信息,然后将其进行数据化处理,再经过软件的压缩进行处理,这时就可对这些数据进行保存、回放、传输等各种操作。
Windows专门提供了Video for Windows来对视频处理进行支持,提供的接口可以被大多数的视频采集卡支持,并有多种视频压缩驱动供选择(当然视频压缩可以自己开发),采集卡支持摄像头,TV等多种输入。

一. 视频捕获快速入门
视频捕捉将一个视频流和音频流数字化, 然后存储在硬盘或其他存储介质上.
一个AVICap视窗口句柄描述了声频与视频流的细节, 这样就使你的应用程序从AVI文件格式, 声频视频缓冲管理, 低层声频视频驱动访问等等解脱出来, AVICap为应用程序提供了一个灵活的介面, 你可以仅仅使用如下几行代码就可以将视频捕捉加入你的程序:
hWndC = capCreateCaptureWindow ( "My Own Capture Window",
WS_CHILD | WS_VISIBLE , 0, 0, 160, 120, hwndParent, nID);
SendMessage (hWndC, WM_CAP_DRIVER_CONNECT, 0 /* wIndex */, 0L);
SendMessage (hWndC, WM_CAP_SEQUENCE, 0, 0L);
一个宏其实也是使用SendMessage, 只不过提供给程序一个更易读的代码而已, 下面的这些示例就是使用宏的方法将视频捕捉加入程序:
hWndC = capCreateCaptureWindow ( "My Own Capture Window",
WS_CHILD | WS_VISIBLE , 0, 0, 160, 120, hwndParent, nID);
capDriverConnect (hWndC, 0);
capCaptureSequence (hWndC);


当你创建了一个AVICap类的捕捉窗口并将它连接到一个视频驱动时, 此捕捉窗口即可以开始捕捉数据,你的程序可以简单的发送WM_CAP_SEQUENCE 消息(或者使用capCaptureSequence 宏)来开始捕捉.
如果是缺省的设置, WM_CAP_SEQUENCE会开始捕捉视频音频流到CAPTURE.AVI文件中, 直到下面的某一事件发生为止:
用户按下了ESC键或者一个鼠标键
你的应用程序终止或异常中断捕捉操作
磁盘已满

在一个应用程序里, 你可以发送WM_CAP_STOP消息来终止捕捉数据(或者使用capCaptureStop宏), 你也可以发送WM_CAP_ABORT消息(或者使用capCaptureAbort宏)来终止.

二.基本的捕获设置
基本的捕获设置包括:设置捕获速度(每秒捕获多少帧),是否同时捕获声频,捕获缓冲,允许最大丢失多少帧,是否使用DOS内存,以及用键盘的哪个键或鼠标的哪个键来终止捕获等等。这些基本的设置都可以使用CAPTUREPARAMS结构来描述,你可以使用capCaptureGetSetup宏来得到当前的设置,然后改变此结构的成员变量,再使用capCaptureSetSetup宏设置新的设置。
例如:
1.设置捕获速度:
捕捉速度是指捕捉任务每秒钟捕获的帧数, 你可以发送WM_CAP_GET_SEQUENCE_SETUP消息(或者使用capCaptureGetSetup宏)来得到当前的捕捉速度, 当前的捕捉速度保存在CAPTUREPARAMS结构的dwRequestMicroSecPerFrame成员变量中, 你可以通过设置此变量来改变当前设置, 单位是每毫秒连续的帧数, 你可以发送WM_CAP_SET_SEQUENCE_SETUP消息(或者使用capCaptureSetSetup宏), dwRequestMicroSecPerFrame的值是66667, 相当于每秒15帧.

2.设置终止捕获
你可以允许用户按下某键或某组合键或者鼠标的左右键来终止一个捕获任务, 如果是实时的捕获, 则捕获的文件将会被丢弃;
如果是单步捕获, 在终止之前所捕获的内容将会被保存.
你可以通过发送WM_CAP_GETQUENCE_SETUP消息(或者使用capCaptureGetSetup宏)来得到当前的设置, 当前的按键设置保存在CAPTUREPARAMS的vKeyAbort成员中, 当前的鼠标设置保存在fAbortLeftMouse和fAbortRightMouse成员中. 你可以设置新的按键或按键组合, 或者鼠标左右键, 当你修改的CAPTUREPARAMS后,应该发送WM_CAP_SET_SEQUENCE_SETUP消息来进行更新(或者使用capCaptureSetSetup宏). 缺省的按键是VK_ESCAPE. 你必须在指定按键之前使用RegisterHotKey函数, 鼠标缺省的值是fAbortLeftMouse和fAbortRightMouse都为TRUE.

3.捕获的时间限制
CAPTUREPARAMS结构中的fLimitEnabled指示是否有时间限度, wTimeLimit指示最大的持续时间, 单位为秒.
得到fLimitEnabled和wTimeLimit的值可以发送WM_CAP_GET_SEQUENCE_SETUP消息(或使用capCatureGetSetup宏), 当设置了这些成员变量后, 应该发送消息WM_CAP_SET_SEQUENCE_SETUP消息(或capCaptureSetSetup宏)来更新CAPTUREPARAMS结构.

三.关于捕获窗口
在捕获之前必须创建一个捕获窗口(capture window),在发送消息或使用宏的过程中都需要使用此窗口。
1.创建一个AVICap捕获窗口
你可以使用capCreateCaptureWindow函数来创建一个AVICap捕获窗口, 此函数将会返回一个句柄, 此句柄以后在发送消息时要用.
你可以在一个程序里创建一个或多个捕获窗口, 然后给每一个窗口连接不同的捕获设置.

2.将一个捕获窗口连接至捕获设备
你可以动态的在一个捕获窗口与一个捕获设备之前连接或断接, 你可以发送WM_CAP_DRIVER_CONNECT消息来使一个捕获窗口与一个捕获设备连接或关联. 当连接上以后, 你就可以通过捕获窗口向捕获设备发送各种消息.
如果你的系统里装有多个捕获设备, 你可以在发送WM_CAP_DRIVER_CONNECT消息时用wParam参数指定使用哪一个, 此参数是登记在SYSTEM.INI文件的[drivers]一节里的列表中的某一项, 0为第一个.
你可以使用capGetDriverDescription函数来得到已安装的捕获设备的名称及版本, 这样你的程序就可以列举所有已安装的捕获设备和驱动, 这样用户就可以选择其中的一个来与你的捕获窗口连接.
你可以发送WM_CAP_DRIVER_GET_NAME消息(或capDriverGetName宏)来得到连接到捕获窗口的捕获设备的名称, 得到版本发送WM_CAP_DRIVER_GET_VERSION消息(或capDriverGetVersion宏)
你可以发送WM_CAP_DRIVER_DISCONNECT消息(或capDriverDisconnect宏)来断接.

3. 父窗口与子窗口的交互
一些象WM_PALETTECHANGED和WM_QUERYNEWPALETTE的系统级消息只能发送到顶级窗口或OVERLAPPED窗口, 如果一个捕获窗口是子窗口,就必须通过父窗口转送.
同样的, 如果父窗口的尺寸改变了, 它就需要通知捕获窗口, 相反地, 如果捕获窗口的尺寸改变了, 捕获窗口就需要发送消息给父窗口, 一个简单的方法就是始终保持捕获窗口的尺寸与视频流的尺寸一致, 并随时将尺寸的改变通知父窗口.

4.捕获窗口的状态
你可以发送WM_CAP_GET_STATUS消息(或capGetStatus宏)来得到当前捕获窗口的状态, 得到的是一个CAPSTATUS结构的拷贝, 它包含图片的尺寸, 卷轴的当前位置, overlay和preview是否已设置.
因为CAPSTATUS信息是动态的, 你的程序应该只要捕获的视频流的尺寸或格式可能发生了改变就应该进行刷新(例如: 显示了捕获设备的视频格式以后).
改变捕获窗口的尺寸并不影响实际的捕获的视频流的尺寸, 视频捕获设备的格式对话框捕获频流的尺寸.

四.视频捕获驱动和音频驱动
1.视频捕获驱动的性能:
你可以通过发送WM_CAP_DRIVER_GET_CAPS消息(或者capDriverGetCaps宏)来得到当前连接的视频驱动的硬件性能. 得到的信息保存在CAPDRIVERCAPS结构中.

2.视频对话框:
每一个视频驱动能够提供四个对话框来控制视频捕获和数字化处理, 定义压缩品质等, 这些对话框都定义在视频捕获驱动中.
Video Source对话框用于控制选择视频来源, 此对话框列举了此视频捕获卡连接的所有视频源(典型的例如:SVHS和合成输入), 并提供了改变色调, 对比度, 饱和度. 如果视频驱动支持此对话框, 你就可以显示并更新它, 使用WM_CAP_DLG_VIDEOSOURCE消息(或capDlgVideoSource宏).
Video Format对话框定义视频帧的尺寸以及精度, 视频捕获卡的压缩设置. 如果卡支持的话, 可以发送消息WM_CAP_DLG_VIDEOFORMAT消息或(capDlgVideoFormat宏).
Video Display对话框控制在视频捕获期间在显示器上的显示, 此控制不会影响视频数字数据, 但是他们可能会影响数字信号的表现形式, 例如: 如果捕获设备支持overlay, 可能允许改变色调和饱和度, 关键色彩 或者overlay队列. 如果卡支持, 你可以发送WM_CAP_DLG_VIDEODISPLAY消息(或者使用capDlgVideoDisplay宏).
Video Compression对话框控制压缩品质, 如果卡支持, 发送消息WM_CAP_DLG_VIDEOCOMPRESSION(或capDlgVideoCompression宏).

3.Preview 和 Overlay模式:
一个视频捕获驱动对进入的视频流有两种工作模式: Preview模式和overlay模式, 如果一个捕获驱动能够执行两种方法, 用户可以在其中选择一种.
Preview模式把从捕获硬件传来的数据送入系统内存并使用图形设备介面(GDI)将数字化帧显示在捕获窗口内. 应用程序可以在父窗口失去焦点时减缓显示速度, 当重新又得到焦点后加快显示速度, 此种模式要占用大量CPU时间.
有三种消息控制Preview操作:
WM_CAP_SET_PREIVEW消息(capPreview宏)允许或禁止preview模式
WM_CAP_SET_PREVIEWRATE(capPreviewRate宏)当帧在preview模式显示时设置速度.
WM_CAP_SET_SCALE(capPreviewScale宏)允许或禁止preview视频的缩放比例.
当preview和scaling同时使用, 捕获的视频帧将会根据捕获窗口的尺寸自动缩放, 允许preview模式会自动关闭overlay模式.
overlay模式是一个硬件函数它将数据送入捕获缓冲区中因而不占用CPU资源. 你可以发送消息WM_CAP_SET_OVERLAY(或capOverlay宏)给捕获窗口来启用或终止overlay模式, 允许overlay模式会自动禁止preview模式.
你同时也可以在preview模式或overlay模式里发送WM_CAP_SET_SCROLL消息(或capSetScrollPos宏)来设置视频帧的客户区卷轴位置.

4.视频格式
你可以通过发送WM_CAP_GET_VIDEOFORMAT消息(或capGetVideoFormat和capGetVideoFormatSize宏)来得到视频格式的结构或结构的尺寸. 你可以通过发送CAP_SET_VIDEOFORMAT消息(或capSetVideoFormat宏)来设置视频格式.

5.视频捕获设置
CAPTUREPARMS结构包含了对视频捕获流的控制参数, 你可以完成以下这些任务:
指定帧数
指定分配多少视频缓冲
允许或禁止声频捕获
指定捕获的时间间隔
指定在捕获的过程中是否使用MCI设置(VCR或者videodisc)
指定终止流的键盘或鼠标
specify the type of video averaging applied during capture.

得到:WM_CAP_GET_SEQUENCE_SETUP消息(或capCaptureGetSetup宏)
设置:WM_CAP_SET_SEQUENCE_SETUP消息(或capCaptureSetSetup宏)

6.声频格式
你可以通过发送WM_CAP_GET_AUDIOFORMAT消息(或capGetAudioFormat宏和capGetAudioFormatSize宏)来得到当前捕获音频数据的格式或尺寸格式。缺省的声频格式是:单声道、8位、11kHz PCM。 当你使用WM_CAP_GET_AUDIOFORMAT时,总是使用WAVEFORMATEX结构。
设置发送消息WM_CAP_SET_AUDIOFORMAT消息(或capSetAudioFormat宏),可以传送WAVEFORMAT,WAVEFORMATEX,PCMWAVEFORMAT结构指针。
五.使用视频捕获
1.创建捕获窗口(Creating a Capture Window)
hWndC = capCreateCaptureWindow (
(LPSTR) "My Capture Window", // window name if pop-up
WS_CHILD | WS_VISIBLE, // window style
0, 0, 160, 120, // window position and dimensions
(HWND) hwndParent,
(int) nID /* child ID */);


2.连接到捕获驱动(Connecting to a Capture Driver)
下面的例子是将MSVIDEO驱动连接到句柄为hWndC的捕获窗口, 然后调用capDriverDisconnect宏来断接.
fOK = SendMessage (hWndC, WM_CAP_DRIVER_CONNECT, 0, 0L);

//
// Or, use the macro to connect to the MSVIDEO driver:
// fOK = capDriverConnect(hWndC, 0);

//
// Place code to set up and capture video here.
//
capDriverDisconnect (hWndC);


3.列举所有已安装的捕获驱动(Enumerating Installed Capture Drivers)
下面的例子使用capGetDriverDescription函数得到已安装的捕获驱动的名称及版本:
char szDeviceName[80];
char szDeviceVersion[80];

for (wIndex = 0;
wIndex < 10;
wIndex++)
{
if (capGetDriverDescription (wIndex, szDeviceName,
sizeof (szDeviceName), szDeviceVersion,
sizeof (szDeviceVersion))
{
// Append name to list of installed capture drivers
// and then
let the user select a driver to use.
}
}

4.得到捕获驱动的性能(Obtaining the Capabilities of a Capture Driver)
发送WM_CAP_DRIVER_GET_CAPS消息可以得到捕获驱动的性能,并保存入一个CAPDRIVERCAPS结构.每当程序连接一个新的捕获驱动到一个捕获窗口时, 就应该更新CAPDRIVERCAPS结构. 下面的程序举例说明了如何使用capDriverGetCaps宏来得到捕获驱动的性能:

CAPDRIVERCAPS CapDrvCaps;

SendMessage (hWndC, WM_CAP_DRIVER_GET_CAPS,
sizeof (CAPDRIVERCAPS), (LONG) (LPVOID) &amp;CapDrvCaps);

// Or, use the macro to retrieve the driver capabilities.
// capDriverGetCaps(hWndC, &amp;CapDrvCaps, sizeof (CAPDRIVERCAPS));

5.得到捕获窗口的状态(Obtaining the Status of a Capture Window)
下面的例子使用SetWindowPos函数使捕获窗口与进来的视频流尺寸保持一致, 视频流的基本信息是使用capGetStatus宏得到的, 保存在CAPSTATUS结构中.

CAPSTATUS CapStatus;
capGetStatus(hWndC, &amp;CapStatus, sizeof (CAPSTATUS));

SetWindowPos(hWndC, NULL, 0, 0, CapStatus.uiImageWidth,
CapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE);


6.显示对话框设置视频特征(Displaying Dialog Boxes to Set Video Characteristics)
每个视频捕获卡一般能提供三个不同的对话框用于控制视频捕获及数字化处理. 下面的例子说明如何显示这些对话框, 在显示这些对话框之前,使用了capDriverGetCaps宏来检查CAPDRIVERCAPS结构, 以检测该卡是否有显示这些对话框:

CAPDRIVERCAPS CapDrvCaps;

capDriverGetCaps(hWndC, &amp;CapDrvCaps, sizeof (CAPDRIVERCAPS));


// Video source dialog box.
if (CapDriverCaps.fHasDlgVideoSource)
capDlgVideoSource(hWndC);


// Video format dialog box.
if (CapDriverCaps.fHasDlgVideoFormat)
{
capDlgVideoFormat(hWndC);

// Are there new image dimensions?
capGetStatus(hWndC, &amp;CapStatus, sizeof (CAPSTATUS));
// If so, notify the parent of a size change.
}

// Video display dialog box.
if (CapDriverCaps.fHasDlgVideoDisplay)
capDlgVideoDisplay(hWndC);


7.得到和设置视频格式(Obtaining and Setting the Video Format)
BITMAPINFO结构的长度既适应于标准的也适应于压缩的数据格式, 所有程序必须总是询问此结构的尺寸以便在得到当前的视频格式之前分配内存. 下面的例子就是使用capGetVideoFormatSize宏来得到缓冲区尺寸并调用capGetVideoFormat宏来得到当前的视频格式.

LPBITMAPINFO lpbi;
DWORD dwSize;
dwSize = capGetVideoFormatSize(hWndC);
lpbi = GlobalAllocPtr (GHND, dwSize);
capGetVideoFormat(hWndC, lpbi, dwSize);


// Access the video format and then
free the allocated memory.

程序可以使用capSetVideoFormat宏(或WM_CAP_SET_VIDEOFORMAT消息)发送一个BITMAPINFO头结构给捕获窗口, 因为视频格式是设备细节, 你的程序应该检查返回值以便确定此格式是否已被接受.

8. 预览视频(Previewing Video)
下面的例子使用capPreviewRate宏来设置每66毫秒显示一帧, 并使用capPreview宏将它放置在捕获窗口里.

capPreviewRate(hWndC, 66);
// rate, in milliseconds
capPreview(hWndC, TRUE);
// starts preview
// Preview
capPreview(hWnd, FALSE);
// disables preview

9.将视频设置为overlay模式(Enabling Video Overlay)
下面的例子: capDriverGetCaps宏确定此捕获卡是否有overlay功能, 如果有就使用宏来设置它

CAPDRIVERCAPS CapDrvCaps;

capDriverGetCaps(hWndC, &amp;CapDrvCaps, sizeof (CAPDRIVERCAPS));


if (CapDrvCaps.fHasOverlay)
capOverlay(hWndC, TRUE);

10.命名捕获文件(Naming the Capture File)
下面的例子: 使用capFileSetCaptureFile宏来指定预备文件名为:MYCAP.AVI, capFileAlloc宏预先指定它的大小为5M.

char szCaptureFile[] = &quot;MYCAP.AVI&quot;;
capFileSetCaptureFile( hWndC, szCaptureFile);

capFileAlloc( hWndC, (1024L * 1024L * 5));


11.格式化声频捕获(Formatting Audio Capture)
下面的例子使用capSetAudioFormat来设置声频格式为:11kHz, PCM 8位, 立体声

WAVEFORMATEX wfex;
wfex.wFormatTag = WAVE_FORMAT_PCM;
wfex.nChannels = 2;
// Use stereo
wfex.nSamplesPerSec = 11025;
wfex.nAvgBytesPerSec = 22050;
wfex.nBlockAlign = 2;
wfex.wBitsPerSample = 8;
wfex.cbSize = 0;

capSetAudioFormat(hWndC, &amp;wfex, sizeof(WAVEFORMATEX));


12.改变视频捕获设置(Changing a Video Capture Setting)
下面的例子使用capCaptureGetSetup和capCaptureSetSetup宏得将捕获帧数从缺省的15帧改成每秒10帧.

CAPTUREPARMS CaptureParms;
float FramesPerSec = 10.0;

capCaptureGetSetup(hWndC, &amp;CaptureParms, sizeof(CAPTUREPARMS));

CaptureParms.dwRequestMicroSecPerFrame = (DWORD) (1.0e6 /FramesPerSec);
capCaptureSetSetup(hWndC, &amp;CaptureParms, sizeof (CAPTUREPARMS));


13.捕获数据(Capturing Data)
下面的例子使用capCaptureSequence宏来开始捕获视频并使用capFileSaveAs宏来将捕获的数据拷贝至NEWFILE.AVI文件中.

char szNewName[] = &quot;NEWFILE.AVI&quot;;
// Set up the capture operation.
capCaptureSequence(hWndC);

// Capture.
capFileSaveAs(hWndC, szNewName);

14.增加一个信息块(Adding an Information Chunk)
如果你需要在你的程序捕获的声频和视频数据中加入你的其他信息, 你可以创建一个信息块并将它们插入捕获文件中, 信息块可以包含一些典型的信息, 例如:版权信息,视频来源, 外部定位信息等. 下面的例子使用capFileSetInfoChunk宏来插入一个信息块, 里面包含了一个SMPTE的时间代码.

// This example assumes the application controls
// the video source for preroll and postroll.
CAPINFOCHUNK cic;
// .
// .
// .
cic.fccInfoID = infotypeSMPTE_TIME;
cic.lpData = &quot;00:20:30:12&quot;;

cic.cbData = strlen (cic.lpData) + 1;
capFileSetInfoChunk (hwndC, &amp;cic);


15.在程序中加入一个回调函数(Adding Callback Functions to an Application)
一个程序可以为捕获窗口登记一个回调函数以便在以下的这些情况下通知程序.

状态改变
错误发生
视频框架和声频缓冲区变得可用
程序应用在捕获视频流的过程中接收

下面的例子创建一个捕获窗口并登记状态,错误,视频流和框架回调函数在消息处理对列中, 也包括了一个终止回调函数的说明.

case WM_CREATE:
{
char achDeviceName[80] ;

char achDeviceVersion[100] ;

char achBuffer[100] ;

WORD wDriverCount = 0 ;

WORD wIndex ;

WORD wError ;

HMENU hMenu ;


// Create a capture window using the capCreateCaptureWindow macro.
ghWndCap = capCreateCaptureWindow((LPSTR)&quot;Capture Window&quot;,
WS_CHILD | WS_VISIBLE, 0, 0, 160, 120, (HWND) hWnd, (int) 0);


// Register the error callback function using the
// capSetCallbackOnError macro.
capSetCallbackOnError(ghWndCap, fpErrorCallback);


// Register the status callback function using the
// capSetCallbackOnStatus macro.
capSetCallbackOnStatus(ghWndCap, fpStatusCallback);


// Register the video-stream callback function using the
// capSetCallbackOnVideoStream macro.
capSetCallbackOnVideoStream(ghWndCap, fpVideoCallback);


// Register the frame callback function using the
// capSetCallbackOnFrame macro.
capSetCallbackOnFrame(ghWndCap, fpFrameCallback);


// Connect to a capture driver

break;

}
case WM_CLOSE:
{
// Use the capSetCallbackOnFrame macro to
// disable the frame callback. Similar calls exist for the other
// callback functions.

capSetCallbackOnFrame(hWndC, NULL);


break;

}

16.创建一个状态回调函数(Creating a Status Callback Function)
下面的例子是创建一个简单的状态回调函数,登记此回调函数使用capSetCallbackOnStatus宏.

// StatusCallbackProc: status callback function
// hWnd: capture window handle
// nID: status code for the current status
// lpStatusText: status text string for the current status
//
LRESULT PASCAL StatusCallbackProc(HWND hWnd, int nID,
LPSTR lpStatusText)
{
if (!ghWndMain)
return FALSE;


if (nID == 0) { // Clear old status messages.
SetWindowText(ghWndMain, (LPSTR) gachAppName);

return (LRESULT) TRUE;

}
// Show the status ID and status text...
wsprintf(gachBuffer, &quot;Status# %d: %s&quot;, nID, lpStatusText);


SetWindowText(ghWndMain, (LPSTR)gachBuffer);

return (LRESULT) TRUE;

}

17.创建一个错误回调函数( Creating an Error Callback Function)
下面的例子是创建一个简单的错误回调函数,登记此回调函数使用capsetCallbackOnError宏:

// ErrorCallbackProc: error callback function
// hWnd: capture window handle
// nErrID: error code for the encountered error
// lpErrorText: error text string for the encountered error
//
LRESULT PASCAL ErrorCallbackProc(HWND hWnd, int nErrID,
LPSTR lpErrorText)
{
if (!ghWndMain)
return FALSE;


if (nErrID == 0) // Starting a new major function.
return TRUE;
// Clear out old errors.

// Show the error identifier and text.
wsprintf(gachBuffer, &quot;Error# %d&quot;, nErrID);


MessageBox(hWnd, lpErrorText, gachBuffer,
MB_OK | MB_ICONEXCLAMATION);


return (LRESULT) TRUE;

}

18.创建一个框架回调函数(Creating a Frame Callback Function)
登记此回调函数使用capSetCallbackOnFrame宏:

// FrameCallbackProc: frame callback function
// hWnd: capture window handle
// lpVHdr: pointer to struct containing captured
// frame information
//
LRESULT PASCAL FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)
{
if (!ghWndMain)
return FALSE;


wsprintf(gachBuffer, &quot;Preview frame# %ld &quot;, gdwFrameNum++);

SetWindowText(ghWndMain, (LPSTR)gachBuffer);

return (LRESULT) TRUE ;

}



六.将四个标准对话框改成函数调用形式
系统提供了四个标准的对话框:AudioFormat, VideoFormat, VideoSource, Video Compression,但有时程序希望通过函数控制它们,而不是使用系统提供的那个单一的对话框,此时就应该使用函数调用的方法:

AudioFormat对话框
可以通过使用capSetAudioFormat来实现,此时要使用WAVEFORMATEX结构。
例如:改成PCM格式,立体声,16声道,12.05kHz,则:
WAVEFORMATEX audioFormat;
// 确定宽度
acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT,&amp;dwSize);
dwSize = max (dwSize, capGetAudioFormatSize (m_hwCapCapturing));
// 设置参数
audioFormat.wFormatTag = WAVE_FORMAT_PCM;
audioFormat.nChannels = 2;
audioFormat.nSamplesPerSec = 120500;
audioFormat.wBitsPerSample =16;
audioFormat.nBlockAlign = nBitsPerSample * nChannels / 8;
audioFormat.nAvgBytesPerSec =
audioFormat.nBlockAlign * nSamplesPerSec;
// 更新
capSetAudioFormat(ghCapWnd,&amp;audioFormat,dwSize);
VideoFormat对话框
可以通过使用capSetVideoFormat来实现,此时要使用BITMAPINFOHEADER结构。
例如:设置图片大小为RGB24位岁,大小为230X160
BITMAPINFOHEADER bi;
DWORD dwSize,dw;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = 320;
// 起作用
bi.biHeight = 160;
// 起作用
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 176;
bi.biYPelsPerMeter = 144;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
dwSize = bi.biSize + ((bi.biBitCount > 8 || bi.biClrUsed) ? (bi.biClrUsed * sizeof(PALETTEENTRY)) : (2 ^ bi.biBitCount * sizeof(PALETTEENTRY)));
dw = capSetVideoFormat(m_hwCapCapturing, &amp;bi, dwSize);
VideoSource对话框
没有找到现成的方法,但视频捕获卡提供的CD里面有一个动态链接库可以实现。
Video Compression对话框
可以通过使用ICOpen,ICInfo等函数联合起来,得到当前系统里面的视频压缩驱动的列表,并可选择其一,MSDN里面有一个程序示范了此用户,程序名叫:ICWalk。

注:此文绝大部分是我译自MSDN的资料。
 
用Delphi编写视频广播和点播程序



关键字: 2002年 视频广播

用Delphi编写视频广播和点播程序

陈经韬



本文目录

一:前言

二:Mpeg1文件格式

三:网络数据广播

四:服务端程序编写

五:客户端程序编写

六:后记



一:前言

本文将介绍如何用Delphi编写视频广播(DVB)和点播(VOD)程序.内容将涉及网络数据广播和Mpeg1文件格式.希望通过本文大家可以知道流行的流媒体是怎么一回事.

我们先来了解一下什么叫DVB和VOD.DVB就是视频广播,表现形式为服务端程序打开一个视频文件播放,同一个网络内的客户端程序都可以接收到,节目的播放位置由服务端控制,客户端是被动接收的,不能改变播放位置.而VOD则刚好相反,大量的视频文件放在服务端电脑上,由客户端选择播放.播放过程中可以随时改变播放进度.无论是DVB还是VOD,它们在本质上都有共同点:就是把数据还原为影像.

关于网络播放,大概有以下几种形式:一种是基于文件共享方式,这种方式我以前已经写过一篇<<用Delphi在局域网中实现网上影院>>,这里不再详述.另外一种是基于文件切割,我在<<谈Delphi编程中文件格式的应用>>也有提到,就是把一个大的文件切割成多个小文件广播出去.这两种方法都有它的致命弱点.在第一种方法里,如果文件是在光盘里面的,那么几个人一起读的话就把电脑弄死了.即使是在硬盘里面,人数一多都会导致系统变慢.第二种方法,先不说接收文件存盘再播放有延时问题,而且多个文件之间无缝切换也是很麻烦的事情.如果客户端是无盘工作站,则完全不能用.

解决上面的问题就是用流来实现.一种方法就是服务端以流的方式读取文件(关于流的操作技巧,请参阅我以前写的<<谈Delphi编程中“流”的应用>>一文),然后在数据前面添加形如以下的数据:

var

strHeader:String;

begin


strHeader:= 'HTTP/1.1 200 OK' + EOL ;

strHeader:= strHeader + 'Server: Lovejingtao Web Server/1.0.0' + EOL ;

strHeader:= strHeader + 'Content-Type: application/octet-stream' + EOL ;

strHeader:= strHeader + 'Accept-Ranges: bytes' + EOL ;

strHeader:= strHeader + 'ETag: &quot;05fa3dd93a9bd1:889&quot;
'+ EOL ;

strHeader:= strHeader + 'Content-Length: 1000' + EOL ;

strHeader:= strHeader + EOL ;

......

end;


客户端用系统本身带的Mediaplay来接收,接收地址象以下这种格式:http://192.168.0.1:8080.说白了,就是伪装Mediaplay Server服务端而已.

第二种方法就是本文要讲述的方法.其实这个方法与上面的是一样的.不同的是上面的格式之类是系统集成的,我们无法改变.而这种方法需要自己进行控制.

DVB的原理如下:服务端读取文件,然后向整个网络广播或者组播,客户端接收到数据后,放到内存中播放即可.点播的核心其实也是一样,也需要把数据转化为影像.我们把这个将数据还原为影像的过程称为&quot;解码&quot;,大家经常听到的&quot;解码器&quot;就是这个意思,当然,解码器是有硬解码和软解码之分的.市面上有各种各样的解码器,RealPlay公司的,微软的等等.实际上很多播放软件都是调用别人的解码库而已,所以我们说豪杰公司的<<超级解霸>>很牛,因为它的解码器是自己写的.可能你也可以写一个功能跟它相近的播放器,但是我想解码器未必是可以人人都自己做的.顺便提一句:Delphi带的那个Mediaplay控件是基于MCI方式的,在核心层面来说实际上只不过是调用Windows本身的API而已.

本文用的解码器是基于微软的DirectxShow开发包,由那个Memfile改写而成.已经封装成DLL形式,这样一来就可以方便的在VC,Delphi,Vb等程序中调用.共提供了23个函数给大家调用.声明如下:

unit Mpeg1DecodeDll;

interface

uses Windows;

const DllName='Mpeg1Decode.dll';

function Mpeg1Decode_Init():bool;stdcall;external DllName;

function Mpeg1Decode_OpenVideo(hWnd:Thandle;r:TRECT;iSeek:DWORD=0):bool;stdcall;external DllName;

function Mpeg1Decode_PlayVideo():Bool;stdcall;external DllName;

function Mpeg1Decode_PauseVideo():Bool;stdcall;external DllName;

function Mpeg1Decode_ResumeVideo():Bool;stdcall;external DllName;

function Mpeg1Decode_StopVideo():Bool;stdcall;external DllName;

function Mpeg1Decode_CloseVideo():Bool;stdcall;external DllName;

function Mpeg1Decode_UnInit():Bool;stdcall;external DllName;



function Mpeg1Decode_SendBuf(buf:pbyte;lbufsize:longlong):DWORD;stdcall;external DllName;



function Mpeg1Decode_ReSizeWindowRect(r:TRect):Bool;stdcall;external DllName;

function Mpeg1Decode_SetHWND(hwnd:Thandle):Bool;stdcall;external DllName;

function Mpeg1Decode_SetNewHWND(hwnd:Thandle):Bool;stdcall;external DllName;

function Mpeg1Decode_SetWindowRect(r:Trect):Bool;stdcall;external DllName;

function Mpeg1Decode_FullScreenVideo(bFull:BOOL):Bool;stdcall;external DllName;



procedure Mpeg1Decode_About();stdcall;external DllName;

procedure Mpeg1Decode_PopupAudioPropDlg(hwnd:Thandle);stdcall;external DllName;

function Mpeg1Decode_GetMinimalVolume:DWORD;stdcall;external DllName;

function Mpeg1Decode_GetMaximalVolume:DWORD;stdcall;external DllName;

function Mpeg1Decode_GetCurrentVolume:DWORD;stdcall;external DllName;

procedure Mpeg1Decode_SetCurrentVolume(dwValue:DWORD);stdcall;external DllName;



function Mpeg1Decode_GetMpegFilePacketHead(FileName:Pchar;dwPackStartPos:PDWORD):Bool;stdcall;external DllName;

function Mpeg1Decode_CheckIsDatFormat(FileName:Pchar):Bool;stdcall;external DllName;

function Mpeg1Decode_CheckIsMpegFile(FileName:Pchar):Bool;stdcall;external DllName;



implementation

end.




在程序中使用这个Dll,首先调用Mpeg1Decode_Init来完成初始化COM对象,分配内存等工作.然后调用Mpeg1Decode_OpenVideo(hWnd:Thandle;r:TRECT;iSeek:DWORD=0)来指定播放的窗口句柄和大小尺寸.最后一个参数是要跳过的字节数.然后用Mpeg1Decode_SendBuf(buf:pbyte;lbufsize:longlong)来将Mpeg1数据发送到缓冲区,调用Mpeg1Decode_PlayVideo进行播放.播放结束后一次调用Mpeg1Decode_StopVideo,Mpeg1Decode_CloseVideo和Mpeg1Decode_UnInit进行释放资源之类的工作.



二:Mpeg1文件格式

Mpeg1文件分为两种,一种是纯Mpeg文件,另外一种是Dat格式的VCD文件.实际上,Dat文件也是Mpeg1文件,Dat的文件由文件头和数据两部分组成,从文件头位置开始,以2352字节为一个数据包.而纯Mpeg文件是没有那个头的,它是一系列的2324字节的数据包组成.将Dat文件转换为Mpeg文件很简单,从那个头开始,每个数据包只取前面的2324字节即可.我们下面用Delphi来写一个类好了.

{******************About the Unit********************

版本:1.0

名称:Mpeg1DataFormat.pas

功能:动态转换VCD数据到纯MPEG数据

依赖:Mpeg1DecodeDll

作者与版权所有:陈经韬,lovejingtao@21cn.com,http://lovejingtao.126.com

最后修改日期:2002,6,15

修改历史:无

****************************************************** }

unit Mpeg1DataFormat;



interface

uses

Windows,SysUtils,Classes,Mpeg1DecodeDll;

const

DatFramSize=2352;//Dat文件每个包大小

MpgFramSize=2324;//Mpg文件每个包大小

iOddCount=DatFramSize-MpgFramSize;//两种包相差值

type

TMpeg1DataFormat = class(TObject)

private

hFile:THandle;

FileName:String;

PackHead:integer;

IsVcd:Boolean;

function CheckIsVcd(const FileName:String):Boolean;
//判断是否是DAT文件

function GetPacketHead(const FileName:String):integer;
//取得文件头位置

function GetFileSize(const FileName: string): LongInt;//取文件大小

protected

public

FileSize:LongInt;

iFramCout:integer;

function GetMpegDateToByte(Buffer:PByte;const iFramSeek:integer=0):Boolean;

Constructor Create(const StrFilename:String);

Destructor Destroy;
override;

end;






implementation

function TMpeg1DataFormat.GetFileSize(const FileName: string): LongInt;

var

SearchRec: TSearchRec;

begin


if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then


Result := SearchRec.Size

else


Result :=0;

end;


constructor

TMpeg1DataFormat.Create(const StrFilename:String);

begin


inherited Create;

FileName:= StrFilename;

FileSize:= GetFileSize(FileName);

PackHead:= GetPacketHead(FileName);

IsVcd:= CheckIsVcd(FileName);
//建立读文件句柄

hFile:= CreateFile(Pchar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);


if hFile=INVALID_HANDLE_VALUE then


Exit;

if IsVcd then


iFramCout:=(FileSize-PackHead) div DatFramSize

else
iFramCout:=(FileSize) div MpgFramSize

end;



destructor TMpeg1DataFormat.Destroy;

begin


CloseHandle(hFile);


inherited;

end;





function TMpeg1DataFormat.CheckIsVcd(const FileName:String):Boolean;

begin


Result:=Mpeg1Decode_CheckIsDatFormat(Pchar(FileName));

end;




function TMpeg1DataFormat.GetPacketHead(const FileName:String):integer;


var

i:PDWORD;

begin


new(i);

Mpeg1Decode_GetMpegFilePacketHead(Pchar(FileName),i);

Result:=i^;

Dispose(i);

end;




function TMpeg1DataFormat.GetMpegDateToByte(Buffer:PByte;const iFramSeek:integer =0):Boolean;

var

re:BOOL;

dwBytesRead:DWORD;

FramSize:integer;


begin


Result:=False;

if not(FileExists(FileName)) then


Exit;


if IsVcd then


FramSize:=DatFramSize

else
FramSize:=MpgFramSize;


if FileSize<(PackHead+FramSize*iFramSeek) then


Exit;



If FileSeek(hFile,PackHead+FramSize*iFramSeek,soFrombegin
ning)=-1 then


Exit;

re :=ReadFile(hFile,Buffer^,MpgFramSize,dwBytesRead,nil);

if dwBytesRead<>MpgFramSize then


Exit;




If re<>TRUE then


Exit;//读文件失败的时候

Result:= True;


end;


end.




三:网络数据广播

首先要指出的是,广播是只有UDP协议才支持的,TCP是无法进行广播的.广播分为两种,一种是directed broadcast.<<谈Delphi编程中“流”的应用>>实际应用之三:利用流制作自己的OICQ中已经说的很详细了,下面摘录下来给大家:UDP协议还有一个很大的好处就是可以广播,就是说处于一个网段的都可以接收到信息而不必指定具体的IP地址。网段一般分A、B、C三类, 1~126.XXX.XXX.XXX

(A类网):广播地址为XXX.255.255.255 128~191.XXX.XXX.XXX(B类网):广播地址为XXX.XXX.255.255

192~254.XXX.XXX.XXX(C类网):广播地址为XXX.XXX.XXX.255

比如说三台计算机192.168.0.1、192.168.0.10、192.168.0.18,发送信息时只要指定IP地址为192.168.0.255就可以实现广播了。下面给出一个转换IP为广播IP的函数,快拿去完善自己的OICQ吧^-^.

Function Trun_ip(S:string):string;

var s1,s2,s3,ss,sss,Head:string;


n,m:integer;

begin


sss:=S;

n:=pos('.',s);


s1:=copy(s,1,n);

m:=length(s1);


delete(s,1,m);


Head:= copy(s1,1,(length(s1)-1));


n:=pos('.',s);


s2:=copy(s,1,n);

m:=length(s2);


delete(s,1,m);

n:=pos('.',s);

s3:=copy(s,1,n);

m:=length(s3);

delete(s,1,m);

ss:=sss;

if strtoint(Head) in [1..126] then


ss:=s1+'255.255.255';

//1~126.255.255.255(A类网)

if strtoint(Head) in [128..191] then


ss:=s1+s2+'255.255';//128~191.XXX.255.255(B类网)

if strtoint(Head) in [192..254] then


ss:=s1+s2+s3+'255';
//192~254.XXX.XXX.255(C类网)

Result:=ss;


end;



另外一种是limited broadcast,广播地址是255.255.255.255.它的好处是只要在同一子网中的主机,就可以收到这种广播,而不必非要在统一逻辑子网中.例如,如果你的地址是192.168.0.1,那么这种广播,地址是192.202.30.17的主机也能收到.本文将使用此种广播. 在Delphi中使用UDP广播可以用控件,也可以API.本文中将完全使用API来实现.</FONT>
 
后退
顶部