下載本插件
音視頻捕獲就是獲取電腦外接的話筒,攝像頭,或線路輸入的音頻和視頻。
本插件捕獲電腦外接的音頻和視頻。最多可以同時獲取4個視頻源和4個音頻源。插件可以在win32和MFC程序中使用。
使用方法
首先,加載本“捕獲”DLL,獲得DLL模塊句柄。
HMODULE hCap = LoadLibrary(L"捕獲.dll");//加載捕獲模塊
獲取所有導出函數的地址。
typedef int(__cdecl *MYPROC_Sample)(LONGLONG star, LONGLONG end, BYTE* pB, LONG len);struct CAP_INIT
{MYPROC_Sample VideoSample0 = NULL;//視頻樣本輸出函數MYPROC_Sample VideoSample1 = NULL;MYPROC_Sample VideoSample2 = NULL;MYPROC_Sample VideoSample3 = NULL;MYPROC_Sample AudioSample0 = NULL;//音頻樣本輸出函數MYPROC_Sample AudioSample1 = NULL;MYPROC_Sample AudioSample2 = NULL;MYPROC_Sample AudioSample3 = NULL;
};
struct CAP_VIDEO_INFO
{UINT32 Width=0;//視頻寬,單位像素UINT32 Height=0;//視頻高,單位像素double nFrames=0;//幀率WCHAR Name[MAX_PATH];//視頻捕獲設備名稱
};
struct CAP_AUDIO_INFO
{UINT32 nCh;//聲道數WCHAR Name[MAX_PATH];//音頻捕獲設備名稱
};typedef int(__cdecl *MYPROC_CAP_Init)(CAP_INIT init, CAP_VIDEO_INFO*& VideoInfo, CAP_AUDIO_INFO*& AudioInfo);
typedef int(__cdecl *MYPROC_VOID)();MYPROC_CAP_Init CAP_Init=NULL;
MYPROC_VOID CAP_ReleaseDevice=NULL;if (hCap){CAP_Init=(MYPROC_CAP_Init)GetProcAddress(hCap, "Init");//“初始化”函數CAP_ReleaseDevice=(MYPROC_VOID)GetProcAddress(hCap, "ReleaseDevice");//“釋放所有捕獲設備”函數}
定義8個全局函數,以提供給初始化函數;全局函數用于輸出視頻和音頻樣本。
int VideoSample0(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//視頻流1樣本輸出函數
{return 1;
}int VideoSample1(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//視頻流2樣本輸出函數
{return 1;
}int VideoSample2(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//視頻流3樣本輸出函數
{return 1;
}int VideoSample3(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//視頻流4樣本輸出函數
{return 1;
}int AudioSample0(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//音頻流1樣本輸出函數
{return 1;
}int AudioSample1(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//音頻流2樣本輸出函數
{return 1;
}int AudioSample2(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//音頻流3樣本輸出函數
{return 1;
}int AudioSample3(LONGLONG star, LONGLONG end, BYTE* pB, LONG len)//音頻流4樣本輸出函數
{return 1;
}CAP_INIT CapInit;CapInit.VideoSample0 = &VideoSample0;//為視頻,音頻源指定輸出函數CapInit.VideoSample1 = &VideoSample1;CapInit.VideoSample2 = &VideoSample2;CapInit.VideoSample3 = &VideoSample3;CapInit.AudioSample0 = &AudioSample0;CapInit.AudioSample1 = &AudioSample1;CapInit.AudioSample2 = &AudioSample2;CapInit.AudioSample3 = &AudioSample3;
捕獲視頻和音頻。
CAP_Init函數為每一個捕獲設備(接口為IMFMediaSource)創建1個源讀取器(接口為IMFSourceReader),并指定輸出媒體類型;
為每一個捕獲設備創建1個線程,在線程中使用源讀取器接口讀取樣本,反復調用CAP_INIT結構提供的樣本輸出函數輸出視頻和音頻樣本。
CAP_VIDEO_INFO* pVInfo = new CAP_VIDEO_INFO[4];//結構數組用于存儲視頻流,音頻流信息CAP_VIDEO_INFO* pAInfo = new CAP_AUDIO_INFO[4];WORD w = CAP_Init(CapInit, pVInfo, pAInfo);//獲取捕獲設備,創建視頻流,音頻流VCount = LOBYTE(w); //獲取視頻流數量ACount = HIBYTE(w);//獲取音頻流數量
釋放所有獲取到的捕獲設備。
函數將終止所有捕獲線程,并釋放所有源讀取器接口。
CAP_ReleaseDevice();
有關本插件的使用示例,可以看后續文章“Windows應用-音視頻捕獲”。
“捕獲”DLL的全部代碼
DLL.h
#pragma once
#include <SDKDDKVer.h>#define WIN32_LEAN_AND_MEAN // 從 Windows 頭中排除極少使用的資料
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 某些 CString 構造函數將是顯式的
#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // 移除對話框中的 MFC 控件支持#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN // 從 Windows 頭中排除極少使用的資料
#endif#include <afx.h>
#include <afxwin.h> // MFC 核心組件和標準組件
#include <afxext.h> // MFC 擴展
#ifndef _AFX_NO_OLE_SUPPORT
#include <afxdtctl.h> // MFC 對 Internet Explorer 4 公共控件的支持
#endif
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC 對 Windows 公共控件的支持
#endif // _AFX_NO_AFXCMN_SUPPORT// Windows 頭文件:
#include <windows.h>#include "mfidl.h"
#include "mfapi.h"
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfplat")#include "mfreadwrite.h"
#pragma comment(lib, "mfreadwrite")#ifndef SAFE_RELEASE
#define SAFE_RELEASEtemplate <class T> void SafeRelease(T** ppT)
{if (*ppT){(*ppT)->Release();*ppT = NULL;}
}#endif //SAFE_RELEASEtypedef int(__cdecl *MYPROC_Sample)(LONGLONG star, LONGLONG end, BYTE* pB, LONG len);struct CAP_INIT
{MYPROC_Sample VideoSample0 = NULL;//視頻樣本輸出函數MYPROC_Sample VideoSample1 = NULL;MYPROC_Sample VideoSample2 = NULL;MYPROC_Sample VideoSample3 = NULL;MYPROC_Sample AudioSample0 = NULL;//音頻樣本輸出函數MYPROC_Sample AudioSample1 = NULL;MYPROC_Sample AudioSample2 = NULL;MYPROC_Sample AudioSample3 = NULL;
};struct CAP_VIDEO_INFO
{UINT32 Width=0;//視頻寬,單位像素UINT32 Height=0;//視頻高,單位像素double nFrames=0;//幀率WCHAR Name[MAX_PATH];//視頻捕獲設備名稱
};struct CAP_AUDIO_INFO
{UINT32 nCh;//聲道數WCHAR Name[MAX_PATH];//音頻捕獲設備名稱
};class Capture
{
public:Capture();~Capture();CAP_INIT mInit;CAP_VIDEO_INFO mVideoInfo[4];HANDLE hEXIT = NULL;HANDLE hVThread[4];HANDLE hAThread[4];IMFSourceReader* pVideoReader[4];//視頻源讀取器指針數組IMFSourceReader* pAudioReader[4];//音頻源讀取器指針數組void GetDevice(int& mVideo, CAP_VIDEO_INFO* VideoInfo, int& mAudio, CAP_AUDIO_INFO* AudioInfo);//獲取前4個音頻,視頻捕獲設備void ReleaseDevice();//釋放捕獲的設備int Init(CAP_INIT init, CAP_VIDEO_INFO*& VideoInfo, CAP_AUDIO_INFO*& AudioInfo);//初始化捕獲設備
};struct PARAM
{Capture* pCapture = NULL;int index;
};
DLL.cpp
#include "DLL.h"Capture::Capture()
{HRESULT hr = MFStartup(MF_VERSION);hEXIT = CreateEvent(NULL, TRUE, FALSE, NULL);//創建“退出”事件。手動重置,初始無信號
}Capture::~Capture()
{CloseHandle(hEXIT);MFShutdown();
}DWORD WINAPI AudioThread(LPVOID lp);
DWORD WINAPI VideoThread(LPVOID lp);void Capture::GetDevice(int& mVideo, CAP_VIDEO_INFO* VideoInfo, int& mAudio, CAP_AUDIO_INFO* AudioInfo)//獲取捕獲設備
{HRESULT hr;IMFAttributes *pVideoAttributes = NULL;hr = MFCreateAttributes(&pVideoAttributes, 1);hr = pVideoAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);UINT32 countV;IMFActivate **ppVideoDevices = NULL;hr = MFEnumDeviceSources(pVideoAttributes, &ppVideoDevices, &countV);//枚舉視頻捕獲設備SafeRelease(&pVideoAttributes);if (countV > 4){for (int i = 4; i < (int)countV; i++)//釋放4個設備后面的設備{ppVideoDevices[i]->Release();}countV = 4;}mVideo = countV;for (DWORD i = 0; i < countV; i++)//獲取所有視頻源信息,為視頻源創建讀取樣本線程{hr = S_OK;WCHAR *szFriendlyName = NULL;UINT32 cchName;hr = ppVideoDevices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &szFriendlyName, &cchName); //獲取顯示名稱wmemset(VideoInfo[i].Name, 0, MAX_PATH);CopyMemory(VideoInfo[i].Name, szFriendlyName, 2 * (wcslen(szFriendlyName) + 1));CoTaskMemFree(szFriendlyName);IMFMediaSource *pSource = NULL;if (SUCCEEDED(hr)){hr = ppVideoDevices[i]->ActivateObject(IID_IMFMediaSource, (void**)&pSource);//激活對象}IMFAttributes* pIMFAttributes = NULL;if (SUCCEEDED(hr)){hr = MFCreateAttributes(&pIMFAttributes, 0);//創建空的屬性}if (SUCCEEDED(hr)){hr = pIMFAttributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, (UINT32)1);//使用基于硬件的媒體基礎轉換}if (SUCCEEDED(hr)){hr = pIMFAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, (UINT32)1);//允許源讀取器進行有限的視頻處理}hr = MFCreateSourceReaderFromMediaSource(pSource, pIMFAttributes, &pVideoReader[i]);//從媒體源創建源讀取器SafeRelease(&pIMFAttributes); SafeRelease(&pSource);IMFMediaType* pVideoMTV = NULL;if (SUCCEEDED(hr)){hr = MFCreateMediaType(&pVideoMTV);//創建空的媒體類型}if (SUCCEEDED(hr)){hr = pVideoMTV->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);//設置主要類型為視頻}if (SUCCEEDED(hr)){hr = pVideoMTV->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);//設置子類型RGB32}if (SUCCEEDED(hr)){hr = pVideoReader[i]->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pVideoMTV);//設置源讀取器視頻輸出媒體類型}SafeRelease(&pVideoMTV);IMFMediaType* pMT = NULL;if (SUCCEEDED(hr)){hr = pVideoReader[i]->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, &pMT);//獲取源讀取器視頻輸出媒體類型。此時可以獲得完整的輸出媒體類型信息}UINT32 Width, Height;if (SUCCEEDED(hr)){hr = MFGetAttributeSize(pMT, MF_MT_FRAME_SIZE, &Width, &Height);//獲取視頻寬高VideoInfo[i].Width = Width; VideoInfo[i].Height = Height;}UINT32 Numerator, Denominator;if (SUCCEEDED(hr)){hr = MFGetAttributeRatio(pMT, MF_MT_FRAME_RATE, &Numerator, &Denominator);//獲取幀率VideoInfo[i].nFrames = (double)Numerator / (double)Denominator;}SafeRelease(&pMT);mVideoInfo[i] = VideoInfo[i];PARAM* pParam = new PARAM();pParam->index = i;pParam->pCapture = this;hVThread[i] = CreateThread(NULL, 0, VideoThread, pParam, 0, NULL);//為視頻源創建讀取線程}CoTaskMemFree(ppVideoDevices);//釋放視頻捕獲設備接口指針數組使用的內存IMFAttributes *pAudioAttributes = NULL;hr = MFCreateAttributes(&pAudioAttributes, 1);//創建空的屬性hr = pAudioAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID);UINT32 countA;IMFActivate **ppAudioDevices = NULL;hr = MFEnumDeviceSources(pAudioAttributes, &ppAudioDevices, &countA);//枚舉音頻捕獲設備SafeRelease(&pAudioAttributes);if (countA > 4){for (int i = 4; i < (int)countA; i++)//釋放4個設備后面的設備{ppAudioDevices[i]->Release();}countA = 4;}mAudio = countA;for (DWORD i = 0; i < countA; i++){hr = S_OK;WCHAR *szFriendlyName = NULL;UINT32 cchName;hr = ppAudioDevices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &szFriendlyName, &cchName); //獲取捕獲設備名稱wmemset(AudioInfo[i].Name, 0, MAX_PATH);CopyMemory(AudioInfo[i].Name, szFriendlyName, 2*(wcslen(szFriendlyName) + 1));CoTaskMemFree(szFriendlyName);IMFMediaSource *pSource = NULL;if (SUCCEEDED(hr)){hr = ppAudioDevices[i]->ActivateObject(IID_IMFMediaSource, (void**)&pSource);//激活對象}IMFAttributes* pIMFAttributes = NULL;if (SUCCEEDED(hr)){hr = MFCreateAttributes(&pIMFAttributes, 0);}if (SUCCEEDED(hr)){hr = pIMFAttributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, (UINT32)1);//使用基于硬件的媒體基礎轉換}if (SUCCEEDED(hr)){hr = pIMFAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, (UINT32)1);//允許源讀取器進行有限的視頻處理}if (SUCCEEDED(hr)){hr = MFCreateSourceReaderFromMediaSource(pSource, pIMFAttributes, &pAudioReader[i]);//從媒體源創建源讀取器}SafeRelease(&pIMFAttributes);SafeRelease(&pSource);IMFMediaType* pAudioMTA = NULL;if (SUCCEEDED(hr)){hr = MFCreateMediaType(&pAudioMTA);//創建空的媒體類型}if (SUCCEEDED(hr)){hr = pAudioMTA->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);//設置主要類型音頻}if (SUCCEEDED(hr)){hr = pAudioMTA->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);//設置子類型PCM}if (SUCCEEDED(hr)){hr = pAudioMTA->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, (UINT32)16);//設置樣本16位}if (SUCCEEDED(hr)){hr = pAudioMTA->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, (UINT32)48000);//設置樣本采樣率48000}if (SUCCEEDED(hr)){hr = pAudioReader[i]->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, pAudioMTA);//設置音頻輸出媒體類型(此時未提供媒體類型全部信息)}SafeRelease(&pAudioMTA);IMFMediaType* pMT = NULL;if (SUCCEEDED(hr)){hr = pAudioReader[i]->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &pMT);//獲取音頻輸出媒體類型,此時可以得到完整的媒體類型信息}UINT32 nch;if (SUCCEEDED(hr)){hr = pMT->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &nch);//獲取聲道數AudioInfo[i].nCh = nch;}SafeRelease(&pMT);PARAM* pParam = new PARAM();pParam->index = i;pParam->pCapture = this;hAThread[i] = CreateThread(NULL, 0, AudioThread, pParam, 0, NULL);//為音頻源創建讀取線程}CoTaskMemFree(ppAudioDevices);//釋放音頻捕獲設備接口指針數組使用的內存
}DWORD WINAPI VideoThread(LPVOID lp)//視頻源讀取樣本線程共用函數
{PARAM* pParam = (PARAM*)lp;Capture* pCapture = pParam->pCapture;int index = pParam->index;HRESULT hr;IMFSourceReader* pIMFSourceReader = pCapture->pVideoReader[index];double dur = (double)10000000 / pCapture->mVideoInfo[index].nFrames;//計算1幀持續時間IMFSample* pMFSample = NULL; DWORD flags;
Agan:hr = pIMFSourceReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &flags, NULL, &pMFSample);//讀取視頻樣本if (hr == S_OK && pMFSample){LONGLONG Cur, Dur;hr = pMFSample->GetSampleTime(&Cur);//獲取顯示時間hr = pMFSample->GetSampleDuration(&Dur);//獲取持續時間DWORD Lt;hr = pMFSample->GetTotalLength(&Lt);//獲取有效長度DWORD count;hr = pMFSample->GetBufferCount(&count);//獲取緩沖區數量IMFMediaBuffer* pMFBuffer = NULL;if (count == 1)//如果只有1個緩沖區{hr = pMFSample->GetBufferByIndex(0, &pMFBuffer);}else//如果有多個緩沖區{hr = pMFSample->ConvertToContiguousBuffer(&pMFBuffer);}BYTE* pSB = NULL;hr = pMFBuffer->Lock(&pSB, NULL, NULL);//鎖定緩沖區switch (index){case 0://第1個視頻源pCapture->mInit.VideoSample0(Cur, Cur + Dur, pSB, (LONG)Lt);//發送樣本break;case 1://第2個pCapture->mInit.VideoSample1(Cur, Cur + Dur, pSB, (LONG)Lt);break;case 2:pCapture->mInit.VideoSample2(Cur, Cur + Dur, pSB, (LONG)Lt);break;case 3:pCapture->mInit.VideoSample3(Cur, Cur + Dur, pSB, (LONG)Lt);break;}hr = pMFBuffer->Unlock();//解鎖緩沖區SafeRelease(&pMFBuffer); SafeRelease(&pMFSample);//釋放接口}DWORD mExit = WaitForSingleObject(pCapture->hEXIT, 0);if (mExit == WAIT_OBJECT_0)//有“退出”信號{delete pParam;return 0;}goto Agan;
}DWORD WINAPI AudioThread(LPVOID lp)//讀取音頻源樣本線程共用函數
{PARAM* pParam = (PARAM*)lp;Capture* pCapture = pParam->pCapture;int index = pParam->index;HRESULT hr;IMFSourceReader* pIMFSourceReader = pCapture->pAudioReader[index];IMFSample* pMFSample = NULL; DWORD flags;
Agan:hr = pIMFSourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &flags, NULL, &pMFSample);//讀取音頻樣本if (hr == S_OK && pMFSample){LONGLONG Cur, Dur;hr = pMFSample->GetSampleTime(&Cur);//獲取顯示時間hr = pMFSample->GetSampleDuration(&Dur);//獲取持續時間DWORD Lt;hr = pMFSample->GetTotalLength(&Lt);//獲取有效長度DWORD count;hr = pMFSample->GetBufferCount(&count);//獲取緩沖區數量IMFMediaBuffer* pMFBuffer = NULL;if (count == 1)//如果只有1個緩沖區{hr = pMFSample->GetBufferByIndex(0, &pMFBuffer);}else//如果有多個緩沖區{hr = pMFSample->ConvertToContiguousBuffer(&pMFBuffer);}BYTE* pSB = NULL;hr = pMFBuffer->Lock(&pSB, NULL, NULL);//鎖定緩沖區switch (index){case 0://第1個音頻源pCapture->mInit.AudioSample0(Cur, Cur + Dur, pSB, (LONG)Lt);//發送樣本break;case 1://第2個pCapture->mInit.AudioSample1(Cur, Cur + Dur, pSB, (LONG)Lt);break;case 2:pCapture->mInit.AudioSample2(Cur, Cur + Dur, pSB, (LONG)Lt);break;case 3:pCapture->mInit.AudioSample3(Cur, Cur + Dur, pSB, (LONG)Lt);break;}hr = pMFBuffer->Unlock();//解鎖緩沖區SafeRelease(&pMFBuffer); SafeRelease(&pMFSample);//釋放接口}DWORD mExit = WaitForSingleObject(pCapture->hEXIT, 0);if (mExit == WAIT_OBJECT_0)//有“退出”信號{delete pParam;return 0;}goto Agan;
}void Capture::ReleaseDevice()
{SetEvent(hEXIT);//發送“退出”信號WaitForMultipleObjects(4, hVThread, TRUE, INFINITE);//等待所有線程退出WaitForMultipleObjects(4, hAThread, TRUE, INFINITE);for (int i = 0; i < 4; i++)//釋放所有視頻音頻源讀取器接口{SafeRelease(&pVideoReader[i]); SafeRelease(&pAudioReader[i]);}
}int Capture::Init(CAP_INIT init, CAP_VIDEO_INFO*& VideoInfo, CAP_AUDIO_INFO*& AudioInfo)
{mInit = init;ResetEvent(hEXIT);//設置“退出”無信號int mV, mA;GetDevice(mV,VideoInfo, mA,AudioInfo);//獲取前4個音頻,視頻捕獲設備return (int)MAKEWORD((BYTE)mA, (BYTE)mV);
}Capture mCapture;#ifdef __cplusplus // If used by C++ code,
extern "C" { // we need to export the C interface
#endif__declspec(dllexport) int __cdecl Init(CAP_INIT init, CAP_VIDEO_INFO*& VideoInfo, CAP_AUDIO_INFO*& AudioInfo)//最多獲取前4個視頻音頻捕獲設備{return mCapture.Init(init, VideoInfo, AudioInfo);}__declspec(dllexport) int __cdecl ReleaseDevice()//釋放獲取到的捕獲設備{mCapture.ReleaseDevice();return 0;}#ifdef __cplusplus
}
#endif