Files
gb28181/GB28181Device/media/video_capture_win.cpp
2024-12-15 20:42:32 +08:00

982 lines
20 KiB
C++

/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2022, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "sys_inc.h"
#include "video_capture_win.h"
#include "media_format.h"
#include "media_codec.h"
#include "lock.h"
#include <mfidl.h>
#include <mfapi.h>
#include <locale.h>
/***************************************************************************************/
struct mf_fmt_map
{
int ht_fmt;
GUID mf_fmt;
};
const struct mf_fmt_map mf_fmt_table[] =
{
{ VIDEO_FMT_YUV420P, MFVideoFormat_IYUV },
{ VIDEO_FMT_YUYV422, MFVideoFormat_YUY2 },
{ VIDEO_FMT_YVYU422, MFVideoFormat_YVYU },
{ VIDEO_FMT_UYVY422, MFVideoFormat_UYVY },
{ VIDEO_FMT_YUV420P, MFVideoFormat_YV12 },
{ VIDEO_FMT_NV12, MFVideoFormat_NV12 },
{ VIDEO_FMT_RGB24, MFVideoFormat_RGB24 },
{ VIDEO_FMT_RGB32, MFVideoFormat_RGB32 },
{ VIDEO_FMT_ARGB, MFVideoFormat_ARGB32 },
{ VIDEO_FMT_NONE, MFVideoFormat_Base },
};
BOOL mf_fmt_is_support(GUID guid)
{
int i;
for (i = 0; mf_fmt_table[i].ht_fmt != VIDEO_FMT_NONE; i++)
{
if (mf_fmt_table[i].mf_fmt == guid)
{
return TRUE;
}
}
return FALSE;
}
int mf_fmt_to_ht_fmt(GUID guid)
{
int i;
for (i = 0; mf_fmt_table[i].ht_fmt != VIDEO_FMT_NONE; i++)
{
if (mf_fmt_table[i].mf_fmt == guid)
{
return mf_fmt_table[i].ht_fmt;
}
}
return VIDEO_FMT_NONE;
}
/***************************************************************************************/
static void * videoCaptureThread(void * argv)
{
CWVideoCapture *capture = (CWVideoCapture *)argv;
capture->captureThread();
return NULL;
}
/***************************************************************************************/
CWVideoCapture::CWVideoCapture(void) : CVideoCapture()
{
m_pSource = NULL;
m_pReader = NULL;
}
CWVideoCapture::~CWVideoCapture(void)
{
stopCapture();
}
CVideoCapture * CWVideoCapture::getInstance(int devid)
{
if (devid < 0 || devid >= MAX_VIDEO_DEV_NUMS)
{
return NULL;
}
sys_os_mutex_enter(m_pInstMutex);
if (NULL == m_pInstance[devid])
{
m_pInstance[devid] = (CVideoCapture *)new CWVideoCapture;
if (m_pInstance[devid])
{
m_pInstance[devid]->m_nRefCnt++;
m_pInstance[devid]->m_nDevIndex = devid;
}
}
else
{
m_pInstance[devid]->m_nRefCnt++;
}
sys_os_mutex_leave(m_pInstMutex);
return m_pInstance[devid];
}
int CWVideoCapture::getDeviceNums()
{
int i, count = 0;
IMFAttributes * pAttributes = NULL;
IMFActivate ** ppDevices = NULL;
// Create an attribute store to specify the enumeration parameters.
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr))
{
goto done;
}
// Source type: video capture devices
hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr))
{
goto done;
}
// Enumerate devices
hr = MFEnumDeviceSources(pAttributes, &ppDevices, (UINT*)&count);
if (FAILED(hr))
{
goto done;
}
done:
if (pAttributes)
{
pAttributes->Release();
}
for (i = 0; i < count; i++)
{
ppDevices[i]->Release();
}
CoTaskMemFree(ppDevices);
return count > MAX_VIDEO_DEV_NUMS ? MAX_VIDEO_DEV_NUMS : count;
}
void CWVideoCapture::listDevice()
{
int i, count = 0;
IMFAttributes * pAttributes = NULL;
IMFActivate ** ppDevices = NULL;
// Create an attribute store to specify the enumeration parameters.
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr))
{
goto done;
}
// Source type: video capture devices
hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr))
{
goto done;
}
// Enumerate devices
hr = MFEnumDeviceSources(pAttributes, &ppDevices, (UINT*)&count);
if (FAILED(hr))
{
goto done;
}
done:
if (pAttributes)
{
pAttributes->Release();
}
printf("\r\nAvailable video capture device : \r\n\r\n");
for (i = 0; i < count && i < MAX_VIDEO_DEV_NUMS; i++)
{
wchar_t name[256];
hr = ppDevices[i]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, name, 256, NULL);
if (SUCCEEDED(hr))
{
char * locale = setlocale(LC_ALL, ".UTF8");
_tprintf(_T("index : %d, name : %ws\r\n"), i, name);
setlocale(LC_ALL, locale);
}
ppDevices[i]->Release();
}
CoTaskMemFree(ppDevices);
}
int CWVideoCapture::getDeviceIndex(const char * name)
{
int i, count = 0, index = 0, size;
IMFAttributes * pAttributes = NULL;
IMFActivate ** ppDevices = NULL;
wchar_t * wszname = NULL;
// Create an attribute store to specify the enumeration parameters.
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr))
{
goto done;
}
// Source type: video capture devices
hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr))
{
goto done;
}
// Enumerate devices
hr = MFEnumDeviceSources(pAttributes, &ppDevices, (UINT*)&count);
if (FAILED(hr))
{
goto done;
}
size = MultiByteToWideChar(CP_ACP, 0, name, -1, NULL, 0);
wszname = (wchar_t *)malloc(size * sizeof(wchar_t));
if (wszname)
{
MultiByteToWideChar(CP_ACP, 0, name, -1, wszname, size);
}
done:
if (pAttributes)
{
pAttributes->Release();
}
for (i = 0; i < count && i < MAX_VIDEO_DEV_NUMS; i++)
{
wchar_t wname[256];
hr = ppDevices[i]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, wname, 256, NULL);
if (SUCCEEDED(hr))
{
if (wszname != NULL && wcsicmp(wname, wszname) == 0)
{
index = i;
}
}
ppDevices[i]->Release();
}
CoTaskMemFree(ppDevices);
if (wszname)
{
free(wszname);
}
return index;
}
BOOL CWVideoCapture::checkMediaType()
{
BOOL ret = FALSE;
GUID guid;
IMFMediaType *pType = NULL;
HRESULT hr = m_pReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, &pType);
if (FAILED(hr))
{
goto done;
}
hr = pType->GetGUID(MF_MT_SUBTYPE, &guid);
if (FAILED(hr))
{
goto done;
}
if (guid == MFVideoFormat_MJPG)
{
m_nVideoFormat = VIDEO_FMT_MJPG;
}
else if (mf_fmt_is_support(guid))
{
m_nVideoFormat = mf_fmt_to_ht_fmt(guid);
}
else
{
goto done;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, (UINT*)&m_nWidth, (UINT*)&m_nHeight);
if (FAILED(hr))
{
goto done;
}
UINT num, den;
hr = MFGetAttributeRatio(pType, MF_MT_FRAME_RATE, &num, &den);
if (FAILED(hr))
{
goto done;
}
m_nFramerate = (double)num/den;
ret = TRUE;
done:
if (pType)
{
pType->Release();
}
return ret;
}
BOOL CWVideoCapture::setMediaFormat1(int codec, int width, int height)
{
BOOL set = FALSE;
int dw = 0, dh = 0, w = 0, h = 0;
DWORD index = 0;
HRESULT hr;
IMFMediaType *pType = NULL;
IMFMediaType *pDType = NULL;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
while (SUCCEEDED(hr) && pType)
{
GUID guid;
hr = pType->GetGUID(MF_MT_SUBTYPE, &guid);
if (FAILED(hr))
{
goto retry;
}
if (MFVideoFormat_MJPG == guid && VIDEO_CODEC_JPEG == codec)
{
}
else
{
goto retry;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, (UINT*)&w, (UINT*)&h);
if (FAILED(hr))
{
goto retry;
}
if (w == width && h == height)
{
hr = m_pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pType);
if (SUCCEEDED(hr))
{
set = TRUE;
pType->Release();
break;
}
}
else if (dw * dh < w * h)
{
dw = w;
dh = h;
if (pDType)
{
pDType->Release();
}
pDType = pType;
index++;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
continue;
}
retry:
pType->Release();
index++;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
}
if (!set && pDType)
{
hr = m_pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pDType);
if (SUCCEEDED(hr))
{
set = TRUE;
}
}
if (pDType)
{
pDType->Release();
}
return set;
}
BOOL CWVideoCapture::setMediaFormat2(int codec, int width, int height)
{
BOOL set = FALSE;
int dw = 0, dh = 0, w = 0, h = 0;
DWORD index = 0;
HRESULT hr;
IMFMediaType *pType = NULL;
IMFMediaType *pDType = NULL;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
while (SUCCEEDED(hr) && pType)
{
GUID guid;
hr = pType->GetGUID(MF_MT_SUBTYPE, &guid);
if (FAILED(hr))
{
goto retry;
}
if (!mf_fmt_is_support(guid))
{
goto retry;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, (UINT*)&w, (UINT*)&h);
if (FAILED(hr))
{
goto retry;
}
if (w == width && h == height)
{
hr = m_pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pType);
if (SUCCEEDED(hr))
{
set = TRUE;
pType->Release();
break;
}
}
else if (dw * dh < w * h)
{
dw = w;
dh = h;
if (pDType)
{
pDType->Release();
}
pDType = pType;
index++;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
continue;
}
retry:
pType->Release();
index++;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
}
if (!set && pDType)
{
hr = m_pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pDType);
if (SUCCEEDED(hr))
{
set = TRUE;
}
}
if (pDType)
{
pDType->Release();
}
return set;
}
BOOL CWVideoCapture::setMediaFormat3(int codec, int width, int height)
{
BOOL set = FALSE;
int dw = 0, dh = 0, w = 0, h = 0;
DWORD index = 0;
HRESULT hr;
IMFMediaType *pType = NULL;
IMFMediaType *pDType = NULL;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
while (SUCCEEDED(hr) && pType)
{
GUID guid;
hr = pType->GetGUID(MF_MT_SUBTYPE, &guid);
if (FAILED(hr))
{
goto retry;
}
if (MFVideoFormat_MJPG == guid)
{
}
else
{
goto retry;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, (UINT*)&w, (UINT*)&h);
if (FAILED(hr))
{
goto retry;
}
if (w == width && h == height)
{
hr = m_pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pType);
if (SUCCEEDED(hr))
{
set = TRUE;
pType->Release();
break;
}
}
else if (dw * dh < w * h)
{
dw = w;
dh = h;
if (pDType)
{
pDType->Release();
}
pDType = pType;
index++;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
continue;
}
retry:
pType->Release();
index++;
hr = m_pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &pType);
}
if (!set && pDType)
{
hr = m_pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pDType);
if (SUCCEEDED(hr))
{
set = TRUE;
}
}
if (pDType)
{
pDType->Release();
}
return set;
}
BOOL CWVideoCapture::setFramerate(double framerate)
{
if (framerate <= 0)
{
return TRUE;
}
int min, max, cur;
UINT num, den;
BOOL ret = FALSE;
IMFMediaType *pType = NULL;
HRESULT hr = m_pReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, &pType);
if (FAILED(hr))
{
goto done;
}
hr = MFGetAttributeRatio(pType, MF_MT_FRAME_RATE_RANGE_MIN, &num, &den);
if (FAILED(hr))
{
goto done;
}
min = num / den;
hr = MFGetAttributeRatio(pType, MF_MT_FRAME_RATE_RANGE_MAX, &num, &den);
if (FAILED(hr))
{
goto done;
}
max = num / den;
hr = MFGetAttributeRatio(pType, MF_MT_FRAME_RATE, &num, &den);
if (FAILED(hr))
{
goto done;
}
cur = num /den;
if (framerate < min)
{
framerate = min;
}
else if (framerate > max)
{
framerate = max;
}
if (framerate == cur)
{
ret = TRUE;
goto done;
}
if (framerate - (int)framerate > 0.0)
{
num = (int)(1001 * framerate) + 1;
}
else
{
num = (int)(1001 * framerate);
}
den = 1001;
hr = MFSetAttributeRatio(pType, MF_MT_FRAME_RATE, num, den);
if (FAILED(hr))
{
goto done;
}
hr = m_pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pType);
if (FAILED(hr))
{
goto done;
}
ret = TRUE;
done:
if (pType)
{
pType->Release();
}
return ret;
}
BOOL CWVideoCapture::initCapture(int codec, int width, int height, double framerate, int bitrate)
{
CLock lock(m_pMutex);
if (m_bInited)
{
return TRUE;
}
BOOL set;
int i, count = 0;
IMFAttributes * pAttributes = NULL;
IMFActivate ** ppDevices = NULL;
// Create an attribute store to specify the enumeration parameters.
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr))
{
goto done;
}
// Source type: video capture devices
hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr))
{
goto done;
}
// Enumerate devices
hr = MFEnumDeviceSources(pAttributes, &ppDevices, (UINT*)&count);
if (FAILED(hr))
{
goto done;
}
if (count <= m_nDevIndex)
{
goto done;
}
hr = ppDevices[m_nDevIndex]->ActivateObject(__uuidof(IMFMediaSource), (void**)&m_pSource);
if (FAILED(hr))
{
goto done;
}
hr = MFCreateSourceReaderFromMediaSource(m_pSource, NULL, &m_pReader);
if (FAILED(hr))
{
goto done;
}
set = setMediaFormat1(codec, width, height);
if (!set)
{
set = setMediaFormat2(codec, width, height);
}
if (!set)
{
set = setMediaFormat3(codec, width, height);
}
if (!set)
{
goto done;
}
setFramerate(framerate);
if (!checkMediaType())
{
goto done;
}
setCodecFlag(codec);
if (m_bDecode)
{
if (!m_decoder.init(getVideoCodec(m_nVideoFormat), NULL, 0, HW_DECODING_AUTO))
{
goto done;
}
m_decoder.setCallback(VideoDecodeCb, this);
}
if (m_bEncode)
{
VideoEncoderParam params;
memset(&params, 0, sizeof(params));
params.SrcWidth = m_nWidth;
params.SrcHeight = m_nHeight;
params.SrcPixFmt = to_avpixelformat(m_nVideoFormat);
params.DstCodec = codec;
params.DstWidth = width ? width : m_nWidth;
params.DstHeight = height ? height : m_nHeight;
params.DstFramerate = m_nFramerate;
params.DstBitrate = bitrate;
if (m_encoder.init(&params) == FALSE)
{
goto done;
}
}
m_nBitrate = bitrate;
m_bInited = TRUE;
done:
if (pAttributes)
{
pAttributes->Release();
}
for (i = 0; i < count; i++)
{
ppDevices[i]->Release();
}
CoTaskMemFree(ppDevices);
return m_bInited;
}
BOOL CWVideoCapture::capture()
{
if (!m_bInited)
{
return FALSE;
}
DWORD dwFlags = 0;
IMFSample *pSample = NULL;
HRESULT hr = m_pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &dwFlags, NULL, &pSample);
if (FAILED(hr) || NULL == pSample)
{
return FALSE;
}
IMFMediaBuffer *pBuffer = NULL;
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
if (FAILED(hr))
{
pSample->Release();
return FALSE;
}
BYTE * buff = NULL;
DWORD size = 0;
hr = pBuffer->Lock(&buff, NULL, &size);
if (FAILED(hr))
{
pBuffer->Release();
pSample->Release();
return FALSE;
}
BOOL ret = TRUE;
if (m_bDecode)
{
ret = m_decoder.decode(buff, size, 0);
}
else if (m_bEncode)
{
ret = m_encoder.encode(buff, size);
}
else
{
m_encoder.procData(buff, size);
}
pBuffer->Unlock();
pBuffer->Release();
pSample->Release();
return ret;
}
BOOL CWVideoCapture::startCapture()
{
CLock lock(m_pMutex);
if (m_hCapture)
{
return TRUE;
}
m_bCapture = TRUE;
m_hCapture = sys_os_create_thread((void *)videoCaptureThread, this);
return (NULL != m_hCapture);
}
void CWVideoCapture::stopCapture()
{
m_bCapture = FALSE;
// wait for capture thread exit
while (m_hCapture)
{
usleep(10*1000);
}
if (m_pReader)
{
m_pReader->Release();
m_pReader = NULL;
}
if (m_pSource)
{
m_pSource->Release();
m_pSource = NULL;
}
m_bInited = FALSE;
}
void CWVideoCapture::captureThread()
{
int64 cur_delay = 0;
int64 pre_delay = 0;
int timeout = 1000000.0 / m_nFramerate;
uint32 cur_time = 0;
uint32 pre_time = 0;
while (m_bCapture)
{
if (capture())
{
cur_time = sys_os_get_ms();
cur_delay = timeout;
if (pre_time > 0)
{
cur_delay += pre_delay - (cur_time - pre_time) * 1000;
if (cur_delay < 1000)
{
cur_delay = 0;
}
}
pre_time = cur_time;
pre_delay = cur_delay;
if (cur_delay > 0)
{
usleep(cur_delay);
}
}
else
{
usleep(10*1000);
}
}
m_hCapture = 0;
}