视频缓冲器

lijin177036962 2010-05-14 03:14:45
小弟真的属于技术很菜的.
最近小弟给公司做了一个H.264专用播放器.
它只支持本地播放视频.
但是公司给小弟一个任务.
让小弟可以支持网络播放.
小弟觉得应该加一个缓冲.
但是小弟很菜,
没有一点认知.
各位高手能不能给我一点思路.?
...全文
394 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
afeng124 2010-05-14
  • 打赏
  • 举报
回复
不用太感谢,自己对这个也感兴趣。希望以下的文章能帮到你。


大家知道,在网络上传输音/视频等多媒体信息目前主要有下载和流式传输两种方案。下载的主要缺点是,必须等全部内容传输完毕,然后才能在本地机器打开;而采用流式传输方案,声音、影像或动画等时基媒体由音/视频服务器向用户计算机连续、实时地传送,用户不必等到整个文件全部下载完毕,只需经过几秒或十数秒的启动延时即可进行观看。流式传输广泛应用于Internet上视频点播,以及网络视频监控等领域。
本文从Microsoft DirectShow以及Windows Socket等方面的技术角度出发,阐述一个简单的媒体流式传输、实时观看的解决方案。
关键词:DirectShow Filter Windows Socket
一. 开发原理
(一)介绍一下微软的DirectShow技术
长久以来,多媒体应用一直面临着挑战,包括多媒体大量的数据传输,快速的数据处理要求,音视频流的同步,媒体流的格式转换等等。DirectShow正是微软针对以上问题设计,并且很好地解决了这些问题的一种应用架构。它的设计目标是,隔离数据传输、硬件兼容、流同步等底层处理,使客户能够轻轻松松地创建Windows应用平台上的多媒体应用程序。
为提高数据处理的效率,DirectShow运用了DirectDraw和DirectSound技术。同时,DirectShow还运用了COM组件技术,它的名字叫作Filter。Filter大致可分为三种:Source Filters、Transform Filters、Rendering Filters。Filter传输数据的端口叫作Pin。Pin有Input Pin和Output Pin两种。在应用中,将各种Filter连接起来,也就是将前面Filter(Upstream Filter)的Output Pin与后面Filter(Downstream Filter)的Input Pin连接起来,即组成一个完整的Filter Graph。运行这个Filter Graph,多媒体数据就开始从Source Filters,经过Transform Filters到Rendering Filters流动。DirectX提供了一个非常实用的工具——GraphEdit.exe——用以可视化调试Filter。下面看一下DirectShow大致的处理流程图:

在我们后面的应用实例中,我们自己编写了一个Filter(这是一个Source Filter),用于异步读取网络上出来的Mpeg1数据;紧跟这个Filter后面的是Transform Filters,负责Mpeg1数据的音视频分离(Mpeg1 Stream Splitter)、音视频数据的解码(Mpeg Audio Decoder和Mpeg Video Decoder);然后是音视频流各自的Rendering Filters,一个叫Video Renderer的负责视频的显示,一个叫Default DirectSound Device的负责音频数据的播放。参考图如下:

注:Source Filter的改写参考了DirectX 8.0 SDK的例子代码,它的路径为:samplesMultimediaDirectShow FiltersAsyncMemfile。

(二)介绍一下Windows Socket网络传输技术
我们要处理的数据并不在本地计算机,而是由另外一台视频服务器负责发送出来。那么,我们如何得到这些数据,然后再使用我们的DirectShow Filter进行处理呢?这就用到了Windows Socket技术。
运用Windows Socket技术,能够让两台计算机通过网络建立连接,并且通过定义一些上层协议,实现计算机之间的大量数据传输以及其他一些控制。微软的MFC也提供了两个类:CAsyncSocket和CSocket,用以方便客户使用Socket的特性。CAsyncSocket从较低层次封装了Windows Socket API,并且通过内建一个窗口,实现了适合Windows应用的异步机制。CSocket类从CAsyncSocket中继承而来,更简化了客户对Socket的应用。但是,这两个类均有缺陷,特别是在跨线程使用Socket的时候。
在我们后面的应用实例中,因为处理的数据量较大,我们使用了多线程。在负责数据发送的服务器端,使用专门的线程进行数据的发送;在客户端,使用专门的线程进行数据的接收,并把数据放到缓冲队列中,供DirectShow Filter读取处理。因此,我们自己封装了几个Socket的类:CListenSocket(用于服务器端建立监听客户连接的Socket类)、CWorkerSocket(负责数据传输的Socket基类)。从CWorkerSocket再派生两个类:CMediaSocketServer(用于服务器端数据的发送)和CMediaSocketClient(用于客户端数据的接收)。
我们把连续不断的Mpeg1数据分成一个一个小包的负载数据(取一个PACK的大小,2324字节),加上一定的信息头,在网络两端传输。定义的数据结构为:
// Message header
typedef struct
{
unsigned int nMsgType:8; // Payload type
unsigned int nDataSize:16; // Payload size
} MSG_HEADER, *PMSG_HEADER;
typedef struct
{
CHAR MPEGData[2324];
} MPEG1_PACK, *PMPEG1_PACK;
定义的一些信息头参考如下:
// Payload types
#define DATA_REQUEST 0X00 // Request the remote data
#define DATA_REFUSED 0X01 // Refuse the remote data
#define DATA_MEDIA 0X02 // Media data
#define DISCONNECT_REQUEST 0X03 // Request the remote to shut down
至于网络客户端的接收,首先阻塞地接收一个消息头大小(sizeof(MSG_HEADER))的数据,然后根据nDataSize的值接收相应的负载数据。

(三)介绍一种双缓冲队列技术
网络客户端接收到的Mpeg1数据,必须进行一定量的缓冲,然后才能交给DirectShow解码处理。接着,动态地,一边继续从网络接收数据,一边得到新的数据进行解码回放。这里介绍一种使用的双缓冲队列技术。
大致的工作原理是,建立两个队列,一个是PoolList,空闲的缓冲队列,用以接收存放数据;另一个是DataList,尚未处理的数据缓冲队列,等待处理。网络接收这边:当客户端接收到一个包的数据,从PoolList队列的头拿出一个缓冲,存放数据,然后将这个缓冲加入到DataList的尾部等待DirectShow的Filter读取。DirectShow这边:从DataList队列的头拿出一个缓冲,读取数据,将读完的缓冲加到PoolList的尾部,等待再一次地接收数据。

二. 应用实例
开发环境:Windows 2000,PII 400,Microsoft DirectX SDK 8.0
开发工具:Microsoft Visual C++ 6.0
应用模型:Dialog Based Application
开发步骤:
(1) 服务器端(MediaServer)
服务器端负责Mpeg1数据的发送,供演示用。这里主要的工作是,在对话框初始化函数(OnInitDialog)中建立一个监听Socket。当客户端有连接请求时,CListenSocket中就发出一个自定义消息WM_NEW_SOCKET给主对话框;主对话框响应该消息,用CMediaSocketServer与客户端建立建立连接。
CMediaSocketServer有两个独立的工作者线程,用户可以自定义它们的线程循环体。CMediaSocketServer::ReceivingLoop为接收线程处理,当接收到DATA_REQUEST命令后就开始数据发送;数据发送处理在CMediaSocketServer::SendingLoop中。请参考演示代码,下面我们重点讲一下客户端的DirectShow处理技术。
(2) 客户端(MediaClient)
客户端负责数据传输的是CMediaSocketClient类,接收线程处理体为CmediaSocket Client::ReceivingLoop;CDataAdmin类封装了双缓冲队列对数据的管理。实现细节请参考演示代码。
下面重点讲一下DirectShow是如何将数据进行解码回放的。
l CFilterGraph类封装了Filter以及Filter Graph的创建和控制。
HRESULT hr = CoCreateInstance(CLSID_FilterGraph,
NULL,
CLSCTX_INPROC,
IID_IGraphBuilder,
(void**)&m_pGB);
创建了Filter Graph的一个实例。
hr = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC);
得到了启动、停止这个Filter Graph的控制接口。还有其他控制接口,如控制视频传口的m_pVW、控制音频的m_pBA等。
CMemReader是我们自己编写的一个Filter,通过AddFilter方法将其加入到以上Filter Graph中,如下:
hr = m_pGB->AddFilter(m_pSourceReader, NULL);
最后一步是,得到Source Filter的Output Pin,然后Render这个Pin,完整的Filter Graph就构建成功了。Filter Graph的参考图见上文。
HRESULT hr = m_pGB->Render(m_pSourceReader->GetPin(0));
l 我们编写的这个Filter,相关的源文件有asyncio.cpp、asyncio.h、asyncrdr.cpp、asyncrdr.h和MemFilter.h。前四个是DirectShow SDK提供的基类,我们在MemFilter中派生了自己的两个类CMemStream和CMemReader,以定义我们自己的Filter行为。Filter Graph中有两种基本的数据流模式:推模式(Push Mode)和拉模式(Pull Mode)。推模式情况下,Source Filter主动把数据往下传送;而在拉模式下,需要后面的Filter向Source Filter请求数据。我们的Source Filter就属于拉模式的Filter,它的Output Pin必须实现IAsyncReader接口。数据的流动,主要靠后面的Filter(本例中为Mpeg1 Stream Splitter)调用Source Filter的Output Pin上IAsyncReader接口的Read方法。下面是我们的Filter的Read方法实现,这也是本应用方案的关键部分:
HRESULT Read(PBYTE pbBuffer, DWORD dwBytesToRead,
BOOL bAlign, LPDWORD pdwBytesRead)
{
if (m_pDataList == NULL) return S_FALSE;
CAutoLock lck(&m_csLock);
DWORD dwHaveRead = 0;
PMPEG1_PACK pPack = NULL;
while (dwHaveRead < dwBytesToRead){
if (dwBytesToRead - dwHaveRead >= MPEG1_PACK_SIZE - m_ulPositionInPack) {
// Just copy the whole pack data
pPack = m_pDataList->GetDataBuffer();
if (pPack != NULL) {
CopyMemory((PVOID)(pbBuffer + dwHaveRead),
(PVOID)((PBYTE)(pPack) + m_ulPositionInPack), (SIZE_T)MPEG1_PACK_SIZE - m_ulPositionInPack);
m_pDataList->ReleaseDataBuffer(pPack);
dwHaveRead += MPEG1_PACK_SIZE - m_ulPositionInPack;
m_ulPositionInPack = 0;
}
else if (m_pDataList->IsFlushing()) { return E_FAIL; }
else { Sleep(10); }
}
else {
// Copy part of the pack data
pPack = m_pDataList->PointToDataHead();
if (pPack != NULL) {
m_ulPositionInPack = dwBytesToRead - dwHaveRead;
CopyMemory((PVOID)(pbBuffer + dwHaveRead),
(PVOID)(pPack), (SIZE_T)m_ulPositionInPack);
dwHaveRead += m_ulPositionInPack;
}
else if (m_pDataList->IsFlushing()) {return E_FAIL;}
else {Sleep(10); }
}
}
*pdwBytesRead = dwBytesToRead;
return S_OK;
}
(3) 演示过程
l 运行Builds目录下的MediaServer.exe程序,并选择一个Mpeg1文件(如Demo.mpg);
l 在网络中的另一台计算机(或本机)上运行MediaClient.exe程序,确认启动MediaServer程序的计算机IP地址;
l 然后单击“连接”按钮,再单击“传输”按钮,1-2秒钟后,即可在客户端看到图像,并且界面上有客户端已经接收到数据包个数的动态显示;
l 按“断开”按钮即可将服务器端和客户端的Socket连接断开,停止传输数据。

三. 几点说明
1. 本方案用到了DirectX 8.0 SDK,请确认配置好了您的VC开发环境:首先将DirectX8的安装目录下例子代码中的DirectShow基类编译一下,Debug版本生成strmbasd.lib,Release版本生成strmbase.lib;然后配置VC开发环境:Tools->Options-> Directories->Include files加入两个路径,DirectX8include和DIRECTX8SAMPLESMULTIMEDIADIRECTSHOWBASECLASSES,并且在MFC目录的前面;Library file加入三个路径,DirectX8lib、DIRECTX8SAMPLESMULTIMEDIADIRECTSHOWBASECLASSESDEBUG和DIRECTX8SAMPLESMULTIMEDIADIRECTSHOWBASECLASSESRELEASE。
2. 本演示方案采用Mpeg1格式的数据,但该方案并不局限于Mpeg1。
3. 由于演示代码没有实时的数据源,所以服务器端通过读取Mpeg1文件的方法模拟实时数据。演示中,可以采用较大的Mpeg1文件,效果会更逼真。
4. 本方案应用到网络实时监控时,可能还会涉及到Mpeg数据流的随机访问,即Mpeg数据流分析、截取问题,限于篇幅,本文不作介绍。有兴趣的朋友可以参考相关资料后,对示例代码进行修改。
5. 本方案的演示代码仅限于一对一的传输,只要稍加修改,服务器端便可提供多路数据的传输。
afeng124 2010-05-14
  • 打赏
  • 举报
回复
socket + DirectX
afeng124 2010-05-14
  • 打赏
  • 举报
回复
Visual C#使用DirectX实现视频播放

本文来自[Svn中文网]转发请保留本站地址:http://www.svn8.com/dotnet/Csharp/2010012318719.html
wuyq11 2010-05-14
  • 打赏
  • 举报
回复
lijin177036962 2010-05-14
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 yuanhuiqiao 的回复:]
参考
[/Quote]
我现在本地播放已经成功了.
现在 要添加url播放.
这个功能怎么实现.?
yuanhuiqiao 2010-05-14
  • 打赏
  • 举报
回复
Forrest23 2010-05-14
  • 打赏
  • 举报
回复
H.264专用播放器? 没做过这方面的 等待楼下
lijin177036962 2010-05-14
  • 打赏
  • 举报
回复
看过的,不会的,能否帮顶下.?谢谢.!
tashiwoweiyi 2010-05-14
  • 打赏
  • 举报
回复
自己帮顶,要不沉了
lijin177036962 2010-05-14
  • 打赏
  • 举报
回复
自己帮顶,要不沉了.

110,577

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧