原文出处:http://littleflute.blog.hexun.com/1043233_d.html

这里我们讲解怎样用DirectShow来压缩一个AVI文件。我们重点放到视频压缩,同样的方法可以应用到音频压缩。
我们分以下几步来讲解:
1、 选择一个压缩过滤器
有许多种方法可以压缩视频或者音频,比如:
a、 本地DirectShow过滤器
b、 视频压缩管理编码器(VCM)
c、 音频压缩管理编码器(ACM)
d、 DirectX媒体对象(DMOs)
在DirectShow中,VCM被AVI Compressor过滤器封装了。同样的,ACM编码器也被ACM Wrapper过滤器封装了。DMOs被DMO Wrapper过滤器封装。系统设备枚举器提供了一个统一的方法来枚举和创建这些压缩器,我们不用考虑底层的操作。
枚举设备方法请参照前面的讲述。这里我们只给出代码:
void OnInitDialog(HWND hDlg)
{
  HRESULT hr;
  ICreateDevEnum *pSysDevEnum = NULL;
  IEnumMoniker *pEnum = NULL;
  IMoniker *pMoniker = NULL;

hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
    CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, 
    (void**)&pSysDevEnum);
  hr = pSysDevEnum->CreateClassEnumerator(
       CLSID_VideoCompressorCategory, &pEnum, 0);
  
  while (S_OK == pEnum->Next(1, &pMoniker, NULL))
  {
    IPropertyBag *pPropBag = NULL;
    pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
                (void **)&pPropBag);
    VARIANT var;
    VariantInit(&var);
    hr = pPropBag->Read(L"FriendlyName", &var, 0);
    if (SUCCEEDED(hr))
    {
      LRESULT iSel = AddString(GetDlgItem(hDlg, 
            IDC_CODEC_LIST), var.bstrVal);
    }  
    VariantClear(&var); 
    pPropBag->Release();
    pMoniker->Release();
  }

SendDlgItemMessage(hDlg, IDC_CODEC_LIST, 
            LB_SETCURSEL, 0, 0);
  pSysDevEnum->Release();
  pEnum->Release();
}
创建一个过滤器的实例,调用IMoniker::BindToObject方法。方法会返回一个IBaseFilter接口指针。就象下面那样:
IBaseFilter *pFilter = NULL;
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, 
                    (void**)&pFilter);
if (SUCCEEDED(hr))
{
  // 可以使用过滤器了
  // 记着释放IBaseFilter接口指针
}
2、 设置视频压缩属性
视频压缩过滤器可以在它的输出引脚支持IAMVideoCompression接口。使用这个接口可以设置压缩的属性,比如桢率,压缩质量等待。
首先,调用IBaseFilter::EnumPins方法找到过滤器的输出引脚,然后为接口查询引脚。一些过滤器不是所有的接口都支持,也有的不支持某个压缩属性。为了决定支持的属性能力,我们调用IAMVideoCompression::GetInfo来确定。这个方法返回一些信息:
a、 一个设置性能的标识
b、 一个描述字符串和版本字符串
c、 默认的桢速率,质量等参数
它按照下面的语法调用:
hr = pCompress->GetInfo(pszVersion, &cbVersion, pszDesc, &cbDesc, 
     &lKeyFrame, &lPFrame, &dblQuality, &lCap);

pszVersion和pszDesc参数是接收版本和描述字符串的宽字符缓冲区。cbVersion和cbDesc参数接收被请求的缓冲区大小。IkeyFrame,lPFrame和dblQuality参数获得默认的桢速率,p桢率和质量。质量是用从0.0到1.0的浮点数来表示的。Icap参数获得一个被或的能力标识,这个被定义为CompressionCaps枚举类型。
这些参数的任何一个都可以设置为NULL,。例如如果为了获得版本和描述字符串,第一次调用方法的时候,第一个和第三个参数为NULL。使用返回值cbVersion和cbDesc来分配缓冲区,然后再次调用该方法:
int cbVersion, cbDesc; // Size in bytes, not characters!
hr = pCompress->GetInfo(0, &cbVersion, 0, &cbDesc, 0, 0, 0, 0);
if (SUCCEEDED(hr))
{
   WCHAR *pszVersion = new WCHAR[cbVersion/2]; 
   WCHAR *pszDesc = new WCHAR[cbDesc/2];
   hr = pCompress->GetInfo(pszVersion, 0, pszDesc, 0, 0, 0, 0, 0);
}
iCap参数提供了对IAMVideoCompression方法的过滤器的支持能力。例如,如果iCap包含了CompressionCaps_CanKeyFrame标志,你就可以调用IAMVideoCompression::get_KeyFrameRate方法来得到关键桢的速率,调用IAMVideoCompression::put_KeyFrameRate来设置桢速率。如果iCap的值没有包含这些参数,那么就只能使用默认的值了。
if (lCap & CompressionCaps_CanKeyFrame)
{
   hr = pCompress->get_KeyFrameRate(&lKeyFrame);
   if (FAILED(hr) || lKeyFrame < 0)
   {
     lKeyFrame = lDefaultKeyFrame; // 来自 GetInfo.
   }
}

下面的代码试着在输出引脚寻找IAMVideoCompression接口。如果成功,它将会返回默认的和实际的压缩属性值。
HRESULT hr = E_FAIL;
IEnumPins *pEnum = NULL;
IPin *pPin = NULL;
IAMVideoCompression *pCompress = NULL;

// 寻找支持 IAMVideoCompression的引脚
pFilter->EnumPins(&pEnum);
while (S_OK == pEnum->Next(1, &pPin, NULL))
{
   hr = pPin->QueryInterface(IID_IAMVideoCompression, (void**)&pCompress);
   pPin->Release();
   if (SUCCEEDED(hr)) // Found the interface.
   {
     break;
   }
}
if (SUCCEEDED(hr)) 
{
   long lCap;           // 性能标识
   long lKeyFrame, lPFrame;    // 真实值
   double m_Quality;
   long lKeyFrameDef, lPFrameDef; //默认值
   double QualityDef;
   
  //得到默认值和它的性能
   hr = pCompress->GetInfo(0, 0, 0, 0, &KeyFrameDef, &lPFrameDef,
       &QualityDef, &lCap);
   if (SUCCEEDED(hr))
   {
     // 得到实际的值.
     if (lCap & CompressionCaps_CanKeyFrame)
     {
       hr = pCompress->get_KeyFrameRate(&lKeyFrame);
       if (FAILED(hr) || lKeyFrame < 0)
         lKeyFrame = lKeyFrameDef;
     }
     if (lCap & CompressionCaps_CanBFrame)
     {
       hr = pCompress->get_PFramesPerKeyFrame(&lPFrame);
       if (FAILED(hr) || lPFrame < 0)
         lPFrame = lPFrameDef;
     }
     if (lCap & CompressionCaps_CanQuality)
     {
       hr = pCompress->get_Quality(&Quality);
       if (FAILED(hr) || Quality < 0)
         Quality = QualityDef;
     }
   }
}

注意:如果你使用IcaptureGraphBuilder2接口来创建你的过滤图形,你可以调用IcaptureGraphBuilder2::FindInterface方法来获得IAMVideoCompression接口。
3、 建立压缩图形
下图是一个典型的AVI文件的过滤图形:

AVI_Splitter过滤器从文件的源过滤器(File Source(Async))拉数据,然后分解到视频和音频流。视频解压缩过滤器解码被压缩的视频,然后重新被视频压缩器重新压缩。
被压缩的视频进入到AVI Mux过滤器。音频流在这个例子中没有被压缩,因此它直接从AVI Splitter传输到AVI Mux。AVI Mux进行隔行扫描,然后使用File Write过滤器将数据输出到磁盘上。注意,就算原始文件里面没有音频流,AVI Mux过滤器也是必须的。最简单的方法创建这种过滤图形就是使用Capture Graph Builder,这是DirectShow里面为了建立捕获图形或者别的定制的过滤图形的一个部件。
注意:DirectShow里面包含了两个Capture Graph Builder版本。它们提供了不同的接口和类的标识。早期的版本类标识是CLSID_CaptureGraphBuild,接口是IcaptureGraphBuilder。它兼容存在的应用程序。新版本的类标识是CLSID_CaptureGraphBuilder2新的接口名称是IcaptureGraphBuilder2。新的接口比老的接口有更多的灵活性。
创建Capture Graph Builder我们还是使用CoCreateInstance:
ICaptureGraphBuilder2 *pBuild = NULL;
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, 
             NULL, CLSCTX_INPROC_SERVER,
   IID_ICaptureGraphBuilder2, (void **)&pBuild);
然后我们使用Capture Graph Builder来建立过滤图形:
a、 建立部分渲染的过滤图形,它包含AVI Mux 过滤器和File Writer过滤器。
b、 添加源过滤器和压缩过滤器。
c、 连接源过滤器到MUX 过滤器。
下面逐步的解释每一个细节:
建立渲染段
为了建立过滤图形的渲染段,调用IcaptureGraphBuilder2::SetOutputFileName方法。它返回一个MUX的过滤器和File Write的指针。MUX是下面建立过滤图形所需要的,但是这个例子不需要File Write,因此,它的参数为NULL。
IBaseFilter *pMux = NULL;
pBuild->SetOutputFileName(
     &MEDIASUBTYPE_Avi, //文件类型
     wszOutputFile,   // 文件名
     &pMux,       // 得到一个指向multiplexer的指针
    NULL);       // 得到一个指向File Write的指针

当该方法返回,MUX过滤器有一个很明显的参考计数,所以以后一定要确保释放它。MUX过滤器提供了两个接口来控制AVI格式:
IconfigInterleaving接口:设置交错模式
IconfigAviMux接口:设置主流和AVI兼容性的索引
添加源过滤器和压缩过滤器
下一步我们要在过滤图形中添加源过滤器和压缩过滤器。当你调用SetOutputFileName的时候,Capture Graph Builder会自动的创建一个过滤图形管理器的实例。你可以调用IcaptureGraphBuilder::GetFiltergraph方法来获得刚才创建的过滤图形管理器的指针。
IGraphBuilder *pGraph = NULL;
pBuild->GetFiltergraph(&pGraph);
现在我们该调用IgraphBuilder::AddSourceFilter方法来添加异步文件源过滤器,然后调用IfilterGraph::AddFilter方法来添加视频压缩过滤器:
IBaseFilter *pSrc = NULL;
pGraph->AddSourceFilter(wszInputFile, L"Source Filter", &pSrc);
pGraph->AddFilter(pVComp, L"Compressor");
到了这一步我们的状态就象下图那样,源过滤器和压缩过滤器没有和别的任何过滤器连接。

连接源到Mux
最后一步就是通过视频压缩过滤器连接源过滤器到AVI Mux过滤器。我们使用IcaptureGraphBuilder2::RenderStream方法来连接源过滤器的输出引脚到指定的过滤器。
前两个参数指定了用那个源过滤器的引脚来连接,通过指明引脚的分类和媒体类型来实现。异步文件源过滤器只有一个输出引脚,所以这些参数要设置成NULL。后三个参数指定了源过滤器,压缩过滤器,和Mux过滤器。
下面的代码演示了通过视频压缩过滤器来渲染视频流:
pBuild->RenderStream(
    NULL,    // 输出引脚类型
    NULL,    // 媒体类型
    pSrc,    // 源过滤器
    pVComp,   // 压缩过滤器
    pMux);   
假定源文件包含了音频流,AVI Splitter过滤器会在输出引脚输出音频流。为了连接这个管脚我们需要再次调用RenderStream:
pBuild->RenderStream(NULL, NULL, pSrc, NULL, pMux);
这里我们没有指定压缩过滤器。而且源过滤器的输出引脚已经连接了,因此RenderStream方法会搜索一个未连接的输出引脚到Splitter过滤器。它可以直接连接引脚到MUX过滤器。但是如果源文件没有音频流,那么第二次调用会失败。
4、 写文件
如果想写文件正常的进行,你必须调用ImediaControl::Run方法来运行过滤图形。等到播放完成后调用ImediaControl::Stop。
如果要显示文件的写的进度,你可以使用ImediaSeeking来查询Mux过滤器。调用ImediaSeeking::GetDuration方法来获得文件的持续时间。定时的使用ImediaSeeking::GetCurrentPostition方法来获得当前的位置,但必须要是过滤图形运行中。
注意,一般情况下我们用ImediaSeeking接口查询过滤图形管理器来定位,如果正在写文件的话,这将是一个特例。这里我们要查询Mux过滤器就可以了。查询过滤图形定位要求回放的时候才可以,而不是写文件的时候。
看了下面的代码读者就会更加清楚了。
IMediaSeeking *pSeek = NULL;
IMediaEventEx *pEvent = NULL;
IMediaControl *pControl = NULL;
REFERENCE_TIME rtTotal;

hr = pMux->QueryInterface(IID_IMediaSeeking, (void**)&pSeek);
hr = pGraph->QueryInterface(IID_IMediaEventEx, (void**)&pEvent);
hr = pGraph->QueryInterface(IID_IMediaControl, (void**)&pControl);

// 设置DirectShow的时间通报
hr = pEvent->SetNotifyWindow((OAHWND)hwnd, WM_GRAPHNOTIFY, 0);

hr = GetDuration(&rtTotal);
SendDlgItemMessage(hwnd, IDC_PROGRESS1, PBM_SETRANGE, 0, 
  MAKELPARAM(0, rtTotal / 10000000));
// 开始时间.
UINT_PTR res = SetTimer(hwnd, nIDEvent, 100, NULL);
// 运行过滤图形.
pControl->Run();
当应用程序收到定时器事件,它就更新当前位置:
void OnTimer(HWND hDlg, IMediaSeeking *pSeek)
{
  REFERENCE_TIME rtNow;
  HRESULT hr = pSeek->GetCurrentPosition(&rtNow);
  if (SUCCEEDED(hr))
  {
    SendDlgItemMessage(hDlg, IDC_PROGRESS1, PBM_SETPOS, rtNow/10000000, 0);
  }
}
当应用程序收到DirectShow结束事件,它就可以停止图形,就象下面那样:
LRESULT CALLBACK WndProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
  /* ... */
  case WM_GRAPHNOTIFY:
    DoHandleEvent();
    break;
  /* ... */
  }
}

void DoHandleEvent()
{
  long evCode, param1, param2;
  bool bComplete = false;
  if (!pEvent) return;

//得到所有的事件
  while (SUCCEEDED(pEvent->GetEvent(&evCode, &param1, &param2, 0))
  {
    pEvent->FreeEventParams(evCode, param1, param2);
    switch(evCode)
    {
      case EC_USERABORT:
      case EC_ERRORABORT:
      case EC_COMPLETE:
        bComplete = true;
        break;
    }
  }
  if (bComplete)
  {
    pControl->Stop(); // Important! You must stop the graph!

//关掉事件通报.
    pEvent->SetNotifyWindow(NULL, 0, 0);
    pEvent->Release();
    pEvent = NULL;
    SendDlgItemMessage(IDC_PROGRESS1, PBM_SETPOS, 0, 0);
    KillTimer(hwnd, nIDEvent);
  }
}

怎样用DirectShow来压缩一个AVI文件相关推荐

  1. Directshow学习笔记六-----重新压缩一个AVI文件(个人学习总结,仅供参考)

    用DirectShow来压缩一个AVI文件 一. 选择一个压缩过滤器 有许多种方法可以压缩视频或者音频,比如: a. 本地DirectShow过滤器 b. 视频压缩管理编码器(VCM) c. 音频压缩 ...

  2. 重新压缩一个AVI文件

    效果图: directshow文档中有一节关于重新压缩一个AVI,按照文章的思路: 1.选择一个压缩Filter 2.设置视频压缩属性 3.生成重新压缩graph 4.写文件 给出所有源代码(文档中已 ...

  3. 如何查看一个avi文件的播放帧率?和帧数

    如何查看一个avi文件的播放帧率?和帧数 右键属性,详细信息: 如何查看avi文件的帧数? 拖拽到imageJ中,在左上角就可以看到:

  4. 如何把未压缩的.avi文件批量地转为.yuv文件(yuv420)?

    clear clc cd('F:\qcifAvi');myFile = dir('*.avi'); % 无损压缩的.avi文件集合 for i = 1 : length(myFile) % qcifA ...

  5. C#将图像文件压缩为AVI文件播放

    using System; using System.Runtime.InteropServices; using System.Drawing; using System.Drawing.Imagi ...

  6. AVI文件规范(更新版)

    AVI文件规范 本文主要参考了如下资料: http://blog.csdn.net/sunshine1314/archive/2007/10/14/1824432.aspx 格式规范 http://b ...

  7. vc++实现avi文件的操作 用于视频解析及录制(转)

    vc++实现avi文件的操作 为了对avi进行读写,微软提供了一套API,总共50个函数,他们的用途主要有两类,一个是avi文件的操作,一类是数据流streams的操作. 1.打开和关闭文件 AVIF ...

  8. AVI文件数据流操作

    Video for Windows (VFW)在16位Windows中就引入了,现在的很多功能被DirectX所代替或超越了.但是还是有一些用处的. Audio-video-interleaved f ...

  9. vc++实现avi文件的操作

    vc++实现avi文件的操作 为了对avi进行读写,微软提供了一套API,总共50个函数,他们的用途主要有两类,一个是avi文件的操作,一类是数据流streams的操作. 1.打开和关闭文件 AVIF ...

最新文章

  1. 使用Python,OpenCV寻找图像中的轮廓
  2. 数据为王的时代,如何用图谱挖掘商业数据背后的宝藏?
  3. 大数据虚拟化零起点-5基础运维第四步-部署vSphere Big Data Extensions 1.0 vApp
  4. hdu 3074 线段树 OR 树状数组
  5. 对飞行前请求的响应未通过访问控制检查:它没有http ok状态。_对不起,看完这篇HTTP,真的可以吊打面试官...
  6. 简述python执行原理_Python程序的执行原理(1)(2)
  7. dubbo 分组后,dubbo-admin 看不到服务
  8. zb怎么做渲染图_如何在ZBrush中渲染漫画风格的插画
  9. 使用系统定时器SysTick实现精确延时微秒和毫秒函数
  10. 情侣在公交车上接吻遭乘客指责
  11. 怪事:两台linux的samba共享目录,一个能访问一个不能访问
  12. 六年级下计算机课ppt课件ppt课件,小学信息技术浙摄影版六年级上册第1课 走进计算机说课ppt课件...
  13. 安装vs遇到 不受信任的证书或者 时间戳签名和/或证书无法验证或已损坏 解决方案
  14. 开源 免费使用 打包下载 自行部署 :升讯威 周报系统 2.0 最新版
  15. Android 混淆配置
  16. php ini 分号,当分号(;)被包含在值中时,用PHP解析INI文件
  17. 第三阶段应用层——1.12 数码相册—interval_page设置时间间隔界面的显存管理、页面规划、输入控制
  18. 你知道PDF怎么合并吗?这些技巧快来码住
  19. 网络学习:路由器 如何实现不同网络互连(ip协议) 和 和内网访问外网( nat转化协议)
  20. Peekaboo——项目系统设计与数据库设计

热门文章

  1. ug后处理安装步骤是什么?ug后处理怎么添加?ug后处理文件位置路径?ug10后处理安装步骤?
  2. java1到100的阶乘之和
  3. C++三只小猪称体重案例
  4. 如何检查Linux系统下的Virtio 驱动
  5. 【机试备考】Day15-Special数 | 平方数立方数判断
  6. C# P3色域计算、计算色域覆盖率、已知坐标求多边形面积计算、已知两条线的坐标求交点计算、坐标顺时针排序计算
  7. 一套免费开源Java充电桩平台
  8. 程序员表白html倒计时,火热的程序员表白方式,调皮弹窗表白代码,赶紧拿去试试吧...
  9. usb接上计算机没反应怎么办,usb硬盘插上电脑没反应怎么办,小编教你怎么解决...
  10. 31 SpringBoot多环境的切换(生产环境、开发环境、测试环境)