用DirectShow来压缩一个AVI文件
一、选择一个压缩过滤器
有许多种方法可以压缩视频或者音频,比如:
a、本地DirectShow过滤器
b、视频压缩管理编码器(VCM)
c、音频压缩管理编码器(ACM)
d、DirectX媒体对象(DMOs)
系统设备枚举器提供了一个统一的方法来枚举和创建这些压缩器,我们不用考虑底层的操作。
代码:
//获取编解码器列表
// 初始化COM HRESULT hr = CoInitialize(NULL); ICreateDevEnum *pSysDevEnum = NULL; //使用CoCreateInstance函数创建系统枚举器组件对象,并获得ICreateDevEnum接口; hr = CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,(void**)&pSysDevEnum); if(FAILED(hr)) { return; } //使用接口方法ICreateDevEnum::CreateClassEnumerator为指定的Filter注册类型目录 //创建一个枚举器,并获得IEnumMoniker接口; IEnumMoniker *pEnumCat = NULL; hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat,0); if(hr == S_OK) { //枚举名称 IMoniker *pMoniker = NULL; ULONG cFetched; while(pEnumCat->Next(1,&pMoniker,&cFetched) == S_OK) { if(pMoniker) { WCHAR * wzDisplayName = NULL; IPropertyBag *pPropBag; IBaseFilter *pFilter = NULL; //获取当前设备的显示名字 //hr = pMoniker->GetDisplayName(NULL,NULL,&wzDisplayName); //调用IMoniker::BindToStorage之后,可以访问设备标识的属性集, //比如得到Display Name,Friendly Name等; hr = pMoniker->BindToStorage(0,0,IID_IPropertyBag, (void **)&pPropBag); if(SUCCEEDED(hr)) { //获得Filter的FriendlyName VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName",&varName,NULL); if(SUCCEEDED(hr)) { //调用IMoniker::BindToOject可以将设备标识生成一个DirectShow Filter, //随后调用IFilterGraph::AddFilter,并将之加入到FilterGraph中就可以参与工作了 //生成一个filter绑定到设备上。 hr = pMoniker->BindToObject(0,0,IID_IBaseFilter,(void**)&pFilter); } if(SUCCEEDED(hr)) { m_combodecode.AddString(CString(varName.bstrVal)); } //释放使用过的接口 if(pFilter) { pFilter->Release(); pFilter = NULL; } } pPropBag->Release(); } pMoniker->Release(); } pEnumCat->Release(); } pSysDevEnum->Release();
二、设置视频压缩属性
视频压缩过滤器可以在它的输出引脚支持IAMVideoCompression接口。使用这个接口可以设置压缩的属性,比如桢率,压缩质量等待。
首先,调用IBaseFilter::EnumPins方法找到过滤器的输出引脚,然后为接口查询引脚。一些过滤器不是所有的接口都支持,也有的不支持某个压缩属性。为了决定支持的属性能力,我们调用IAMVideoCompression::GetInfo来确定。这个方法返回一些信息:
a、一个设置性能的标识
b、一个描述字符串和版本字符串
c、默认的桢速率,质量等参数
暂时没研究,只是
//调用IAMVideoCompression::put_KeyFrameRate来设置桢速率。
hret = pAMCompress->put_KeyFrameRate(8);
三、建立压缩图形
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过滤器。但是如果源文件没有音频流,那么第二次调用会失败。
代码如下:
//a、 建立部分渲染的过滤图形,它包含AVI Mux 过滤器和File Writer过滤器。 //创建一个过滤器的实例,调用IMoniker::BindToObject方法。方法会返回一个IBaseFilter接口指针 pBuilder->SetOutputFileName( &MEDIASUBTYPE_Avi, //文件类型 dstFile.AllocSysString(), //文件名 &pMux, // 得到一个指向multiplexer的指针 NULL); // 得到一个指向File Write的指针 //b、 添加源过滤器和压缩过滤器。 //调用IgraphBuilder::AddSourceFilter方法来添加异步文件源过滤器 pGraph->AddSourceFilter(srcFile.AllocSysString(), L"Source Filter", &pSrc); if(decodeName.IsEmpty()) { MessageBox("请选择编解码器!","提示"); return; } pCompress = CreateDecodeDevice(CLSID_VideoCompressorCategory,decodeName); if (pCompress==NULL) { MessageBox("没有发现该压缩器!","提示",MB_ICONASTERISK); return; } //b、 添加源过滤器和压缩过滤器。 //调用IfilterGraph::AddFilter方法来添加视频压缩过滤器 pGraph->AddFilter(pCompress,L"Compressor"); IPin* pCompressIn,* pCompressOut; // 寻找支持 IAMVideoCompression的引脚 pCompressIn = FindPin(pCompress,PINDIR_INPUT) ; pCompressOut = FindPin(pCompress,PINDIR_OUTPUT); IAMVideoCompression * pAMCompress ; pCompressOut->QueryInterface(IID_IAMVideoCompression,(void**)&pAMCompress); HRESULT hret; //调用IAMVideoCompression::get_KeyFrameRate方法来得到关键桢的速率, //调用IAMVideoCompression::put_KeyFrameRate来设置桢速率。 hret = pAMCompress->put_KeyFrameRate(8); //c、 连接源过滤器到MUX 过滤器 //使用IcaptureGraphBuilder2::RenderStream方法来连接源过滤器的输出引脚到指定的过滤器。 pBuilder->RenderStream(NULL,NULL,pSrc,pCompress,pMux); HRESULT hr = pMux->QueryInterface(IID_IMediaSeeking, (void**)&pSeek); pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pEvent); hret = pEvent->SetNotifyWindow((OAHWND)m_hWnd,CM_NOTIFY,0); pMediaControl->Run();
暂时就了解这么多,希望高手指点。
亟待解决的问题是,如何将预览视频压缩后保存,边预览边保存。
源码地址:/detail/afu1972715000/8356333
如果觉得《Directshow学习笔记六-----重新压缩一个AVI文件(个人学习总结 仅供参考)》对你有帮助,请点赞、收藏,并留下你的观点哦!