上一篇講了怎么把視音頻采集下來并合成一個AVI文件,但我們看這個AVI文件就發現,雖然很清晰,但就是大小太大了,錄制短短10秒,可能就有100M以上,而且還有一個文件,就是錄制只能是打開采集時開始,停止采集時停止,不能預覽的時候隨心所欲地錄制。本篇就是要解決這些問題。
之前有一篇(使用DShow進行采集拍照)在講實時拍照時曾用到過ISampleGrabber來抓取圖像,然后設置緩存,從緩存中取數據然后生成圖片,本篇也使用ISampleGrabber,但不使用緩存的方式,而是使用回調的方式抓取圖像,在回調中先將RGB24的幀轉換為YUV420,然后使用第三方的編碼器X264對其進行編碼。下面我們來做做看。大致的代碼跟實時拍照那一篇差不多,不過設置回調的地方不一樣,代碼如下:
//設置視頻分辨率、格式 IAMStreamConfig *pConfig = NULL; m_pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pVideoFilter, IID_IAMStreamConfig, (void **) &pConfig); AM_MEDIA_TYPE *pmt = NULL; VIDEO_STREAM_CONFIG_CAPS scc; pConfig->GetStreamCaps(nResolutionIndex, &pmt, (BYTE*)&scc); //nResolutionIndex就是選擇的分辨率序號 pmt->majortype = MEDIATYPE_Video; pmt->subtype = MEDIASUBTYPE_RGB24; //抓取RGB24 pmt->formattype = FORMAT_VideoInfo; pConfig->SetFormat(pmt); m_pGrabberFilter->QueryInterface(IID_ISampleGrabber, (void **)&m_pGrabber); HRESULT hr = m_pGrabber->SetMediaType(pmt); if(FAILED(hr)) { AfxMessageBox(_T("Fail to set media type!")); return; } //是否緩存數據,緩存的話,可以給后面做其他處理,不緩存的話,圖像處理就放在回調中 m_pGrabber->SetBufferSamples(FALSE); m_pGrabber->SetOneShot(FALSE); mCB.lWidth = nSetWidth; mCB.lHeight = nSetHeight; //設置回調,在回調中處理每一幀 m_pGrabber->SetCallback(&mCB, 1); hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, m_pVideoFilter, m_pGrabberFilter, NULL); if( FAILED(hr)) { AfxMessageBox(_T("RenderStream failed")); return; }代碼中mCB是一個類的實例,這個類是繼承至ISampleGrabberCB的,所以程序中要新建一個類,讓其繼承至ISampleGrabberCB。其他代碼都差不多,啟動預覽后,回調中的BufferCB函數就不斷能收到數據,這些數據是每收到一次就是一幀的數據,所以編碼的工作主要在這里進行。本篇使用的H264編碼器是大名鼎鼎的X264,編碼效率高而小巧,源碼下載地址:http://www.videolan.org/developers/x264.html。Windows環境下要下載mingw編譯器來編一下,生成一個DLL和一個lib庫拷貝到自己的工程中,再到源碼中把下面這三個頭文件拷貝到你的工程中

注意,編出來的dll可能帶版本后綴,請去掉,否則你的程序可能不認,比如我編出來的dll是libx264-148.dll,改成libx264.dll
你在程序中使用X264,下面這樣調用即可(路徑問題請自己添加好)
extern "C"{#include "x264.h" };#pragma comment(lib,"libx264.lib")
下面說說怎么進行編碼吧,當錄制開始的時候,收到一幀后,要先轉換為YUV420,我們知道,之前抓取圖像的時候已經設置了抓取的為RGB24。具體轉換按照一定的算法進行即可,網上這樣的算法很多,我也下了一個,具體就不展示了。
//每一幀大小ULONG nYUVLen = lWidth * lHeight + (lWidth * lHeight)/2;BYTE * yuvByte = new BYTE[nYUVLen];//先把RGB24轉為YUV420RGB2YUV(pBuffer, lWidth, lHeight, yuvByte, &nYUVLen);
轉換后,使用X264進行編碼,代碼如下:
int csp = X264_CSP_I420; int width = lWidth; int height = lHeight; int y_size = width * height; //剛開始打開要初始化一些參數 if (m_bFirst) { m_bFirst = FALSE; CTime time = CTime::GetCurrentTime(); CString szTime = time.Format("%Y%m%d_%H%M%S.h264"); CString strSavePath = _T(""); strSavePath.Format(_T("%s%s"), m_sSavePath, szTime); USES_CONVERSION; string strFullPath = W2A(strSavePath); m_fp_dst = fopen(strFullPath.c_str(), "wb"); m_pParam = (x264_param_t*)malloc(sizeof(x264_param_t)); //初始化,是對不正確的參數進行修改,并對各結構體參數和cabac編碼,預測等需要的參數進行初始化 x264_param_default(m_pParam); //如果有編碼延遲,可以這樣設置就能即時編碼 x264_param_default_preset(m_pParam, "fast", "zerolatency"); m_pParam->i_width = width; m_pParam->i_height = height; m_pParam->i_csp = X264_CSP_I420; //設置Profile,這里有5種級別(編碼出來的碼流規格),級別越高,清晰度越高,耗費資源越大 x264_param_apply_profile(m_pParam, x264_profile_names[5]); //x264_picture_t存儲壓縮編碼前的像素數據 m_pPic_in = (x264_picture_t*)malloc(sizeof(x264_picture_t)); m_pPic_out = (x264_picture_t*)malloc(sizeof(x264_picture_t)); x264_picture_init(m_pPic_out); //為圖像結構體x264_picture_t分配內存 x264_picture_alloc(m_pPic_in, csp, m_pParam->i_width, m_pParam->i_height); //打開編碼器 m_pHandle = x264_encoder_open(m_pParam); } if (m_pPic_in == NULL || m_pPic_out == NULL || m_pHandle == NULL || m_pParam == NULL) { return 2; } int iNal = 0; //x264_nal_t存儲壓縮編碼后的碼流數據 x264_nal_t* pNals = NULL; //注意寫的起始位置和大小,前y_size是Y的數據,然后y_size/4是U的數據,最后y_size/4是V的數據 memcpy(m_pPic_in->img.plane[0], yuvByte, y_size); //先寫Y memcpy(m_pPic_in->img.plane[1], yuvByte + y_size, y_size/4); //再寫U memcpy(m_pPic_in->img.plane[2], yuvByte + y_size + y_size/4, y_size/4); //再寫V m_pPic_in->i_pts = m_nFrameIndex++; //時鐘 //編碼一幀圖像,pNals為返回的碼流數據,iNal是返回的pNals中的NAL單元的數目 int ret = x264_encoder_encode(m_pHandle, &pNals, &iNal, m_pPic_in, m_pPic_out); if (ret < 0) { OutputDebugString(_T("/n x264_encoder_encode err")); return 1; } //寫入目標文件 for (int j = 0; j < iNal; ++j) { fwrite(pNals[j].p_payload, 1, pNals[j].i_payload, m_fp_dst); } delete[] yuvByte; //用完要釋放第一次執行要執行一下m_bFirst中的初始化參數的代碼,代碼具體的解釋見代碼中的注釋。當錄制結束的時候要flush一下編碼器中剩余的幀,然后釋放相關參數,代碼如下:
//結束編碼 if (m_bEndEncode) { m_bEndEncode = FALSE; int iNal = 0; //x264_nal_t存儲壓縮編碼后的碼流數據 x264_nal_t* pNals = NULL; //flush encoder //把編碼器中剩余的碼流數據輸出 while (1) { int ret = x264_encoder_encode(m_pHandle, &pNals, &iNal, NULL, m_pPic_out); if (ret == 0) { break; } printf("Flush 1 frame./n"); for (int j = 0; j < iNal; ++j) { fwrite(pNals[j].p_payload, 1, pNals[j].i_payload, m_fp_dst); } } //釋放內存 x264_picture_clean(m_pPic_in); //關閉編碼器 x264_encoder_close(m_pHandle); m_pHandle = NULL; free(m_pPic_in); m_pPic_in = NULL; free(m_pPic_out); m_pPic_out = NULL; free(m_pParam); m_pParam = NULL; //關閉文件 fclose(m_fp_dst); m_fp_dst = NULL; m_nFrameIndex = 0; }錄制結束后會在設置的目錄下產生一個H264為后綴的文件,可以用VLC打開看看是否正常。工程界面如下:

詳細工程代碼,請到這里下載:完整工程代碼下載
新聞熱點
疑難解答