1.  引言

在局域网内传输音、视频等多媒体数据主要有两种方案:下载和流式传送。下载的主要缺点是,必须等全部内容传输完毕,然后才能在本地机器打开;而采用流式传输方案,多媒体数据流可以连续、实时地向用户计算机传输,用户不必等到整个文件全部下载完毕,只需在客户端传输开始后经过若干秒的延迟即可进行观看。多媒体数据流的解码播放可使用 DirectShow技术来实现。

2.       DirectShow 技术介绍

DirectShow 是一个Windows 平台上的流媒体框架,提供了高质量的多媒体流采集和回放功能。它支持多种多样的媒体文件格式,包括ASFMPEGAVIMP3 WAV 文件,同时支持使用WDM 驱动或早期的VFW 驱动来进行多媒体流的采集。DirectShow 整合了其它的DirectX 技术,能自动地侦测并使用可利用的音视频硬件加速,也能支持没有硬件加速的系统。

2.1 DirectShow 系统结构

DirectShow 是一套完全基于COMComponent Object Model,组件对象模型的应用系统)。DirectShow 定义了如何利用标准组件来处理流媒体数据,这些组件称为过滤器(Filter )。 DirectShow 使用Filter Gragh 来管理Filter  (管理者叫做Filter Gragh Manager)。Filter GraghFilter 的“容器”,而Filter Filter Gragh 中的最小功能模块。Filter 一般由一个或多个Pin组成, Filter 之间通过Pin 相互连接,构成一条顺序的链路。 Filter 又可被细分为Source filter (源过滤器)、Transform   filter (变换过滤器)、Renderer   filter   (表现过滤器)等。源过滤器引入数据到过滤器图表中,不同的源过滤器处理不同类型的数据源;变换过滤器的工作是获取输入流,处理数据,并生成输出流;表现过滤器在过滤器图表里处于最后一级,它们接收数据并把数据提交给外设。

将所有过滤器的Filter 通过Pin 连接起来,才可以完成整个任务。DirectShow 的整体结构如下图所示:

基于DirectShow的局域网内音视频流的多机共享_通信技术

 

                             1 DirectShow 系统构架

如图所示,在DirectShow 系统之上是应用程序(Application )。应用程序要按照一定的意图建立起相应的Filter Gragh DirectShow 能在Filter Gragh 运行的时候接收到各种事件,并通过消息的方式发送到应用程序,从而实现了应用程序与DirectShow  系统之间的交互。另外,DirectShow 过滤器可以与很多硬件通讯并控制它们,包括当地文件系统,TV 调谐器,视频捕获卡,VFM 编×××,视频显示器(通过DirectShow 和图形设备接口(GDI)),以及声卡(通过 DirectSound) 。这样DirectShow就将应用程序与设备的复杂事物隔离开来。 DirectShow 对于一定格式的文件也提供自带的压缩,解压滤波器。

2.2 DirctShow 的传输机制

DirectShow定义了局部存储传输的两种机制,推模式和拉模式。

推模式中源过滤器产生数据并将它传给下流的过滤器。那个过滤器被动的接受数据,处理它,并进一步将它传给下流。

基于DirectShow的局域网内音视频流的多机共享_网络传输_02

 

                             2  推模式数据传输流程

在拉模式中,源过滤器连接一个Parser过滤器(分割过滤器,用于把输入流分成多个输出)。Parser过滤器向源过滤器请求数据。源过滤器通过传送数据来相应请求。推模式IMemInputPin接口。拉模式使用IAsyncReader接口。

基于DirectShow的局域网内音视频流的多机共享_数据传输_03

 

                             3  拉模式数据传输流程

3.  软件的设计与实现

该软件主要涉及到两方面的技术:一是服务器与客户端之间的通信技术,包括多媒体数据的传输、控制命令的传输等,实际应用中使用Windows   Socket 技术来实现;另一方面技术是客户端对接收到的多媒体流实时解码后进行播放,实际应用中采用DirectShow技术完成。

3.1 网络传输

3.1.1 传输模式的选择

套接字是支持TCP/IP 协议的网络通信的基本操作单元。根据网络通信的特点,套接字可以分为两类:流式套接字和数据报套接字。流式套接字是面向连接的,提供双向的、有序的、无重复并且无记录边界的数据流服务,它使用于处理大量数据,使用传输控制协议(TCP);数据报套接字是无连接的,支持双向的数据流,但并不保证数据传输的可靠性、有序性和无重复性,使用用户数据报协议(UDP )。

图像信息关键是要连续快速,即使丢上几帧,也不会对操作人员造成太大的影响,同时鉴于本文的设计,考虑到局域网本身的安全机制,以及保证音视频流在局域网内实现组播,采用数据报套接字模式实现。数据报套接字不提供连接,依靠网络自身来保证传输的可靠,不能保证图像不丢失,  但是简单快速。下图是数据报套接字的编程模型:

基于DirectShow的局域网内音视频流的多机共享_网络传输_04

                            4  数据报套接字编程模型

3.1.2 数据传输的实现

软件设计了 CListenSocket类用于在服务器端监听客户端计算机的网络连接;CWorkerSocket 类封装了各种Socket 操作,用于实际数据的传送和接收;CMediaSocketServer 类从CWorkerSocket    类派生,用于服务器端数据发送;CMediaSocketClient类从 CWorkerSocket 类派生,用于客户端的数据接收。

在服务器与客户端之间存在通信协议的问题,为了简单起见将控制通信与数据通信合并在一起。在服务器端首先将连续的视音频流分成多块的小包负载数据,并且添加一个信息头一起发送;在客户端根据信息头描述将小包负载数据进行拼装。

MPEG1 流为例,信息头和负载数据定义如下:

// 信息头

typedef struct

{

unsigned int nMsgType:8;     // 负载类型

unsigned int nDataSize:16; // 数据长度

} MSG_HEADER, *PMSG_HEADER;

 // 负载数据

typedef struct

      {

      CHAR MPEGData[2324];

     } MPEG1_PACK, *PMPEG_PACK;

当数据量很大时,绘图可能需要几秒钟甚至更长的时间,而且有时还会出现闪烁现象,为了解决这个问题,在程序中我们采用了双缓冲队列技术。双缓冲队列技术即建立两个队列,一个是空闲的缓冲队列PoolList,用来存放接收视频流,另一个是尚未处理的视频流缓冲队列DataList,等待解码。客户端通过socket 接收到数据后,PoolList 队列的头上取出一个缓冲块,存放数据,并将此块放到DataList 队列的尾部,等待处理。同时,在解码的定时时钟控制下,解码模块从 DataList   队列的头部取出一个缓冲块,读出数据、解码,将读完的缓冲块重新放到PoolList 队列的尾部,循环使用。

3.2  客户端Filter 的设计

客户端通过设计Source Filter 来接收服务器发来的数据,并将数据提供给Filter Graph中其他的Filter 解码,实现音视频流的本地回放,数据传送模式采用的是拉模式。客户端用到的Filter Graph 如下图所示(该图可利用DirectX  自带的软件GraphEdit 获得,并可用于程序的调试):

 

基于DirectShow的局域网内音视频流的多机共享_数据传输_05

 

                                    5  客户端的Filter Graph

3.3  客户端Filter Graph 的实现

软件设计了CFilterGraph 类来实现Filter Graph 的功能,首先通过BuildGraph 函数创建 一个Filter Graph 组件,然后获得一系列控制接口,然后创建Source Filter,启动等待线程, 在Source Filter 缓存一定数量的数据之后,与Splitter 建立连接,进行视音频数据的传送。

以下是BuildGraph 函数的实现

bool CFilterGraph::BuildGraph(void)

     {

      m_bInit = false;

      // Create filter graph

          HRESULT hr = CoCreateInstance(CLSID_FilterGraph,

                                              NULL,

                                              CLSCTX_INPROC,

                                              IID_IGraphBuilder,

                                              (void**)&m_pGB);

//省略获得控制接口部分的代码

if (m_bInit)

      {

          CMediaType      mt;

          mt.majortype = MEDIATYPE_Stream;

          mt.subtype    = MEDIASUBTYPE_MPEG1System;

          m_pSourceStream     = new CMemStream(m_pDataList);

          m_pSourceReader     = new CMemReader(m_pSourceStream, &mt, &hr);

          m_pSourceReader->AddRef();

          // Add our filter

          hr = m_pGB->AddFilter(m_pSourceReader, NULL);

          if (FAILED(hr))

          {

               m_bInit = false;                                                                                    

       }

       }

       if (m_bInit)

       {

            // Waiting for event to start filter graph

            ::AfxBeginThread((AFX_THREADPROC)CFilterGraph::WaitingThrd, this);

       }

       return m_bInit;

      }

等待线程的执行函数体:

 UINT CFilterGraph::WaitingThrd(void * pParam)

      {

       CFilterGraph * pGraph = (CFilterGraph *) pParam;

       if (pGraph != NULL && pGraph->m_pDataList != NULL)

       {

            ::WaitForSingleObject(pGraph->m_pDataList->m_hBufEnough, INFINITE);

            if (!pGraph->IsRunning())

            {

                 pGraph->StartGraph();

            }

       }

       return 1;

      }

4.    结束语

基于COM组件的DirectShow 由于其广泛的适用性和可移植性,正日益受到重视和得到了广泛的应用。本文介绍了DirectShow基本结构、组件和机制,使用Socket通信机制,编程实现在局域内音视频流信息的多机共享,后续工作将初步实现局域网内的视频点播功能。