Directshow的音频捕获和回放测试1

来源:互联网 发布:os x和mac os 编辑:程序博客网 时间:2024/05/16 17:30

Directshow的音频捕获和回放测试
2007-6-5
想了很久才定了这个题目,因为所做的工作确实非常有限,只能算是一个小小的测试。以前带学生做过一个采用Windows Media Format 9SDK捕获声音和视频祯,通过网络传输后回放的毕业设计的题目,不过播放的时候有些卡。查阅了一些资料,发现采用directshow做的比较多,并且还可以配套RTP和RTCP协议,达到实时播放的目的。于是决定采用directshow来做一套类似的东西,然后再套上P2P的功能,最后形成一个完整的基于P2P和Directshow技术的网络电视收看平台。关于音频的采集、传输和回放是其中的一个重要的组成部分,本文只是一个小小的测试,主要完成的内容是采用Grabber Filter捕获音频祯,将其保存为一个本地文件,然后再建立一个memplay的source Filter,在graph Edit中或者自己编写程序播放这个本地文件,达到回放的目的。
本文主要包括以下几个方面的内容:
1、Directshow9.0简介
关于Directshow9 SDK的简介随便在网络上一搜,便会有一大堆,我也就不罗唆了。主要说一下它的主要部分就是有一些filter组成的,其中包括source Filter、transform Filter 和render Filter。如果以我们的音频采集和播放为例,source filter对应的就是麦克风的功能,负责采集原始的音频数据;而render filter对应的就是你的音箱的功能,负责音频数据的回放。
安装完DirectX SDK9以后,在开始-程序-Microsoft DirectX 9.0 SDK-DirectX Utilities中会有一个Graph Edit的程序,这个程序非常有用。下面就是利用它完成的一个Filter的连接图。
 
具体的做法是选择Graph-Insert Filters,选择Audio Capture Sources,双击其中的第一个选项。然后右键单击输出针脚,即capture旁边的小凸起,选择Render Pin。这种方法采用的是智能连接的方法,系统自动地为你搜寻可以连接到的Filter。也可以自己选择合适的filter,然后通过鼠标拖拽的方式连接Filter,形成自己的Graph。建立好Graph以后,单击上面的播放按钮就可以运行这个graph了,效果是自己对着麦克风说的话会在音箱中有一个回放的动作。
自己编写好的filter注册后也可以在GraphEdit中使用,一般自己编写的filter都是显示在Directshow filters子项下的。可以通过这个工具测试自己编写的Filter是否能够正常运行。
2、音频祯的捕获
先说一下大体的思路,我没有编写专门的程序来捕获音频祯,而是借用了Directshow自带的一个grabber的例子,稍微改写了一下,将其捕获到的数据祯(既可以是音频也可以是视频)按照一定的格式存放在c盘的audiodata文件中,供以后播放时调用。这种方法并不是特别好,因为额外的写文件操作会严重影响系统的效率,这一点用到视频的时候特别明显。但是作为一个测试来说,尽量从简单易行的角度出发,也就够了。
首先打开C:/DXSDK/Samples/C++/DirectShow/Filters/Grabber(假设安装在c盘的根目录下)下的grabber.dsw,找到grabber.cpp下的Receive函数,在checkpointer(pms, E_POINTER)下输入以下内容:
 //////////////////////////////////////////////////////////////////////
 BYTE *pBuffer = NULL;
 pms->GetPointer(&pBuffer);
 if(pBuffer == NULL) return E_UNEXPECTED;
 REFERENCE_TIME StartTime, StopTime;
    pms->GetTime( &StartTime, &StopTime);
 long LBufferSize = pms->GetSize();
 BYTE * buffer = NULL;
 buffer = new BYTE[LBufferSize];
 if(buffer == NULL) return E_UNEXPECTED;
 memcpy(buffer, pBuffer, LBufferSize);
 FILE * fp = NULL;
 fp = fopen("c://audiodata", "ab+");
 if(fp == NULL) return E_UNEXPECTED;
 fwrite(&LBufferSize, sizeof(long), 1, fp);
 fwrite(&StartTime, sizeof(REFERENCE_TIME), 1, fp);
 fwrite(&StopTime, sizeof(REFERENCE_TIME), 1, fp);
 fwrite(buffer, sizeof(BYTE), LBufferSize, fp);
 fclose(fp);
 delete[] buffer;
 buffer = NULL;
 //////////////////////////////////////////////////////////////////////
这段代码非常简单,大家一看就都明白了,就是把sample的长度,开始时间,结束时间以及sample的内容存放到c盘的audiodata文件中。我习惯于用fopen,而不习惯于用CFile,我觉得fopen更简单,更好使。
编译一下这个文件,最好编译成win32 debug而不是unicode-debug版本,可能会提示说有一个../../BaseClasses/debug/strmbasd.lib找不到,因为你可能还没有编译baseclasses中的文件。找到baseclasses所在的文件夹,按照debug版本编译,然后再重新编译grabber就可以了。
在开始-运行中输入
regsvr32 C:/DXSDK/Samples/C++/DirectShow/Filters/Grabber/Debug/grabber.ax
注册该filter,现在就可以在GraphEdit中的insertFilters-Directshow Filters中找到sample Grabber Example这个Filter了,这就是我们需要用到的捕获音频祯的filter。
在GraphEdit中建立以下Graph:
 
运行这个Graph,对着麦克风说几句话,然后单击停止按钮。好的,现在在C盘的根目录下可以找到audiodata这个文件了,这当中就是我们按照长度、开始时间、结束时间、祯内容存放的音频文件。
3、编写source filter
Directshow提供了两种模式来进行播放,一种是推模式,一种是拉模式。前者对应于有摄像头、麦克风等音视频采集Filter的应用,而后者对应于文件的播放形式。前者的source filter可以自己产生数据,然后将数据主动推送到与之相连的下一个filter,而后者则是相反的一套动作。前者基于IMemInputPin接口,而后者基于IAsyncReader接口。曾经见到过一个用拉模式写的在网络上传输视频文件的例子,效果不是很好,卡得厉害。总以为可能是模式的原因,也没有深究其中的原委,就采用了推模式的方法。
其实所作的工作是自己设计完成了一个source filter的制作,然后利用程序或者在graph Edit中进行测试(后者更为简单),程序还有许多不足的地方,待以后日臻完善。directshow中自带了一些很好的例子,可以拿来修改后使用,非常方便。我研究是其中的filters/ ball程序,这个程序可以自行产生一组跳动的小球的画面播放。由于从它的基础上修改,我甚至连文件的名称都没有改。
fball.h
class CMemPlayStream;
// {A7BD9B8E-36C7-4d7a-9925-A20246DAFD23}
DEFINE_GUID(CLSID_MemPlay,
0xa7bd9b8e, 0x36c7, 0x4d7a, 0x99, 0x25, 0xa2, 0x2, 0x46, 0xda, 0xfd, 0x23);
class CMemPlay : public CSource
{
public:
 static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);
private:
    CMemPlay(LPUNKNOWN lpunk, HRESULT *phr);
}; // CMemPlay
class CMemPlayStream : public CSourceStream
{
public:
CMemPlayStream(HRESULT *phr, CMemPlay *pParent, LPCWSTR pPinName);
~CMemPlayStream();
HRESULT FillBuffer(IMediaSample *pms);
HRESULT DecideBufferSize(IMemAllocator *pIMemAlloc,
                         ALLOCATOR_PROPERTIES *pProperties);
HRESULT SetMediaType(const CMediaType *pMediaType);
HRESULT CheckMediaType(const CMediaType *pMediaType);
HRESULT GetMediaType(CMediaType *pmt);
HRESULT OnThreadCreate(void);
private:
    int m_iRepeatTime;                 // Time in msec between frames
    const int m_iDefaultRepeatTime;      // Initial m_iRepeatTime
    CCritSec m_cSharedState;           // Lock on m_rtSampleTime and m_Ball
    CRefTime m_rtSampleTime;         // The time stamp for each sample
 BYTE * m_DataList[1024];
 long m_SampleSize[1024];
   REFERENCE_TIME m_StartTimeList[1024];
   REFERENCE_TIME m_StopTimeList[1024];
 int m_SampleCount;
 int m_SampleReaded;
}; // CMemPlayStream
其中包括两个类,CMemPlay类是Filter的一个壳子,里面没有什么实质性的内容,无非就是调用了一下CMemPlayStream类,好像都是这么来用的,现在还说不好为什么一定要这么做。CMemPlayStream中主要派生了几个方法:FillBuffer用来填充实际的sample的内容,DecideBufferSize用来设置buffer的大小,GetMediaType、SetMediaType和CheckMediaType用来设置和匹配媒体的类型。好像不用完全覆盖这三个函数,但我没有试过。
fball.cpp
#include <streams.h>
#include <olectl.h>
#include <initguid.h>
#include <stdio.h>
#include "fball.h"
#pragma warning(disable:4710)  // 'function': function not inlined (optimzation)
#pragma warning(disable:4628)
EXTERN_GUID(WMFORMAT_WaveFormatEx,
0x05589f81, 0xc356, 0x11ce, 0xbf, 0x01, 0x00, 0xaa, 0x00, 0x55, 0x59, 0x5a);
const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{
    &MEDIATYPE_Audio,       // Major type
    &MEDIASUBTYPE_NULL      // Minor type
};
const AMOVIESETUP_PIN sudOpPin =
{
    L"Output",              // Pin string name
    FALSE,                  // Is it rendered
    TRUE,                   // Is it an output
    FALSE,                  // Can we have none
    FALSE,                  // Can we have many
    &CLSID_NULL,            // Connects to filter
    NULL,                   // Connects to pin
    1,                      // Number of types
    &sudOpPinTypes };       // Pin details
const AMOVIESETUP_FILTER sudBallax =
{
    &CLSID_MemPlay,   // Filter CLSID
    L"Memory play",   // String name
    MERIT_DO_NOT_USE,       // Filter merit
    1,                      // Number pins
    &sudOpPin               // Pin details
};
CFactoryTemplate g_Templates[] = {
  { L"Memory play"
  , &CLSID_MemPlay
  , CMemPlay::CreateInstance
  , NULL
  , &sudBallax }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2(TRUE);

} // DllRegisterServer
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2(FALSE);

} // DllUnregisterServer
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
BOOL APIENTRY DllMain(HANDLE hModule,
                      DWORD  dwReason,
                      LPVOID lpReserved)
{
 return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}
CUnknown * WINAPI CMemPlay::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    ASSERT(phr);
    CUnknown *punk = new CMemPlay(lpunk, phr);
    if(punk == NULL)
    {
        if(phr)
            *phr = E_OUTOFMEMORY;
    }
    return punk;
} // CreateInstance
CMemPlay::CMemPlay(LPUNKNOWN lpunk, HRESULT *phr) :
    CSource(NAME("Memory Play"), lpunk, CLSID_MemPlay)
{
    ASSERT(phr);
    CAutoLock cAutoLock(&m_cStateLock);
    m_paStreams = (CSourceStream **) new CMemPlayStream*[1];
    if(m_paStreams == NULL)
    {
        if(phr)
            *phr = E_OUTOFMEMORY;
        return;
    }
    m_paStreams[0] = new CMemPlayStream(phr, this, L"Memory Play!");
    if(m_paStreams[0] == NULL)
    {
        if(phr)
            *phr = E_OUTOFMEMORY;
        return;
    }
} // (Constructor)