适合于流式播放的DirectShow客户服的编写

原创 2012年03月23日 17:55:03

 

转载请标明是引用于 http://blog.csdn.net/chenyujing1234

 例子代码:(包括客户端与服务端)(编译工具:VS2005)

http://www.rayfile.com/zh-cn/files/a34cb700-74ce-11e1-999e-0015c55db73d/

 

参考书籍:<<DirectShow开发指南>>

1、设计思路.

1、1需要写一个能够接收服务器发来的数据,并提供给Filter Graph中其他解码的Source Filter,如图所示

0001的source Filter用于接收网络上发来的MPEG数据,且工作在拉模式下。(可参考 http://blog.csdn.net/chenyujing1234/article/details/7378315).

DirectShow提供了MPEG1解码的所有filter,包括MPEG1-1 Stream Splitter、MPEG Vieo Decoder、MPEG Audio Decoder,因为MPEG-1 Stream Splitter(流分离器)只能工作在拉模式下,这样不得不把Source Filter也设计成拉模式。(不过如果有第三方的可以工作的推模式下的Splitter FIlter,则完全可以把Source Filter设计成推模式,即接到网络上的数据就主动传给Splitter Filter,而不是被动地等待Splitter去读取)

1、2 一各种双缓冲队列技术.

必须让Source Filter接收一定数量的数据,并缓存起来。因为在Source Filter和Splitter连接过程中,会从Source Filter中读取一部分数据,以获得数据的格式,否则Filter Graph无法完成连接。而当完整的Filter Graph构建完成,且处于运行状态后,Source Filter必须动态地接收数据,并持续把新的数据给Splitter。

这里使用双缓冲技术。

工作原理:

建立两个队列,一个是空闲的缓冲队列PoolList,用以接收存放数据,另一是尚未处理的数据缓冲队列DataList,等待Splitter读取。

当Source Filter使用socket接收数据后,从PoolList队列的头上取出一个缓冲块,存放数据,然后将这个缓冲块加入到DataList尾部,等待Splitter读取。

而在Splitter要求读取时,Source Filter会从DataList 队列的头上取出一个缓冲块,读取数据,再将读完的缓冲块加到PoolList尾部,回收再利用。

2、源码分析。

主要分析两个类CMemStream CMemReader 及CFilterGraph

//
// MemFilter.h
//
#ifndef  __MemFilter_h__
#define  __MemFilter_h__

#include <streams.h>
#include <stdio.h>
#include "asyncio.h"
#include "asyncrdr.h"
#include "CMediaSocketClient.h"
#include "CDataAdmin.h"
#include "MyDef.h"


//////////////////////////////////////////////////////////////////////
//  Define an internal filter that wraps the base CBaseReader stuff //
//////////////////////////////////////////////////////////////////////
class CMemStream : public CAsyncStream
{
private:
    CCritSec       m_csLock;    // 数据操作的同步对象
	CDataAdmin *   m_pDataList; // 数据接收队列管理器
    ULONG          m_ulPositionInPack;//  当前缓冲块内的偏移量
    // Total length available
	LONGLONG       m_llLength;  // 当前可以获得的数据总长度
	// Bytes totally read
    DWORD          m_dwTimeStart;

public:
    CMemStream(CDataAdmin* inBuffer)
    {
		// 成员变量初始化
		m_pDataList         = inBuffer;
		m_ulPositionInPack  = 0;
		m_llLength          = 0;   
    }

    HRESULT SetPointer(LONGLONG llPos)
    {
		return S_OK;
    }

	// 当Splitter要求数据时,必须在这个函数中提供
    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())
				{
				//	FILE * fp = fopen("c:\\log.txt","a+");
				//	fprintf(fp,"failed!\n");
				//	fclose(fp); 
					return E_FAIL;
				}
				else 
				{
				//	FILE * fp = fopen("c:\\log.txt","a+");
				//	fprintf(fp,"SLEEP!\n");
				//	fclose(fp); 
					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())
				{
				//	FILE * fp = fopen("c:\\log.txt","a+");
				//	fprintf(fp,"failed!\n");
				//	fclose(fp); 
					return E_FAIL;
				}
				else 
				{
				//	FILE * fp = fopen("c:\\log.txt","a+");
				//	fprintf(fp,"SLEEP!\n");
				//	fclose(fp);
					Sleep(10);
				}
			}
		}
		*pdwBytesRead = dwBytesToRead;

		return S_OK;
    }

    LONGLONG Size(LONGLONG *pSizeAvailable)
    {
        *pSizeAvailable = m_llLength;
        return 0x7fffffffff;
    }

    DWORD Alignment()
    {
        return 1;
    }

    void Lock()
    {
        m_csLock.Lock();
    }

    void Unlock()
    {
        m_csLock.Unlock();
    }

	void AddAvailableLength(LONGLONG inLength)
	{
		m_llLength += inLength;
	}
};

class CMemReader : public CAsyncReader
{
public:

    //  We're not going to be CoCreate'd so we don't need registration
    //  stuff etc
    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;
    }
};

#endif  // __MemFilter_h__


 

class CMemReader : public CAsyncReader
{
public:

    //  We're not going to be CoCreate'd so we don't need registration
    //  stuff etc
    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;
    }
};


 

下面来看Filter Graph的构建过程。(参见CFiltreGraph::BuildeGraph的实现)。可以看到BuildGraph函数首先创建一个Filter Graph组件,然后获得一系统控制接口,

接下来创建source filter,值得注意的是,因为这是一个应用程序实现的Filter,不能通过CoCreateInstance函数去创建它,而要直接成成:

// construct source filter
	// Media type
	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();  // 增加一个引用计数,防止COM自动删除
		//  Add our filter
		hr = m_pGB->AddFilter(m_pSourceReader, NULL);
		if (FAILED(hr)) 
		{
			m_bInit = false;
		}  
	}


BuildGraph函数最后并没有将Source Filter的输出Pin连出去,因为Source Filter在没有缓存
 一定数据的数据之前,与Filter连接是不会成功的,解决方法是,启动一个等待线程,
当Source Filter预先接收到足够的数据后,再构建完整的Filter Graph。

if (m_bInit)
	{
		// Waiting for event to start filter graph
		::AfxBeginThread((AFX_THREADPROC)CFilterGraph::WaitingThrd, this);
	}

// When data buffer enough, start the filter graph
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())
		{
			// 开始构建完成的Filter Graph
			pGraph->StartGraph();
		}
	}
	return 1;
}

// 构建完成的Filter Graph,并运行它
bool CFilterGraph::StartGraph(void)
{
	if (m_bInit)
	{
		m_bRun = true;
		// 使用智能连接,将Source Filter 的输出Pin连出来
		HRESULT hr  = m_pGB->Render(m_pSourceReader->GetPin(0));
		if (FAILED(hr))
		{
			m_bRun = false;
			return false;
		}
        // 设置视窗的一些属性
		hr = m_pVW->put_Visible(OAFALSE);
		hr = m_pVW->put_Owner((OAHWND)m_hOwner);
		hr = m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
		hr = m_pVW->SetWindowPosition(m_nLeft, m_nTop, m_nWidth, m_nHeight);
		// Enable the parent window to get the mouse and keyboard event in the video window
		hr = m_pVW->put_MessageDrain((OAHWND)m_hOwner);
		hr = m_pVW->put_Visible(OATRUE);
		
		// run the filter graph
		hr = m_pMC->Run();
		return true;
	}
	
	return false;
}


从CMediaSocketClient::ReceivingLoop函数的实现中,查看客户端的整个数据接收过程。可以看到,ReceivingLoop函数使用了一个while循环不断地接收数据,

并把数据交给队列管理;同时对接收到的数据包进行计数。

 CMemReader(CMemStream *pStream, CMediaType *pmt, HRESULT *phr) :
        CAsyncReader(NAME("Mem Reader"), NULL, pStream, phr)
    {
        m_mt = *pmt;
    }

 

在CMemStream中有Read函数,它就是对收到的数据取出来.

// 当Splitter要求数据时,必须在这个函数中提供
    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())
				{
				//	FILE * fp = fopen("c:\\log.txt","a+");
				//	fprintf(fp,"failed!\n");
				//	fclose(fp); 
					return E_FAIL;
				}
				else 
				{
				//	FILE * fp = fopen("c:\\log.txt","a+");
				//	fprintf(fp,"SLEEP!\n");
				//	fclose(fp); 
					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())


客户端的设计可以用下面的框图表示:

 

 

最简单的基于DirectShow的示例:视频播放器自定义版

本文记录一个简单的基于DirectShow的自定义的视频播放器。这里所说的“自定义播放器”,实际上指的是自己在Filter Graph中手动逐个添加Filter,并且连接这些Filter的后运行的播放...
  • leixiaohua1020
  • leixiaohua1020
  • 2015年01月11日 18:05
  • 10013

用java写简单的web服务器,在宿舍提供视频播放

准备写一个web服务器专门供宿舍看电影用。 之前已经实现基本的web服务器,今天又把视频播放器弄好了,接下来就是整合了。 视频播放器,用的CuPlayer,改改demo就行,之前不成功可能是没在l...
  • Monster_li57
  • Monster_li57
  • 2016年04月13日 11:17
  • 5722

rtp、rtsp客户端开发流程

环境:VLC做服务器,url:rtsp://192.168.100.77:8554/1               rtsp链路采用tcp链接               rtp采用udp 关于rt...
  • yuanbinquan
  • yuanbinquan
  • 2017年02月24日 11:42
  • 1494

HDFS的API调用,创建Maven工程,创建一个非Maven工程,HDFS客户端操作数据代码示例,文件方式操作和流式操作

1. HDFS的java操作hdfs在生产应用中主要是客户端的开发,其核心步骤是从hdfs提供的api中构造一个HDFS的访问客户端对象,然后通过该客户端对象操作(增删改查)HDFS上的文件1.1 搭...
  • toto1297488504
  • toto1297488504
  • 2017年05月30日 16:56
  • 636

流式套接字(SOCK_STREAM)——客户端/服务器(C/S)源代码

 摘自:http://wxxweb.blog.163.com/blog/static/135126900201022691449421/ 实现最简单的客户端和服务器程序先总结一下简单的流程再将这两部分...
  • HEN_MAN
  • HEN_MAN
  • 2011年04月30日 19:37
  • 1560

基于Linux平台实现的流式套接字客户端服务器端代码

(1)服务器段代码如下: #include #include #include #include #include #include #include #include ...
  • u010870518
  • u010870518
  • 2014年12月28日 20:24
  • 1321

流式套接字客户端/服务器编程

此例子将利用上一篇介绍的套接字基础概念实现一个最基本的流式套接字客户端/服务器通信程序。在该程序中,客户端和服务器将按照如下步骤交互: 1)客户端向服务器发出日期请求字符串,如%D%Y%A%T等 ...
  • yujiaming493323
  • yujiaming493323
  • 2013年04月17日 10:15
  • 1177

流式套接字实现简单的客户端/服务端通信过程

流式套接字实现简单的客户端/服务端通信过程一、 实验内容 熟悉流式套接字socket函数的使用方法,包括建立连接、数据发送/接收和关闭连接等; 客户端可向服务端发送任意字符串,服务端在接收到该字 符...
  • Ni9htMar3
  • Ni9htMar3
  • 2016年12月05日 15:46
  • 2039

【网络编程】使用流式套接字实现简单的客户端/服务端通信过程

实验要求: 1. 熟悉流式套接字socket函数的使用方法,包括建立连接、数据发送/接收和关闭连接等; 2. 客户端可向服务端发送任意字符串,服务端在接收到该字符串后,回送给客户端; 3. 服务...
  • zchahaha
  • zchahaha
  • 2017年02月24日 09:54
  • 997

Dancer.Ocx控件,适合于读出声卡上播放的声音

  • 2007年04月21日 20:12
  • 29KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:适合于流式播放的DirectShow客户服的编写
举报原因:
原因补充:

(最多只允许输入30个字)