039 DirectShow播放器

Post date: 2015/5/6 上午 08:50:11

2011-04-19 08:16:49| 分类: 实时操作系统

订阅

|字号

|举报

由于MCI最终没能解决声道切换的问题,所以我转向用DirectShow中的借口来做一个播放器,并在这里增加声道切换的功能。实际上,是能满足一部分功能的。现在把基本的步骤介绍一下。

用DirectShow做一个简易播放器,实现如下功能:

1 播放,暂停,结束

2 音量控制,声道控制,进度控制,速度控制

3 全屏,置顶

有了这些功能,满足大众化基本上就能实现了,剩余的就是界面的美化了和其他交互功能。我用了八个接口分别是:

IGraphBuilder *m_pGraph;

// IGraphBuilder 接口提供了生成Filter Graph相关的方法

IMediaControl *m_pMediaControl;

// IMediaControl 接口提供了控制流经Filter Graph数据流的相关方法

IMediaEventEx *m_pEvent;

// IMediaEventEx 继承自IMediaEvent,提供了从Filter Graph 管理器获取事件消息的方法

IMediaSeeking *m_pMediaSeeking;

// IMediaSeeking 提供了控制流的播放位置和播放速度的方法

IBasicAudio * m_pBasicAudio;

// IBasicAudio接口提供了声音和声道的部分处理,如音量大小和音量均衡等

IBaseFilter * m_pMpegAFilter;

// 在用新的过滤器(Filter)控制声道的时候用到的接口

IMpegAudioDecoder *m_pMpegAudioDec;

// 一个Filter接口,提供了提取和分配声道功能

IVideoWindow * m_pVideoWindow;

// 控制屏幕接口

有了这些接口,我们就可以在自己的类中进行封装了。注意的是要用这些接口来编程需要设置一些环境,如include和lib,还有DirectShow中需要编译的一些lib,当然前提是有了Direct SDK。下面是基本步骤:

一、建立了一个对话框MFC程序。在其上面增加一个Picture控件,用来播放媒体文件。注意要在app初始化中初始化COM:

//初始化COM接口

HRESULT hr = CoInitialize(NULL);

if (FAILED(hr))

{

TRACE("ERROR - Could not initialize COM library.\n");

return FALSE;

}

退出的时候别忘了:CoUninitialize();

二、当我们得到一个文件名(地址),如D:\MTV\刀剑如梦.avi,要如何实现用direct播放呢?主要过程如下:

// 函数 PlayFile:打开制定媒体文件

void PlayFile(BSTR strfilePath)

{

HRESULT hr;

// 1 load builder,IGraphBuilder

hr = CoCreateInstance(CLSID_FilterGraph,

NULL,

CLSCTX_INPROC,

IID_IGraphBuilder,

(void **)&m_pGraph);

// 2 load filter,这里增加自己的过滤器,IMpegAudioDecoder

hr = CoCreateInstance(CLSID_CMpegAudioCodec,

NULL,

CLSCTX_INPROC_SERVER,

IID_IBaseFilter,

(void **)&m_pMpegAFilter );

if(SUCCEEDED(hr))

{

m_pGraph->AddFilter(m_pMpegAFilter, L"Mpeg Audio Decoder"); // add filter to builder

hr = m_pGraph->RenderFile(bstrPath, NULL );

hr = m_pMpegAFilter->QueryInterface(IID_IMpegAudioDecoder,

(void **)&m_pMpegAudioDec);

if(SUCCEEDED(hr))

{

m_pMpegAudioDec->put_DualMode(m_Channel); // 声道选择,0,1,2

}

}

//3 设置IVideoWindow 接口,把播放窗口放置到Picture控件上

m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&m_pVideoWindow); // load

m_pVideoWindow->put_Owner((OAHWND)m_hWnd);

m_pVideoWindow->put_MessageDrain((OAHWND)m_hWnd);

// 当有了这一句,ActiveMovie上接收的消息就被对话框本身截取了

m_pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);

CRect m_winRect;

PicCtrl->GetWindowRect(m_winRect); // PicCtrl是picture控件

ScreenToClient(m_winRect);

m_pVideoWindow->SetWindowPosition(m_winRect.left,

m_winRect.top,

m_winRect.Width(),

m_winRect.Height());

// 4 设置IMediaSeeking 接口,以控制播放进度,IBasicAudio 接口控制音量

m_pGraph->QueryInterface(IID_IMediaSeeking, (void **)&m_pMediaSeeking);

m_pGraph->QueryInterface(IID_IBasicAudio,(void **)&m_pBasicAudio);

m_pMediaSeeking->GetPositions(&m_curpos,&m_stoppos);

m_filelength = m_stoppos - 0; // 得到媒体文件的总大小——帧数

// 5 播放,设置IMediaControl 接口来实现

m_pVideoWindow->put_Visible(OATRUE);

m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl);

m_pMediaControl->Run();

}

如果不是重新打开一个文件,而只是暂停之后的播放则Play函数可简化:

void Play()

{

m_pVideoWindow->put_Visible(OATRUE);

m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl);

m_pMediaControl->Run();

}

三、其他功能的实现

在第二步所有接口的挂接基础之上,其他功能的实现就十分简单了,下面简要介绍。

1 BOOL Pause(void)

{

if(m_pMediaControl != NULL) // isplaying

{

m_pMediaControl->Pause();

return TRUE;

}

return FALSE;

}

2 BOOL Stop(void)

{

if(m_pMediaControl)

{

LONGLONG pos = 0;

m_pMediaControl->Stop();

m_pMediaSeeking->SetPositions(&pos,

AM_SEEKING_AbsolutePositioning ,

&pos,

AM_SEEKING_NoPositioning); // pos代表进度

m_pVideoWindow->put_Visible(OAFALSE);

m_pVideoWindow->Release();

long levCode;

m_pGraph->QueryInterface(IID_IMediaEvent, (void **)&m_pEvent);

m_pEvent->WaitForCompletion(INFINITE, &levCode);

m_pMediaControl->Release();

m_pMediaControl = NULL;

m_pEvent->Release();

m_pEvent = NULL;

return TRUE;

}

return FALSE;

}

3 BOOL SetVolume(long vol)

{

if(!m_pBasicAudio) return FALSE;

m_pBasicAudio->put_Volume(vol);// get_Volume可以得到当前音量

//注意,0为最大,-10000为最小,即静音。所以如果设置大于0, 则自动设为0

return TRUE;

}

4 BOOL SetChannel(int channel)

{

if(!m_pMpegAudioDec)

return;

// channel --- AM_MPEG_AUDIO_DUAL_LEFT为左声道

m_pMpegAudioDec->put_DualMode(channel);

}

5 BOOL SetPrecess(LONGLONG pro)

{

if(!m_pMediaSeeking)

return FALSE;

m_pMediaSeeking->SetPositions(&pos,

AM_SEEKING_AbsolutePositioning ,

&m_stoppos,

AM_SEEKING_AbsolutePositioning);

return TRUE;

}

6 BOOL SetPlayRate(double r)

{

if(!m_pMediaSeeking)

return FALSE;

m_pMediaSeeking->SetRate(r);

return TRUE;

}

7 BOOL FullScreen()

{

if(!m_pVideoWindow)

return FALSE;

m_pVideoWindow->put_Owner(NULL);//否则显示的仍旧限与对话框中pic控件大小

m_pVideoWindow->SetWindowPosition(0, 0, 1024,768);

return TRUE;

}

注意,最好在全屏的时候先保存pic的rect,在退出全屏的时候使用

另外,实际上m_pVideoWindow有put_FullScreenMode方法可以直接全屏,但是使用了这个方法之后,所有的键盘和鼠标消息将无法获取,即便设置了消息传递。所以,我采用了自绘实现全屏。

8 BOOL EscapeFullScreen()

{

if(!m_pVideoWindow)

return FALSE;

m_pVideoWindow->SetWindowPosition(m_winRect.left,

m_winRect.top,

m_winRect.Width(),

m_winRect.Height()); // m_winRect在全屏保存

PicCtrl->Invalidate();

m_pVideoWindow->put_Owner((OAHWND)m_hWnd);

return TRUE;

}

四、总结:到此这个播放器的基本功能就实现了,播放一般的视屏文件耗的资源相对较小(功能少)。同时对VCD格式(Mpeg1)的文件能够提取伴奏声道,实现Kara的效果,其他的文件都还无法实现,或许利用自己的Filter能让更多的媒体文件实现声道提取。DirectShow的接口功能之强大远非能想象出来,这里使用的仅仅是一些皮毛而已,有待于进一步的学习。如实现录音效果,设置麦克效果等。

DirectShow的音量控制

本来这个问题没有任何悬念,但是,事实上并不是简单调用一下IBasicAudio.put_Volume就成了。我的实现代码如下,已在调试中通过,多谢VC+DirectShow+AVS的“上海--阿易”兄的帮助。

private int[] volumes = new int[]{-10000,-6418,-6147,-6000,

-5892,-4826,-4647,-4540

-4477, -4162,-3876, -3614, -3500,

-3492,-3374,-3261,-3100,-3153,-3048,-2947,-2849,-2755,-2700,

-2663,-2575,-2520,-2489,-2406,-2325,-2280,-2246,-2170,-2095,-2050,

-2023,-1952,-1900, -1884,-1834, -1820, -1800,-1780, -1757,-1695,-1636,-1579,

-1521,-1500,-1464,-1436,-1420, -1408,-1353,-1299,-1246,-1195,-1144,

-1096,-1060, -1049,-1020,-1003,-957,-912,-868, -800, -774,-784, -760, -744,

-705,-667,-630,-610,-594,-570 ,-558,-525,-493,-462,-432,-403,

-375,-348,-322,-297,-285, -273,-250,-228,-207,-187,-176, -168,

-150,-102,-75,-19,-10,0,0};

/// <summary>

/// 获得、设置音量

/// </summary>

public int Volume

{

get

{

if (basicAudio == null) return 0;

int hr = 0, volume = 0;

hr = basicAudio.get_Volume(out volume);

DsError.ThrowExceptionForHR(hr);

foreach (int v in volumes)

if (v >= volume) { volume = v; break; }

return volume;

}

set

{

if (basicAudio == null) return;

if (value < 0) value = 0;

if (value >= 100) value = 99;

int hr = 0;

hr = basicAudio.put_Volume(volumes[value]);

DsError.ThrowExceptionForHR(hr);

}

}

本来,directshow中的音量范围是在-10000至0之间,但是我发现,0总是代表当前已有的音量,也就是说播放器只能在已有音量上减小,而不能有所增加。这是个很让人头痛的问题。阿易兄的vc版实现启发了我。