转载请注明出处:http://blog.csdn.net/horkychen 一篇早期写的资料。
Day 1:
我是个DirectShow新手,我基于CCaptureVideo类,写了一个简单的测试程序,界面如下图,代码在这个Group的SkyDriver/Codes目录里,使用DirectX SDK9 2004 Summer Update编译通过。
今天主要做了两件事:
一. 加了Video Capture时间长短控制:
a.在CCaptureVideo中定义了一个m_MaxTime,和一个成员函数SetTimeLimitation来接受UI的设定,以秒为单位,然后在IMediaControl呼叫Run之前设定时间:
if(m_MaxTime>0)
{
// Control the video capture stream.
REFERENCE_TIME rtStop = 10000000 * m_MaxTime;
const WORD wStartCookie = 1, wStopCookie = 2; // Arbitrary values.
hr = m_pCapture->ControlStream(
&PIN_CATEGORY_CAPTURE, // Pin category.
&MEDIATYPE_Video, // Media type.
m_pBF, // Capture filter.
NULL, &rtStop, // Start and stop times.
wStartCookie, wStopCookie // Values for the start and stop events.
); if(FAILED(hr))
{
AfxMessageBox(_T("Couldn’t control the graph!"));
} }
经过在PC上实验,如果设定n秒,则录制的Video时间为n-1秒,大家可以再试试看!
二.加了时间限制后,当录制结束,文件写入已经停止,但UI并没有回复正常,所以还要监测结束事件。于是有了BindMessageWindow函数,从Init函数传入两个句柄:
HRESULT Init(int iDeviceID, HWND hPreviewWnd, HWND hBaseWnd)
hPreviewWnd代表预览窗口
hBaseWnd代表主消息窗口
然后使用IMediaEventEx将消息绑定到主窗口之上(WM_GRAPHNOTIFY),这些都是DirectShow MSDN上所说明的方法:
HRESULT CCaptureVideo::BindMessageWindow()
{
HRESULT hr = S_OK;
if(m_hBaseWnd)
{
hr = m_pGB->QueryInterface(IID_IMediaEventEx, (void **)&m_pEvent);
if (FAILED(hr))return hr;
m_pEvent->SetNotifyWindow((OAHWND)m_hBaseWnd,WM_GRAPHNOTIFY,0);
}
return S_OK;
}
然后在主窗口上,使用其WndProc函数来侦听,得到WM_GRAPHNOTIFY后使用IMediaEventEx的GetEvent方法(我把它包在long CCaptureVideo::GetCurrentEvent()函数中了),如果发现事件是EC_STREAM_CONTROL_STOPPED,表示已经结束录制了,就呼叫STOP的Click事件处理程序,来结束录制。
LRESULT CCameraCaptureDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
if( WM_GRAPHNOTIFY == message)
{
HandleGraphEvent();
}
return CDialog::WindowProc(message, wParam, lParam);
}
void CCameraCaptureDlg::HandleGraphEvent()
{
long eventCode = m_cap.GetCurrentEvent();
TCHAR str[256] = {0};
wsprintf(str,_T("Get Event:%d"),eventCode);
m_ListLog.AddString(str);
switch(eventCode)
{
case EC_STREAM_CONTROL_STOPPED:
if(GetDlgItem(IDC_STOP)->IsWindowEnabled())
OnCommand(IDC_STOP,NULL);
break;
default:
break;
}
}
这些就是今天DirectShow的进展!
Day 2:
今天原本要写个File Writer测试,后来才发现一直使用AVI进行测试,并不能代表录制ASF时有相同的问题,并且录制ASF时使用的是WM ASF Writer,所以实现了ASF录制功能。遇到如下几个问题:
1.需要安装Windows Media SDK. (我用了Windows Media SDK 9),主要是CLSID_WMAsfWriter之类的定义。当然也要引用WMVCORE.lib
2.在PC下,ASF的录制必须安装Audio Capture Filter,否则会出现"Unspecified Error! 0x80004005"!
Audio Filter的安装包括两步: a. Bind the filter b.Call RenderStream for Audio Filter.
详见代码中的BindFilter和Init函数的修改!
因为ASF文件包含很多的profile,可以使用DirectShow带的Profile Emulator来查看。其中也有(No Audio)的profile,可以不使用Audio Capture Filter,而单独录制视频! 所以它的行为和指定的Profile有很大的关系!
3.在PC下,ASF的录制是会自动增长的。所以并没有实质解决G2为什么没有自动增长的问题! 我在PC上也遇到一个问题,我必须选中输出的文件,才能看到文件大小的改变。G2或许有一样的问题!
完成后的Graph应当有如下的Filters,其中的Renderer是用来作为Preview的。
完整的代码,也已经传到本组的SkyDrive的Codes目录下,也包括展开后的Windows Media SDK.
下面附上一段,没有使用到代码,用来选择相应的profile,但对于其第一个参数从何而来,还不清楚!
HRESULT CCaptureVideo::MapProfileIdToProfile(int iProfile, IWMProfile **ppProfile)
{
DWORD cProfiles;
if (!ppProfile)
return E_POINTER;
*ppProfile = 0;
CComPtr <IWMProfileManager> pIWMProfileManager;
HRESULT hr = WMCreateProfileManager( &pIWMProfileManager );
if(FAILED(hr))
{
printf("MapProfile: Failed to create profile manager! hr=0x%x\n", hr);
return hr;
}
// We only use 7_0 profiles
CComQIPtr<IWMProfileManager2, &IID_IWMProfileManager2> pIPM2(pIWMProfileManager);
if(!pIPM2)
{
printf("MapProfile: Failed to QI IWMProfileManager2!\n");
return E_UNEXPECTED;
}
hr = pIPM2->SetSystemProfileVersion( WMT_VER_7_0 );
if(FAILED(hr))
{
printf("MapProfile: Failed to set system profile version! hr=0x%x\n", hr);
return hr;
}
hr = pIWMProfileManager->GetSystemProfileCount( &cProfiles );
if(FAILED(hr))
{
printf("MapProfile: Failed to get system profile count! hr=0x%x\n", hr);
return hr;
}
// Invalid profile requested?
if( (DWORD)iProfile >= cProfiles )
{
printf("Invalid profile: %d\n", iProfile);
return E_INVALIDARG;
}
return (pIWMProfileManager->LoadSystemProfile( iProfile, ppProfile ));
}
Day 3:
在写入ASF文件时,有两种创建Graph的方式:
1.手动创建File Writer和FileSink
hr = CoCreateInstance(CLSID_WMAsfWriter, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, (void**)&m_pMux);
if(FAILED(hr))
return hr;
hr = m_pGB->AddFilter(m_pMux, L"WM ASF Writer");
m_pMux->QueryInterface(IID_IFileSinkFilter,(void**) &m_pSink);
hr=m_pSink->SetFileName(L"C:\\test.wmv",NULL);
SAFE_RELEASE(m_pSink);
2.调用ICaptureGraphBuilder的SetOutputFileName方法
hr = m_pCapture->SetOutputFileName(
&MEDIASUBTYPE_Asf, // File type.
_T("c:\\test.wmv"), // File name, as a wide-character string.
&m_pMux, // Receives a pointer to the multiplexer.
&m_pSink); // Receives a pointer to the file writer.
两者生成的Graph是不同的。前者会使用WM ASF Writer写入文件,而后则使用MUX混合音频和视频后,使用File Writer来写入文件。
另外在使用<<DirectShow实务指南>>第2章的AVCap时,发现在公司的机器上,视频被分成5段,在家里则是正常,分析了一下,发现是启用DMR产生的问题,如下:
VMR(Video Mixing Renderer)是DirectShow的新一代Video Renderer。VMR有两个版本:VMR-7和VMR-9。前者采用了DirectDraw 7技术,仅仅在Windows XP操作系统下可以获得,并且是XP上默认的用于视频显示的Renderer(代替传统的Video Renderer);后者采用了Direct3D 9的技术,是随DirectX9.0一起发布的,但任何时候都不是默认的Renderer。
安装了DirectX9.0以后,就有4个Video Renderer可供选择使用:传统的Video Renderer、Overlay Mixer、VMR-7和CMR-9。
VMR主要利用了显卡专有的图形处理能力(VMR做视频的合成和显示时并不占用系统的CPU资源),它能够表现的性能对于硬件的依赖性很高。
在MSDN有更为详细的解析,MSDN: http://msdn.microsoft.com/en-us/library/dd407295(VS.85).aspx
这和咱们的最终目的关系不大,不再深入。但它引出一个问题,我们的Camera的输出是什么格式的数据? 我在程序中加入了枚举输出格式的检测(CCaptureVideo::EnumVideoMediaType函数,参考CAnalogInputFilter的相应函数)。
发现在公司的摄像头仅支持YUY2 (通用驱动),而家里则支持RGB24和AYUV(厂商提供的驱动),这些格式对于后面写Encoder十分重要,应当需要了解:
权威网站: http://www.fourcc.org/yuv.php
AYUV Combined YUV and alpha
YUY2 YUV 4:2:2 as for UYVY but with different component ordering within the u_int32 macropixel.
有了YUV数据,再转成MPEG,似乎是一套比较成熟的技术, ffmpeg是支持的。
*今天的代码还加入了对Video Caputer Filter的Property Page的显示! 还得了一篇有关如何写Filter的文章,我也放到SkyDrive里分享。希望对大家有用,我在这里抛砖,可能抛不了多久了,欢迎多多讨论!