/*************************************************************************************** * * 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 #include #include /***************************************************************************************/ 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(¶ms, 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(¶ms) == 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; }