之前博客讲的一些DirectShow的相关应用,可能对很多人来说已经有些旧了,因为更新的MediaFoundation已经替代了DShow的位置。但MediaFoundation只支持win7后的系统,也就是说不支持XP,所以在实际商业应用中,就windows平台而言,dshow依然是实际应用中的主流,ffmpeg、opencv第三方库的采集也都支持dshow,所以现在依然是比较常用的技术。
本篇开始之前,有不少人私信我,问一些工程中存在的问题,本篇打算把这些问题尽可能的解决一下。所以本篇的主题就是对之前的代码进行改进,使得工程更具有实用性,以下是要改进的列表:
1.对编码后的数据进行实时封装;之前进行视音频采集、编码后,都生成了.h264的视频文件和.aac的音频文件,也就是说文件落地了,然后才封装成了MP4,本篇会进行实时封装,不再生成独立的视频和音频文件,自始至终只会有一个MP4文件,这更符合实际应用的场景。
2.对视音频进行同步;之前好多人说同步做的不好,其实针对dshow而言,视音频同步起来还是很简单的,后面会详细讲解。
3.对工程引用的库文件和头文件全部整理到工程目录中来,让大家一次就能编译过;因为之前的工程用到的库和头文件比较散乱,导致大家用起来不方便。
4.对部分代码、结构进行改进,增加一些有用的设置,使得项目尽可能的实用一点;
5.会尽可能地增加注释,以便大家看的更明白。
首先,说一下大致的逻辑:使用Dshow进行视音频采集,采集的过程中将音频和视频都放进同一个队列中去,然后开启一个线程,从队列中一个一个取出来进行实时编码,取到视频就用X264进行编码,取到音频就用faac编码,编码的数据不再落地,而是使用MP4V2写到MP4中,编码和封装的过程中会涉及到视音频同步的问题。下面一一讲解。
本篇的例子程序全部放到线程中做了,防止出现卡顿现象。当然,可能还会有一些问题,真正要实用化可能还需要再继续改进。
DSHOW视音频采集部分跟之前大致相同,但这里要说一下,每个视频采集设备的采集能力是不一样的,比如有的相机采集的源数据只支持YUY2,有的支持YUY2和RGB的,我手上还有相机是支持IJPG的,但大部分相机应该都是支持YUY2或RGB的,所以我在列出视频分辨率的时候,顺便把相机支持的源流类型也列出来了,代码如下:
void CMainDlg::GetVideoResolution()
{
if (m_pCapture)
{
m_arrCamResolutionArr.RemoveAll();
m_cbxResolutionCtrl.ResetContent();
IAMStreamConfig *pConfig = NULL;
//&MEDIATYPE_Video,如果包括其他媒体类型,第二个参数设置为0
HRESULT hr = m_pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
m_pVideoFilter, IID_IAMStreamConfig, (void **)&pConfig);
int iCount = 0, iSize = 0;
hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);
// Check the size to make sure we pass in the correct structure.
if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS))
{
// Use the video capabilities structure.
for (int iFormat = 0; iFormat < iCount; iFormat++)
{
VIDEO_STREAM_CONFIG_CAPS scc;
AM_MEDIA_TYPE *pmtConfig = NULL;
hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
if (SUCCEEDED(hr))
{
//(pmtConfig->subtype == MEDIASUBTYPE_RGB24) &&
if ((pmtConfig->majortype == MEDIATYPE_Video) &&
(pmtConfig->formattype == FORMAT_VideoInfo) &&
(pmtConfig->cbFormat >= sizeof (VIDEOINFOHEADER)) &&
(pmtConfig->pbFormat != NULL))
{
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmtConfig->pbFormat;
// pVih contains the detailed format information.
LONG lWidth = pVih->bmiHeader.biWidth;
LONG lHeight = pVih->bmiHeader.biHeight;
BOOL bFind = FALSE;
//是否已经存在这个分辨率,不存在就加入array
for (int n=0; n < m_arrCamResolutionArr.GetSize(); n++)
{
CamResolutionInfo sInfo = m_arrCamResolutionArr.GetAt(n);
if (sInfo.nWidth == lWidth && sInfo.nHeight == lHeight)
{
bFind = TRUE;
break;
}
}
if (!bFind)
{
CamResolutionInfo camInfo;
camInfo.nResolutionIndex = iFormat;
camInfo.nWidth = lWidth;
camInfo.nHeight = lHeight;
m_arrCamResolutionArr.Add(camInfo);
CString strSubType = _T("");
if (MEDIASUBTYPE_RGB24 == pmtConfig->subtype)
{
strSubType = _T("RGB24");
}
else if (MEDIASUBTYPE_RGB555 == pmtConfig->subtype)
{
strSubType = _T("RGB555");
}
else if (MEDIASUBTYPE_RGB32 == pmtConfig->subtype)
{
strSubType = _T("RGB32");
}
else if (MEDIASUBTYPE_RGB565 == pmtConfig->subtype)
{
strSubType = _T("RGB565");
}
else if (MEDIASUBTYPE_RGB8 == pmtConfig->subtype)
{
strSubType = _T("RGB8");
}
else if (MEDIASUBTYPE_IJPG == pmtConfig->subtype)
{
strSubType = _T("IJPG");
}
else if (MEDIASUBTYPE_YUY2 == pmtConfig->subtype)
{
strSubType = _T("YUY2");
}
else if (MEDIASUBTYPE_YUYV == pmtConfig->subtype)
{
strSubType = _T("YUYV");
}
else if (MEDIASUBTYPE_H264 == pmtConfig->subtype)
{
strSubType = _T("H264");
}
else if (MEDIASUBTYPE_MJPG == pmtConfig->subtype)
{
strSubType = _T("MJPG");
}
else if (MEDIASUBTYPE_Y41P == pmtConfig->subtype)
{
strSubType = _T("Y41P");
}
else