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