分享

DirectShow播放器

 我喝多了 2014-10-30

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


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多