由于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版实现启发了我。
联系客服