之前一篇说了采集视频并实时进行H264编码,没有讲音频的实时编码,本篇将讲一个比较完整的例子,从DirectShow采集视音频,然后实时进行视音频的编码,最后封装成MP4。视频编码还使用之前用过的X264编码器,音频使用FAAC,这也比较常用的音频编码器,可直接在官网下载源码,最后将编码后的H264和AAC封装成MP4。
本篇将在前一篇(5.使用DirectShow进行摄像头采集并进行H264实时编码)的基础上进行,只要增加音频采集、编码以及最后的合成封装。不过视频的采集有和之前不一样的地方就是之前的编码工作直接在回调中进行的,本篇换了一种更好的方式,在回调中把每次过来的帧数据都放在一个array中保存起来,然后开启了一个处理线程,一边采集,一边编码处理,这样做比较合理一点,避免在回调中编码时花费大量时间而可能导致帧丢失问题。回调代码如下:
HRESULT STDMETHODCALLTYPE CSampleGrabberCB::BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen)
{
CString str;
//str.Format(_T("\n BufferCB--lBufferSize:%ld,lWidth:%d,lHeight:%d"), BufferLen, lWidth, lHeight);
//OutputDebugString(str);
//也是开始录制
if (m_bBeginEncode)
{
BYTE * pRgbData = new BYTE[BufferLen];
memcpy(pRgbData, pBuffer, BufferLen);
GrabDataInfo sData;
sData.pData = pRgbData;
sData.nDataSize = BufferLen;
sData.dSampleTime = SampleTime;
//把数据先存到array中
m_mxMsgLog.Lock();
m_arrGrabDataArr.Add(sData);
m_mxMsgLog.Unlock();
str.Format(_T("\n Video--BufferLen:%ld, SampleTime:%f \n"), BufferLen, SampleTime);
OutputDebugString(str);
if (m_bFirst)
{
m_bFirst = FALSE;
CString str;
str.Format(_T("\n Video--SampleTime:%f \n"), SampleTime);
OutputDebugString(str);
//开启线程来处理
AfxBeginThread(VideoDealFunc, this);
}
}
return 0;
}
m_arrGrabDataArr是一个结构,定义如下:
struct GrabDataInfo
{
BYTE *pData;
int nDataSize;
double dSampleTime;
GrabDataInfo()
{
pData = NULL;
nDataSize = 0;
dSampleTime = 0.0;
};
GrabDataInfo(const GrabDataInfo &other)
{
*this = other;
};
GrabDataInfo& operator = (const GrabDataInfo& other)
{
pData = other.pData;
nDataSize = other.nDataSize;
dSampleTime = other.dSampleTime;
return *this;
};
};
typedef CArray <GrabDataInfo, GrabDataInfo&> ASGrabDataInfoArray;
然后处理的部分全部放到线程中了,代码如下:
UINT VideoDealFunc(LPVOID lpVoid)
{
CSampleGrabberCB *pManage = (CSampleGrabberCB*)lpVoid;
if (pManage)
{
pManage->VideoDeal();
}
return 0;
}
void CSampleGrabberCB::VideoDeal()
{
//等待音频正式开始的第一个样本时间有了后再比较
while (!theApp.m_IsBegin)
{
Sleep(200);
}
double dSampleTime = theApp.m_nSampleTime;
m_nFrameIndex = 0;
int csp = X264_CSP_I420;
int width = lWidt