#ifndef camera_helper_h__ #define camera_helper_h__ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace comet { #define MAKE_COMET_COMTYPE(T, BASE) \ template<> struct comtype< ::T > \ { \ static const IID& uuid() { return IID_ ## T; } \ typedef ::BASE base; \ } MAKE_COMET_COMTYPE(IMF2DBuffer, IUnknown); MAKE_COMET_COMTYPE(IAMVideoProcAmp, IUnknown); MAKE_COMET_COMTYPE(IAMCameraControl, IUnknown); } struct cam_prop_range { long min; long max; long step; long defaultValue; bool canBeAuto; bool canBeManual; }; inline std::ostream& operator<<(std::ostream& os, const cam_prop_range& val) { os << "[" << val.min << ":" << val.step << ":" << val.max << "], " << val.defaultValue; if (val.canBeAuto && val.canBeManual) os << " (auto/manual)"; else if (val.canBeAuto) os << " (auto)"; else if (val.canBeManual) os << " (manual)"; return os; } struct cam_prop_value { long value; bool isAuto; cam_prop_value() : value(0), isAuto(true) { } cam_prop_value(long value, bool isAuto = false) : value(value), isAuto(isAuto) { } operator long() const { return value; } static cam_prop_value AUTO(long value = 0) { return cam_prop_value(value, true); }; static cam_prop_value MANUAL(long value = 0) { return cam_prop_value(value, false); }; }; inline std::ostream& operator<<(std::ostream& os, const cam_prop_value& val) { os << val.value; os << " ("; if (val.isAuto) os << "auto"; else os << "manual"; os << ")"; return os; } namespace MediaFormat { enum e { Unknown, RGB32, // MFVideoFormat_RGB32 ARGB32, // MFVideoFormat_ARGB32 RGB24, // MFVideoFormat_RGB24 RGB555, // MFVideoFormat_RGB555 RGB565, // MFVideoFormat_RGB565 RGB8, // MFVideoFormat_RGB8 AI44, // MFVideoFormat_AI44 AYUV, // MFVideoFormat_AYUV YUY2, // MFVideoFormat_YUY2 YVYU, // MFVideoFormat_YVYU YVU9, // MFVideoFormat_YVU9 UYVY, // MFVideoFormat_UYVY NV11, // MFVideoFormat_NV11 NV12, // MFVideoFormat_NV12 YV12, // MFVideoFormat_YV12 I420, // MFVideoFormat_I420 IYUV, // MFVideoFormat_IYUV Y210, // MFVideoFormat_Y210 Y216, // MFVideoFormat_Y216 Y410, // MFVideoFormat_Y410 Y416, // MFVideoFormat_Y416 Y41P, // MFVideoFormat_Y41P Y41T, // MFVideoFormat_Y41T Y42T, // MFVideoFormat_Y42T P210, // MFVideoFormat_P210 P216, // MFVideoFormat_P216 P010, // MFVideoFormat_P010 P016, // MFVideoFormat_P016 v210, // MFVideoFormat_v210 v216, // MFVideoFormat_v216 v410, // MFVideoFormat_v410 MP43, // MFVideoFormat_MP43 MP4S, // MFVideoFormat_MP4S M4S2, // MFVideoFormat_M4S2 MP4V, // MFVideoFormat_MP4V WMV1, // MFVideoFormat_WMV1 WMV2, // MFVideoFormat_WMV2 WMV3, // MFVideoFormat_WMV3 WVC1, // MFVideoFormat_WVC1 MSS1, // MFVideoFormat_MSS1 MSS2, // MFVideoFormat_MSS2 MPG1, // MFVideoFormat_MPG1 DVSL, // MFVideoFormat_DVSL DVSD, // MFVideoFormat_DVSD DVHD, // MFVideoFormat_DVHD DV25, // MFVideoFormat_DV25 DV50, // MFVideoFormat_DV50 DVH1, // MFVideoFormat_DVH1 DVC, // MFVideoFormat_DVC H264, // MFVideoFormat_H264 MJPG, // MFVideoFormat_MJPG YUV_420O, // MFVideoFormat_420O H263, // MFVideoFormat_H263 H264_ES, // MFVideoFormat_H264_ES MPEG2, // MFVideoFormat_MPEG2 }; inline e fromGUID(GUID guid) { if (guid == MFVideoFormat_RGB32) return RGB32; else if (guid == MFVideoFormat_ARGB32) return ARGB32; else if (guid == MFVideoFormat_RGB24) return RGB24; else if (guid == MFVideoFormat_RGB555) return RGB555; else if (guid == MFVideoFormat_RGB565) return RGB565; else if (guid == MFVideoFormat_RGB8) return RGB8; else if (guid == MFVideoFormat_AI44) return AI44; else if (guid == MFVideoFormat_AYUV) return AYUV; else if (guid == MFVideoFormat_YUY2) return YUY2; else if (guid == MFVideoFormat_YVYU) return YVYU; else if (guid == MFVideoFormat_YVU9) return YVU9; else if (guid == MFVideoFormat_UYVY) return UYVY; else if (guid == MFVideoFormat_NV11) return NV11; else if (guid == MFVideoFormat_NV12) return NV12; else if (guid == MFVideoFormat_YV12) return YV12; else if (guid == MFVideoFormat_I420) return I420; else if (guid == MFVideoFormat_IYUV) return IYUV; else if (guid == MFVideoFormat_Y210) return Y210; else if (guid == MFVideoFormat_Y216) return Y216; else if (guid == MFVideoFormat_Y410) return Y410; else if (guid == MFVideoFormat_Y416) return Y416; else if (guid == MFVideoFormat_Y41P) return Y41P; else if (guid == MFVideoFormat_Y41T) return Y41T; else if (guid == MFVideoFormat_Y42T) return Y42T; else if (guid == MFVideoFormat_P210) return P210; else if (guid == MFVideoFormat_P216) return P216; else if (guid == MFVideoFormat_P010) return P010; else if (guid == MFVideoFormat_P016) return P016; else if (guid == MFVideoFormat_v210) return v210; else if (guid == MFVideoFormat_v216) return v216; else if (guid == MFVideoFormat_v410) return v410; else if (guid == MFVideoFormat_MP43) return MP43; else if (guid == MFVideoFormat_MP4S) return MP4S; else if (guid == MFVideoFormat_M4S2) return M4S2; else if (guid == MFVideoFormat_MP4V) return MP4V; else if (guid == MFVideoFormat_WMV1) return WMV1; else if (guid == MFVideoFormat_WMV2) return WMV2; else if (guid == MFVideoFormat_WMV3) return WMV3; else if (guid == MFVideoFormat_WVC1) return WVC1; else if (guid == MFVideoFormat_MSS1) return MSS1; else if (guid == MFVideoFormat_MSS2) return MSS2; else if (guid == MFVideoFormat_MPG1) return MPG1; else if (guid == MFVideoFormat_DVSL) return DVSL; else if (guid == MFVideoFormat_DVSD) return DVSD; else if (guid == MFVideoFormat_DVHD) return DVHD; else if (guid == MFVideoFormat_DV25) return DV25; else if (guid == MFVideoFormat_DV50) return DV50; else if (guid == MFVideoFormat_DVH1) return DVH1; else if (guid == MFVideoFormat_DVC) return DVC; else if (guid == MFVideoFormat_H264) return H264; else if (guid == MFVideoFormat_MJPG) return MJPG; else if (guid == MFVideoFormat_420O) return YUV_420O; else if (guid == MFVideoFormat_H263) return H263; else if (guid == MFVideoFormat_H264_ES) return H264_ES; else if (guid == MFVideoFormat_MPEG2) return MPEG2; else return Unknown; } inline const char* to_string (const MediaFormat::e& format) { switch (format) { case RGB32: return "RGB32"; case ARGB32: return "ARGB32"; case RGB24: return "RGB24"; case RGB555: return "RGB555"; case RGB565: return "RGB565"; case RGB8: return "RGB8"; case AI44: return "AI44"; case AYUV: return "AYUV"; case YUY2: return "YUY2"; case YVYU: return "YVYU"; case YVU9: return "YVU9"; case UYVY: return "UYVY"; case NV11: return "NV11"; case NV12: return "NV12"; case YV12: return "YV12"; case I420: return "I420"; case IYUV: return "IYUV"; case Y210: return "Y210"; case Y216: return "Y216"; case Y410: return "Y410"; case Y416: return "Y416"; case Y41P: return "Y41P"; case Y41T: return "Y41T"; case Y42T: return "Y42T"; case P210: return "P210"; case P216: return "P216"; case P010: return "P010"; case P016: return "P016"; case v210: return "v210"; case v216: return "v216"; case v410: return "v410"; case MP43: return "MP43"; case MP4S: return "MP4S"; case M4S2: return "M4S2"; case MP4V: return "MP4V"; case WMV1: return "WMV1"; case WMV2: return "WMV2"; case WMV3: return "WMV3"; case WVC1: return "WVC1"; case MSS1: return "MSS1"; case MSS2: return "MSS2"; case MPG1: return "MPG1"; case DVSL: return "DVSL"; case DVSD: return "DVSD"; case DVHD: return "DVHD"; case DV25: return "DV25"; case DV50: return "DV50"; case DVH1: return "DVH1"; case DVC: return "DVC"; case H264: return "H264"; case MJPG: return "MJPG"; case YUV_420O: return "420O"; case H263: return "H263"; case H264_ES: return "H264_ES"; case MPEG2: return "MPEG2"; default: return "Unknown"; } } inline std::ostream& operator<< (std::ostream& os, const MediaFormat::e& format) { return os << to_string(format); } } class media_type { public: media_type() : _ptr(NULL) { } media_type(IMFMediaType* ptr) : _ptr(ptr) { } media_type(comet::com_ptr ptr) : _ptr(ptr) { } cv::Size resolution() const { UINT32 width, height; MFGetAttributeSize(_ptr.in(), MF_MT_FRAME_SIZE, &width, &height) | comet::raise_exception; return cv::Size(width, height); } double framerate() const { UINT32 num, denom; MFGetAttributeRatio(_ptr.in(), MF_MT_FRAME_RATE, &num, &denom) | comet::raise_exception; return static_cast(num) / denom; } MediaFormat::e format() const { GUID subtype; _ptr->GetGUID(MF_MT_SUBTYPE, &subtype) | comet::raise_exception; return MediaFormat::fromGUID(subtype); } #ifdef _TYPEDEF_BOOL_TYPE typedef media_type _Myt; _TYPEDEF_BOOL_TYPE; _OPERATOR_BOOL() const _NOEXCEPT { // test for non-null pointer return (_ptr != 0 ? _CONVERTIBLE_TO_TRUE : 0); } #else explicit operator bool() const { // test for non-null pointer return _ptr != 0; } #endif comet::com_ptr _ptr; }; class camera { public: camera() { } camera(IMFActivate* ptr) : activate_ptr(ptr) { } camera(comet::com_ptr ptr) : activate_ptr(ptr) { } // TODO actually implement these or make sure they are deleted/created correctly, this might be the reason for weird mapping of stuff camera(const camera& other) { // this-> // this = other; } //= delete; camera(camera&& other) { *this = std::move(other); } //camera& operator=(const camera& other) = delete; camera& operator=(camera&& other) { activate_ptr.swap(other.activate_ptr); source_ptr.swap(other.source_ptr); reader_ptr.swap(other.reader_ptr); return *this; } ~camera() { shutdown(); } bool is_active() const { return !source_ptr.is_null(); } void activate() { if (activate_ptr) activate_ptr->ActivateObject(IID_IMFMediaSource, reinterpret_cast(source_ptr.out())) | comet::raise_exception; reader_ptr = NULL; } void shutdown() { if (activate_ptr) activate_ptr->ShutdownObject() | comet::raise_exception; source_ptr = NULL; reader_ptr = NULL; } cv::Mat read_frame(int streamIndex = MF_SOURCE_READER_FIRST_VIDEO_STREAM, int bufferIndex = 0) { if (!activate_ptr) return cv::Mat(); if (!reader_ptr) { comet::com_ptr pAttributes; MFCreateAttributes(pAttributes.out(), 1) | comet::raise_exception; pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE) | comet::raise_exception; pAttributes->SetUINT32(MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, TRUE) | comet::raise_exception; MFCreateSourceReaderFromMediaSource(source_ptr.in(), pAttributes.in(), reader_ptr.out()) | comet::raise_exception; } comet::com_ptr sample; DWORD actualStreamIndex, flags; LONGLONG timestamp; try { do { reader_ptr->ReadSample( streamIndex, // Stream index. 0, // Flags. &actualStreamIndex, // Receives the actual stream index. &flags, // Receives status flags. ×tamp, // Receives the time stamp. sample.out() // Receives the sample or NULL. ) | comet::raise_exception; } while (sample == NULL && (flags & MF_SOURCE_READERF_STREAMTICK)); } catch (std::exception& e) { std::cerr << "Error getting frame: " << e.what() << std::endl; std::cerr << " flags: " << flags << std::endl; shutdown(); activate(); throw; } media_type cur_media_type; reader_ptr->GetCurrentMediaType(actualStreamIndex, cur_media_type._ptr.out()) | comet::raise_exception; //PrintAttributes(cur_media_type._ptr.in()); auto format = cur_media_type.format(); cv::Mat ret; DWORD bufCount; sample->GetBufferCount(&bufCount) | comet::raise_exception; DWORD bufIndex = bufferIndex >= 0 ? bufferIndex : bufCount - bufferIndex; if (bufCount > bufIndex) { comet::com_ptr buffer; sample->GetBufferByIndex(bufferIndex, buffer.out()) | comet::raise_exception; switch (format) { case MediaFormat::RGB24: case MediaFormat::ARGB32: case MediaFormat::RGB32: { comet::com_ptr buf2d = try_cast(buffer); //DWORD length; //buf2d->GetContiguousLength(&length) | comet::raise_exception; //ret.create(); //COMET_ASSERT(ret.dataend - ret.datastart == length); auto resolution = cur_media_type.resolution(); struct buf2d_lock { comet::com_ptr& buf2d; buf2d_lock(comet::com_ptr& buf2d, BYTE*& scanline0, LONG& pitch) : buf2d(buf2d) { buf2d->Lock2D(&scanline0, &pitch) | comet::raise_exception; } ~buf2d_lock() { buf2d->Unlock2D() | comet::raise_exception; } }; BYTE *scanline0; LONG pitch; buf2d_lock buf_lock_token(buf2d, scanline0, pitch); if (pitch >= 0) { cv::Mat buf2dmat(resolution, (format == MediaFormat::RGB24) ? CV_8UC3 : CV_8UC4, scanline0, pitch); buf2dmat.copyTo(ret); } else { cv::Mat buf2dmat(resolution, (format == MediaFormat::RGB24) ? CV_8UC3 : CV_8UC4, scanline0 + pitch*(resolution.height-1), -pitch); cv::flip(buf2dmat, ret, 0); } break; } case MediaFormat::MJPG: { struct buf_lock { comet::com_ptr& buffer; buf_lock(comet::com_ptr& buffer, BYTE*& data, DWORD& maxLength, DWORD& length) : buffer(buffer) { buffer->Lock(&data, &maxLength, &length) | comet::raise_exception; } ~buf_lock() { buffer->Unlock() | comet::raise_exception; } }; BYTE* data; DWORD length; DWORD maxLength; buf_lock buf_lock_token(buffer, data, maxLength, length); ret = cv::imdecode(cv::Mat(length, 1, CV_8U, data), cv::IMREAD_COLOR); break; } default: std::stringstream sstream; sstream << "Unknown media format: " << format; throw std::runtime_error(sstream.str()); } } return ret; } std::string name() const { return get_attr_str(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME); } std::string symlink() const { return get_attr_str(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK); } // TODO change //explicit operator bool() const //{ // return !activate_ptr.is_null(); //} enum Property { // CameraControl Exposure, Focus, Zoom, Pan, Tilt, Roll, Iris, // VideoProcAmp Brightness, Contrast, Hue, Saturation, Sharpness, Gamma, ColorEnable, WhiteBalance, BacklightCompensation, Gain }; bool has(Property property) const; cam_prop_range get_range(Property property) const; cam_prop_value get(Property property) const; void set(Property property, const cam_prop_value& value); static std::vector list_properties(); static const char* property_name(Property); std::vector media_types(int streamIndex = 0) const { auto pHandler = getMediaTypeHandler(streamIndex); DWORD cTypes = 0; pHandler->GetMediaTypeCount(&cTypes) | comet::raise_exception; std::vector ret; for (DWORD i = 0; i < cTypes; i++) { comet::com_ptr pType; pHandler->GetMediaTypeByIndex(i, pType.out()) | comet::raise_exception; ret.emplace_back(pType); } return ret; } media_type get_media_type(int streamIndex = 0) { media_type ret; getMediaTypeHandler(streamIndex)->GetCurrentMediaType(ret._ptr.out()) | comet::raise_exception; return ret; } void set_media_type(const media_type& type, int streamIndex = 0) { getMediaTypeHandler(streamIndex)->SetCurrentMediaType(type._ptr.in()) | comet::raise_exception; if (reader_ptr) { reader_ptr->SetCurrentMediaType(streamIndex, nullptr, type._ptr.in()) | comet::raise_exception; } } // TODO change //explicit operator bool() { // return !activate_ptr.is_null(); //} private: std::string get_attr_str(REFGUID guid) const { comet::task_ptr pStr; UINT32 strLen; activate_ptr->GetAllocatedString(guid, pStr.out(), &strLen) | comet::raise_exception; return comet::bstr_t(pStr.in(), strLen).s_str(); } comet::com_ptr getMediaTypeHandler(int streamIndex = 0) const { comet::com_ptr pPD; source_ptr->CreatePresentationDescriptor(pPD.out()) | comet::raise_exception; BOOL fSelected; comet::com_ptr pSD; pPD->GetStreamDescriptorByIndex(streamIndex, &fSelected, pSD.out()) | comet::raise_exception; comet::com_ptr pHandler; pSD->GetMediaTypeHandler(pHandler.out()) | comet::raise_exception; return pHandler; } comet::com_ptr activate_ptr; comet::com_ptr source_ptr; comet::com_ptr reader_ptr; }; class camera_helper { public: static std::vector get_all_cameras() { comet::com_ptr config; MFCreateAttributes(config.out(), 1) | comet::raise_exception; config->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID) | comet::raise_exception; comet::com_ptr_array com_ptr_array; MFEnumDeviceSources(config.in(), com_ptr_array.out(), com_ptr_array.out_count()) | comet::raise_exception; std::vector ret; for (size_t i = 0; i < com_ptr_array.count(); ++i) { ret.emplace_back(com_ptr_array[i]); } return ret; } static camera get_camera_by_symlink(const std::string& symlink) { // This is how you should do it, but for some reason it gives an activate pointer with no friendly name // comet::com_ptr config; // MFCreateAttributes(config.out(), 1) | comet::raise_exception; // // config->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID) | comet::raise_exception; // comet::bstr_t symlink_bstr(symlink); // config->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, symlink_bstr.c_str()) | comet::raise_exception; // // comet::com_ptr activate_ptr; // MFCreateDeviceSourceActivate(config.in(), activate_ptr.out()) | comet::raise_exception; // // return camera(activate_ptr); for(auto&& camera : get_all_cameras()) { if (camera.symlink() == symlink) return std::move(camera); } throw std::runtime_error("No camera with symlink: " + std::string(symlink)); } }; #endif // camera_helper_h__